Install using Docker Compose
Who this is for
This guide walks through building your own Docker Compose setup by hand, including the sandbox stack that powers n8n Assistant. Use it if you want full control over your configuration, or need to fold n8n into an existing Compose project.
If you just want n8n (and n8n Assistant) running quickly without writing any files yourself, use the one-line setup instead. It sets up everything below automatically.
What you need before you start
Docker Engine and Docker Compose v2. Run
docker compose versionto check.At least 4 GB of RAM and 2 vCPUs. The sandbox that runs AI-generated code (
sandbox-runner-1) uses Docker-in-Docker, which needs more headroom than a typical container.
Step 1: Create a project folder
mkdir n8n && cd n8nStep 2: Create .env
This file holds the secrets the sandbox services use to talk to each other. Create a file named .env with your own values in place of the placeholders and keep this file out of version control.
# Sandbox service secrets — pick your own values
SANDBOX_API_KEYS=change-me-api-key
SANDBOX_API_RUNNER_REGISTRATION_TOKEN=change-me-registration-token
SANDBOX_API_RUNNER_API_KEY=change-me-runner-key
# Must match a value in SANDBOX_API_KEYS above — this is how n8n authenticates to the sandbox
N8N_SANDBOX_SERVICE_API_KEY=change-me-api-key
# Web search: secret for the bundled SearXNG instance — pick your own value
SEARXNG_SECRET=change-me-searxng-secret
N8N_INSTANCE_AI_SEARXNG_URL=http://searxng:8080You don't need an AI provider key yet; see Turn on n8n Assistant below once everything's running.
Step 3: Create searxng-settings.yml
The stock SearXNG image only serves HTML; n8n's web search needs its JSON API, which this file turns on.
Step 4: Create compose.yml
This defines every service you're setting up: n8n itself, the sandbox stack that lets n8n Assistant safely run code, and SearXNG for web search.
What you've just set up
n8n
The workflow editor itself, available at http://localhost:5678.
sandbox-certs
Runs once to generate the TLS certificates the other sandbox services need, then exits.
sandbox-api
The control plane n8n talks to when n8n Assistant needs to run code.
sandbox-runner-1
Does the actual work; a privileged Docker-in-Docker container that creates and runs the sandboxes.
searxng
Bundled web search backend for n8n Assistant.
This bundles n8n's own sandbox (n8n-sandbox), which is a good fit for local development and testing. For a production instance, n8n currently recommends Daytona as the sandbox provider instead. See Set up n8n Assistant for how to configure a Daytona sandbox.
There's no database service defined here. n8n falls back to its built-in SQLite database, stored inside the container unless you mount a volume for it. For a production instance, swap in Postgres. See Use PostgreSQL instead of SQLite below.
Step 5: Start everything
Wait until sandbox-api shows healthy; sandbox-runner-1 and n8n will then start automatically.
Step 6: Verify it's working
Launch n8n by pointing your web browser to http://localhost:5678
Optional: Turn on n8n Assistant
Everything above runs the full sandbox stack, but n8n Assistant itself stays off until you give it a model to use. You can do this from the n8n UI (in the instance's AI settings) once n8n is running, or using .env if you'd rather configure it before first login:
Add your AI provider key to
.env:Restart n8n so it picks up the change:
Web search runs through the bundled SearXNG service by default. If you'd rather use Brave Search instead, you can set it from the UI or add your Brave API key to .env; it takes priority over SearXNG once set:
Full setup steps, including the supported model providers, are in Set up n8n Assistant.
Optional: Use PostgreSQL instead of SQLite
SQLite is fine for trying things out, but for a production instance that must handle more than a handful of users or workflows running around the clock use Postgres instead.
Add the Postgres credentials to
.env, alongside the sandbox secrets:Add a
postgresservice tocompose.yml, and a volume for its data:Postgres 18 changed where it stores data by default. Setting `PGDATA` keeps it in the same folder as earlier versions, so the volume mount stays the same. Don't remove that line: without it, Postgres 18 writes somewhere the volume doesn't cover and your database starts empty.
Already running an older Postgres? Moving straight to 18 is a major version upgrade, and Postgres can't open a data directory written by an older major. Bumping the image tag on an existing setup fails with `database files are incompatible with server`. Your data stays intact. Back up first with `pg_dumpall`, then follow the official [PostgreSQL upgrade guide](https://www.postgresql.org/docs/18/upgrading.html).
Point n8n at it by adding these to the n8n service's environment block, and making it wait on Postgres too:
Restart everything:
n8n migrates itself to the new Postgres database on startup. Existing SQLite data doesn't carry over automatically. This setup is for a fresh instance, not an in-place migration.
Troubleshooting
sandbox-api or sandbox-runner-1 fail to start, cert errors
sandbox-certs didn't complete. Check docker compose logs sandbox-certs.
sandbox-api never becomes healthy
Check its logs; also confirm wget actually exists in that image.
sandbox-runner-1 crash-loops on startup with ... must be set errors
It's missing required environment variables, most commonly SANDBOX_RUNNER_API_KEYS or SANDBOX_RUNNER_REGISTRATION_TOKEN. For the full list of environment variables the runner requires, run strings /usr/local/bin/sandbox-runner | grep -oE 'SANDBOX_[A-Z_]+ must be set' inside the runner container.
Runner never registers with the API
SANDBOX_RUNNER_REGISTRATION_TOKEN mismatch, or SANDBOX_RUNNER_API_GRPC_ADDR wrong.
n8n's sandbox calls fail
Sandbox URL/key in .env doesn't match sandbox-api's address or SANDBOX_API_KEYS.
Works on Linux, fails on WSL
Usually a bind-mount path issue. Keep the project inside the WSL filesystem, not /mnt/c/....
Security checklist
sandbox-runner-1(privileged: true, Docker-in-Docker) is never exposed to the public internet. Treat it as equivalent to root on the host.Only n8n's port is open on your cloud firewall.
SANDBOX_API_KEYS, the registration token, and the runner key are unique, not left aschange-me-..., and rotated periodically.sandbox-apiandsandbox-runner-1do not useenv_file: .env. Each only receives the specific variables it needs, explicitly, in itsenvironmentblock. The model API key, Brave key, Postgres password, and n8n encryption key never reach the sandbox containers.The mTLS keys under the
sandbox-tlsvolume, including the root CA key, are locked down to0600and owned only by the service that needs them (sandbox-apifor its own key; root for the runner's key and the CA key). None of them are world-readable.You have a plan to regenerate the
sandbox-tlsvolume. The certssandbox-certsgenerates don't autorenew.
Service architecture
n8n sends code execution requests to sandbox-api, which hands them to sandbox-runner-1, which creates and runs the actual sandbox containers. sandbox-certs runs once at startup to generate the TLS certificates the other two need and then exits; everything else waits on it.
Last updated
Was this helpful?