Fast search changes how people use internal tools. A support portal feels better when answers appear while a teammate types, a documentation site becomes more useful when typos are tolerated, and a product catalog is easier to operate when indexing is controlled by a small API instead of a full search cluster. Typesense is a practical fit for that middle ground: it is lightweight, fast, and simple enough to run as a dedicated service for one team or one application.
This guide follows the same production pattern we use for small, dependable services on Ubuntu: Docker Compose for repeatable operations, Caddy for automatic HTTPS, a private admin API key, a separate search-only key, predictable backups, and checks that prove the endpoint is healthy before an application depends on it. The example uses search.example.com; replace it with your real DNS name before deployment.
Architecture and flow overview
The deployment has two containers. Caddy listens on ports 80 and 443, obtains certificates, applies basic security headers, and proxies traffic to Typesense on the private Compose network. Typesense stores indexes under /opt/typesense/data. Applications use a search-only key from browsers or front-end clients, while administrators keep the master key on the server or in a backend secret manager. Backups archive the data directory and deployment files so a restore can rebuild the same service on another host.
The request flow is intentionally boring: user searches in your application, the application sends a query to the public HTTPS endpoint with a limited key, Caddy forwards the request, and Typesense returns ranked results. Indexing jobs, migrations, or admin tasks should run from a trusted backend using the admin key. This separation keeps accidental browser exposure from becoming full index control.
Prerequisites
- An Ubuntu 22.04 or 24.04 server with Docker Engine and the Compose plugin installed.
- A DNS record such as
search.example.compointing to the server. - Ports 80 and 443 open from the internet for Caddy certificate issuance.
- A non-root sudo user and a basic firewall policy.
- A plan for where index backups will be copied after the local archive is created.
Step-by-step deployment
1. Create the application directory and keys
Keep the service isolated under /opt/typesense. The first command creates local directories and two random secrets. The admin key controls the server; the search-only key is a placeholder that you can replace with a scoped key created through the API after your first collection exists.
sudo install -d -m 0750 -o $USER -g $USER /opt/typesense
cd /opt/typesense
mkdir -p data backups caddy
umask 077
openssl rand -base64 32 > .typesense_admin_key
openssl rand -base64 32 > .typesense_search_key
If the copy button is unavailable in your browser, select the command block manually and copy it.
2. Write environment values
Store secrets outside the Compose file so they can be rotated without editing infrastructure definitions. On a production server, paste the generated key values into this file and restrict permissions immediately.
cat > .env <<'EOF'
TYPESENSE_VERSION=0.25.2
TYPESENSE_HOST=search.example.com
TYPESENSE_ADMIN_API_KEY=replace-with-the-value-from-.typesense_admin_key
TYPESENSE_SEARCH_ONLY_API_KEY=replace-with-the-value-from-.typesense_search_key
EOF
chmod 600 .env
If the copy button is unavailable in your browser, select the command block manually and copy it.
3. Define the Compose stack
The Typesense container is not published directly to the host. Only Caddy is reachable from the network, which simplifies TLS and keeps the internal port private. The health check gives Compose and operators a fast signal that the API is responding.
cat > compose.yml <<'EOF'
services:
typesense:
image: typesense/typesense:${TYPESENSE_VERSION}
restart: unless-stopped
command:
- --data-dir=/data
- --api-key=${TYPESENSE_ADMIN_API_KEY}
- --enable-cors
- --log-level=info
volumes:
- ./data:/data
expose:
- "8108"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8108/health"]
interval: 30s
timeout: 5s
retries: 5
caddy:
image: caddy:2
restart: unless-stopped
depends_on:
typesense:
condition: service_healthy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
EOF
If the copy button is unavailable in your browser, select the command block manually and copy it.
4. Add the Caddy reverse proxy and start the service
Caddy handles certificate issuance automatically once DNS resolves to the server. If this host already runs a shared reverse proxy, adapt the upstream target to typesense:8108 and keep the same TLS and header intent.
cat > Caddyfile <<'EOF'
{$TYPESENSE_HOST} {
encode zstd gzip
reverse_proxy typesense:8108
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
EOF
docker compose --env-file .env up -d
If the copy button is unavailable in your browser, select the command block manually and copy it.
Configuration and secrets handling best practices
Treat the admin API key like a database password. It can create collections, delete documents, and generate scoped keys. Do not place it in browser code, analytics snippets, static JavaScript bundles, or mobile app configuration. Use it only from trusted backend jobs that index content or manage schema changes.
For client-side search, create scoped keys that can only search specific collections. When you add collections for docs, tickets, or products, issue one limited key per use case. That makes rotation safer and prevents one leaked key from exposing unrelated indexes. If your application supports tenant isolation, generate keys with filters so one customer can only search its own documents.
ADMIN_KEY=$(cat .typesense_admin_key)
curl -sS -H "X-TYPESENSE-API-KEY: $ADMIN_KEY" -X POST "https://search.example.com/keys" -H 'Content-Type: application/json' -d '{"description":"docs-search-only","actions":["documents:search"],"collections":["docs"]}'
If the copy button is unavailable in your browser, select the command block manually and copy it.
Plan schema changes carefully. Typesense is forgiving for small collections, but production search quality depends on explicit fields, sensible sorting attributes, and tested synonyms. Keep collection schema definitions in your application repository. Rebuild indexes in staging before changing tokenization, typo tolerance, or ranking rules on the live service.
Verification checklist
After the stack starts, verify three layers: the public HTTPS endpoint, the Typesense API, and the containers. The health endpoint should return a positive response, the collections endpoint should authenticate with the admin key, and Compose should show both services running.
curl -fsS https://search.example.com/health
ADMIN_KEY=$(cat .typesense_admin_key)
curl -fsS -H "X-TYPESENSE-API-KEY: $ADMIN_KEY" https://search.example.com/collections | jq .
docker compose ps
docker compose logs --tail=80 typesense
If the copy button is unavailable in your browser, select the command block manually and copy it.
- Confirm
https://search.example.com/healthreturns success from a machine outside the server. - Confirm Caddy logs show a certificate issued for the expected hostname.
- Confirm no host port exposes Typesense directly except through Caddy.
- Create a small test collection and search it before wiring production traffic.
- Document where the admin key is stored and who can rotate it.
Backups and restore testing
Search indexes are often rebuildable from source systems, but restore speed matters during an outage. A nightly archive gives you a quick recovery path while you decide whether to rebuild from the primary application database. Copy archives to object storage or another host; local-only backups do not protect against disk loss.
cat > backup-typesense.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
cd /opt/typesense
stamp=$(date -u +%Y%m%dT%H%M%SZ)
tar -C /opt/typesense -czf "backups/typesense-${stamp}.tgz" data .env Caddyfile compose.yml
find backups -type f -name 'typesense-*.tgz' -mtime +14 -delete
EOF
chmod 700 backup-typesense.sh
(crontab -l 2>/dev/null; echo '17 2 * * * /opt/typesense/backup-typesense.sh') | crontab -
If the copy button is unavailable in your browser, select the command block manually and copy it.
Test the restore process before you need it. A restore should stop the stack, unpack the selected archive, start the service, and verify health. If your data volume is large, schedule a maintenance window and measure how long the archive and extraction steps take.
cd /opt/typesense
docker compose down
tar -xzf backups/typesense-YYYYMMDDTHHMMSSZ.tgz -C /opt/typesense
docker compose --env-file .env up -d
curl -fsS https://search.example.com/health
If the copy button is unavailable in your browser, select the command block manually and copy it.
Common issues and fixes
Caddy cannot issue a certificate
Check DNS first. The hostname must resolve to the server running Caddy, and ports 80 and 443 must be reachable. If another reverse proxy already owns those ports, either move this Caddy instance behind the shared proxy or add the Typesense route to the existing proxy instead of running a second public listener.
Search works with the admin key but fails in the browser
Verify that the browser is using a search-only key with permission for the target collection. Also check CORS expectations. The example enables CORS for simplicity, but high-security environments may prefer to put search behind an application backend that injects keys server-side.
Indexing is slow or memory usage climbs
Watch document size, field count, and sort fields. Large nested payloads make indexes heavier. Store only searchable or display-required fields in Typesense and keep canonical records in the primary database. For sustained ingestion, batch documents and monitor container memory pressure.
Backups are much larger than expected
Search indexes can grow quickly when documents contain repeated descriptions, logs, or large metadata blobs. Review schema fields and remove content that is not used for search or display. If the index is easy to rebuild, you may choose to back up deployment configuration and source data instead of every generated index file.
FAQ
Can I run Typesense on the same server as my application?
Yes, for small and medium workloads. Keep it in a separate Compose project, monitor memory usage, and avoid publishing the Typesense port directly. If search becomes business critical, move it to a dedicated VM so indexing spikes do not affect the application.
Should the public website use the admin API key?
No. Browser code should use a scoped search-only key or call your backend. The admin key should stay on trusted servers because it can modify collections and documents.
How do I rotate a leaked search key?
Create a new scoped key, deploy it to the affected application, verify searches still work, and then revoke the old key. Keep key descriptions meaningful so you can identify which app or environment owns each key.
Do I need PostgreSQL or Redis for this deployment?
No. Typesense stores its own index data on disk. Your application may still use PostgreSQL as the source of truth, but Typesense does not require a separate database for this single-node deployment.
How should I monitor Typesense?
Start with HTTPS health checks, container restart alerts, disk usage, and indexing job failures. For deeper monitoring, export container metrics and alert when memory, CPU, or data directory growth deviates from normal behavior.
Can I cluster this later?
Yes, but keep the first deployment simple unless availability requirements demand clustering now. Document schema and indexing jobs carefully so moving from one node to a clustered layout is an operational change rather than an application rewrite.
What is the safest way to expose search to customers?
Use scoped keys with collection restrictions and, when needed, filter restrictions. If results contain sensitive tenant data, route queries through your backend so authorization is checked before search requests leave the application.
Related internal guides
- Deploy Meilisearch with Docker Compose + NGINX + UFW
- Deploy SearXNG with Docker Compose + Caddy + Redis
- Deploy Paperless-ngx with Docker Compose + Caddy + PostgreSQL + Redis
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.