Website • Documentation • GitHub • Community
n8n is an open-source workflow automation platform. Connect apps, automate tasks, and build complex workflows with a visual editor. A powerful, self-hosted alternative to Zapier and Make.
- Visual Workflow Builder — Drag-and-drop interface
- 400+ Integrations — Connect to popular services
- Code When Needed — JavaScript/Python for custom logic
- Self-Hosted — Full control over your data
- Webhooks — Trigger workflows from external events
- Scheduling — Run workflows on a schedule
- Error Handling — Built-in retry and error workflows
- Docker and Docker Compose
- External Docker network
- Reverse proxy (Caddy, Nginx, Traefik)
Two of the three are --internal (no internet egress):
docker network create n8n-front
docker network create --internal n8n-db
docker network create --internal n8n-runnerscp .env.example .env
nano .envThe .env.example file lists every required value with openssl recipes
for generating the two critical secrets:
N8N_ENCRYPTION_KEY— encrypts saved credentials. If you lose this after saving credentials, those credentials are unrecoverable. Generate with:openssl rand -hex 32N8N_RUNNERS_AUTH_TOKEN— shared between n8n and the task runner. Generate with:openssl rand -base64 32
Also set N8N_HOST and WEBHOOK_URL to match your public domain.
docker compose up -dNavigate to your configured domain and create an owner account.
| Variable | Description | Required |
|---|---|---|
POSTGRES_USER |
Postgres superuser (used only for init) | Yes |
POSTGRES_PASSWORD |
Postgres superuser password | Yes |
POSTGRES_DB |
Database name (default: n8n) |
Yes |
POSTGRES_NON_ROOT_USER |
n8n DB user (least privilege) | Yes |
POSTGRES_NON_ROOT_PASSWORD |
n8n DB user password | Yes |
N8N_ENCRYPTION_KEY |
32-byte hex — encrypts saved credentials | Yes |
N8N_RUNNERS_AUTH_TOKEN |
Shared n8n ↔ runner token | Yes |
N8N_HOST |
Hostname only (no scheme) | Yes |
WEBHOOK_URL |
Full external URL with trailing slash | Yes |
TZ |
Container timezone | No (default: Europe/Bratislava) |
n8n.example.com {
reverse_proxy http://localhost:5678
}
| Port | Service | Description |
|---|---|---|
| 5678 | HTTP | n8n editor & webhooks (bound to 127.0.0.1) |
| 5679 | RPC | Task-runner broker (internal network only) |
| 5680 | RPC | Runner health (internal network only) |
| Path | Description |
|---|---|
./db_storage |
PostgreSQL data (/var/lib/postgresql/data) |
./n8n_storage |
n8n workflows, settings, encrypted credentials |
The runner is stateless — no volume needed.
This template ships with a hardened default configuration:
| Layer | Setting | Effect |
|---|---|---|
| Capabilities | cap_drop: ALL (postgres adds 5 init caps; n8n/runner add none) |
No NET/SYS caps anywhere |
| Privileges | security_opt: no-new-privileges on all containers |
Setuid binaries cannot gain caps |
| IPC | ipc: private on all containers |
Isolated SysV/POSIX IPC namespace |
| Process budget | pids 200 / 500 / 300 (pg / n8n / runner) |
Caps fork sprawl |
| Memory / CPU | Per-container limits | One service can't starve the others |
| Three-network split | n8n-db and n8n-runners created with --internal |
Postgres + runner have no internet egress |
| Port exposure | 127.0.0.1:5678 only |
Only the reverse proxy can reach n8n |
| DB user | POSTGRES_NON_ROOT_USER (created by init-data.sh) |
n8n never has Postgres superuser |
| Task runner | Code/Function nodes execute in a separate container | Workflow JavaScript can't touch n8n's process memory |
| Healthchecks | pg_isready / wget /healthz — no creds on cmdline |
Built-in scripts only |
| Telemetry | N8N_DIAGNOSTICS_ENABLED=false |
No phone-home |
| File perms | N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true |
n8n refuses to start if ~/.n8n/config is world-readable |
Code nodes can't make outbound HTTP calls by default because the runner is on an internal network. If a workflow needs external HTTP from JavaScript, use n8n's HTTP Request node (runs in the n8n container, has internet via
n8n-front). To allow outbound from the runner anyway, dropinternal: trueon then8n-runnersnetwork.
Why
N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0? n8n's broker defaults to127.0.0.1, which is only reachable inside the n8n container. With the runner in a separate container, the bind has to widen.0.0.0.0means all interfaces of the n8n container — not all host interfaces. Three layers actually contain the broker:
- The
n8n-runnersnetwork is created with--internal, so it has no route to the host or the internet.- There is no
ports:mapping for 5679, so the host kernel never sees that port —iptables -Lwon't show it, nothing from outside Docker can reach it.N8N_RUNNERS_AUTH_TOKENauthenticates every task RPC, so even a process that somehow ended up on then8n-runnersnetwork couldn't dispatch work without the secret.The listen address inside the container is a routing concern; the
--internalnetwork is the security boundary.
Postgres image upgrade (optional): swap
postgres:18.4fordhi.io/postgres:18(Docker Hardened Images) for a distroless base with faster CVE patches. Requires a DHI subscription.
To enable email notifications, uncomment and configure the SMTP settings in docker-compose.yml:
- N8N_EMAIL_MODE=smtp
- N8N_SMTP_HOST=smtp.example.com
- N8N_SMTP_PORT=587
- N8N_SMTP_USER=your-user
- N8N_SMTP_PASS=your-password
- N8N_SMTP_SENDER=n8n@example.com- ☁️ n8n Cloud — Managed hosting
- ⭐ Star on GitHub
- 💬 Join Community
- 📖 Documentation
n8n is released under a Sustainable Use License.
