laravel

How I Got Typesense Running in Docker Swarm Using a Secret API Key

Recently, I needed to run Typesense, a fast and open-source full-text search engine, inside a Docker Swarm environment. Everything went smoothly… until I tried to securely pass the API key using Docker Secrets.

The official documentation doesn’t directly cover this scenario, and the seemingly “correct” solutions didn’t work at all. In this post, I’ll document everything I tried — and the one solution that actually worked.


⚠️ First attempt: Classic environment variable

At first, I tried something simple almost like in official documentation:

command: '--api-key=xyz ...'

✅ Works for development ❌ But it’s insecure — the key appears in docker inspect, logs, and debugging tools, and Github repository.

Next try also not working:

environment:
  api-key: xyz

So I moved on to a safer method: Docker Secrets.


❌ Second attempt: Secret mount + _FILE variable

Then I tried a common pattern that works with many official Docker images like MySQL:

secrets:
  - typesense_api_key
environment:
  TYPESENSE_API_KEY_FILE: /run/secrets/typesense_api_key

But Typesense doesn’t read these variables automatically. It doesn’t have an entrypoint.sh that parses _FILE variables like other images do.

➡️ Result: Typesense didn’t start at all. Running:

docker service logs po_typesense

showed the message:

You can also pass these arguments as environment variables such as TYPESENSE_DATA_DIR, TYPESENSE_API_KEY, etc.

This means the server didn’t receive the API key — not via CLI args, not via standard environment variables. So the service failed to start entirely.


❌ Third attempt: Interpolation with cat

Next, I tried reading the secret using cat in the command:

command: '--api-key=$(cat /run/secrets/typesense_api_key)'

💥 Result: Docker Compose throws a parsing error:

invalid interpolation format... you may need to escape any $ with another $

Even with sh -c and escaping, this approach didn’t work in this form.


✅ The correct solution: entrypoint with shell + cat

The only working solution was using a custom entrypoint that runs a shell script and reads the secret at runtime:

entrypoint: >
  sh -c "exec /opt/typesense-server --data-dir /data --api-key=$$(cat /run/secrets/typesense_api_key)"

Full working docker-compose.yml for Swarm:

version: '3.8'

services:
  typesense:
    image: 'typesense/typesense:26.0'
    volumes:
      - typesense_data:/data
    secrets:
      - typesense_api_key
    entrypoint: >
      sh -c "exec /opt/typesense-server --data-dir /data --api-key=$$(cat /run/secrets/typesense_api_key)"
    ports:
      - '8108:8108'

secrets:
  typesense_api_key:
    file: ./typesense_api_key.txt

volumes:
  typesense_data:

📌 Key points:

  • The $$ is required to escape the $ so Docker Compose doesn’t try to interpret it.
  • Using exec ensures typesense-server is PID 1, so signals and restarts work correctly.

🧪 Testing

After deploying:

docker stack deploy -c docker-compose.yml search

Check the logs:

docker service logs search_typesense

You should see something like:

[info][server] API Key provided
[info][server] Typesense server listening on port 8108

I must mention that this is not enough to keep the api-key secret. For example: my laravel application uses typesense/typesense-instantsearch-adapter which displays the api-key in the frontend. And in order not to display the main api-key to the public I generated an api-key exclusively for search according to the official documentation.

🧩 Conclusion

While this setup might seem simple, using Docker Secrets with applications that don’t support _FILE variables requires a little scripting. In the case of Typesense, the only reliable way is to read the secret using cat inside a shell command at container startup.

If you’re running Typesense in production and need a secure setup, this method is safe and Swarm-compatible.

Say hello 👋

Let's Connect!

Let's create something unique together! Here's how you can reach out to me!