self hosting
n8n Docker Compose Setup: Persistent Volume, .env, Postgres, and Webhooks
Use Docker Compose when you need repeatable self-hosting with persistent storage, private env files, Postgres planning, HTTPS, backups, and a correct public webhook URL.
Match your incident first
Start with the symptom you can prove
Container restarts after upgrade or compose pull
First check: Run docker compose ps and inspect the last 100 n8n log lines.
Wrong fix to avoid: Do not delete volumes to make the container start.
Verify: Container stays healthy and existing workflows/credentials remain visible.
Workflows disappear after recreating container
First check: Check compose volumes and whether /home/node/.n8n is persisted.
Wrong fix to avoid: Do not create new workflows until you confirm whether old data exists in a volume or backup.
Verify: A restart preserves workflows, credentials, executions, and settings.
Production URL is wrong in Docker deployment
First check: Inspect only the public URL-related env vars and the proxy route.
Wrong fix to avoid: Do not expose the container directly on the public internet to bypass proxy config.
Verify: Webhook URLs in the editor use the intended HTTPS domain.
- Use when
- self-hosted, Docker Compose
- First check
- Confirm workflows and credentials survive a restart before sending real traffic.
- Time to check
- 5-10 minutes
- Next step
- Match the symptom, then run the verification checks.
Independent third-party notes. n8n is a trademark of its owner and is referenced only for compatibility and troubleshooting context.
Quick Answer
Use Docker Compose when you need repeatable self-hosting with persistent storage, private env files, Postgres planning, HTTPS, backups, and a correct public webhook URL.
Does this match your symptom?
Self-hosted Docker setup looks risky
The instance runs, but persistence, .env files, public URLs, HTTPS, Postgres, or backups are unclear.
First check: Confirm workflows and credentials survive a restart before sending real traffic.
Problem Pattern
The user is moving from a one-line Docker run command to a repeatable self-hosted deployment and needs to avoid losing data or generating bad webhook URLs.
Launch gate
Do not send production traffic until these gates pass
Use this as a stop/go review before a Docker Compose n8n instance receives real schedules, webhooks, customer payloads, or paid workflow traffic.
| Gate | Pass condition | Stop if | Proof to keep |
|---|---|---|---|
| Persistence | /home/node/.n8n is mounted to a named volume or durable host path, and Postgres data is also on a persistent volume if Postgres is used. | Workflows disappear after docker compose down/up or the Compose file stores data only inside the container layer. | A screenshot or note from a recreate test showing workflows and credentials still exist. |
| Public URL | WEBHOOK_URL uses the public HTTPS origin, N8N_PROXY_HOPS matches the reverse proxy chain, and the proxy forwards X-Forwarded-Host and X-Forwarded-Proto. | Generated webhook URLs show localhost, a container hostname, http, a staging domain, or the stack exposes port 5678 publicly to work around routing. | The Webhook node production URL, external curl result to that exact URL, and a compose config note showing the raw n8n port is localhost/private. |
| Secrets | N8N_ENCRYPTION_KEY, database passwords, OAuth secrets, and provider tokens live in private env/secrets storage, not the repo. | A full .env dump is pasted into an issue, README, chat, or public deployment log. | A redacted variable checklist with only names and non-secret routing values. |
| Backup and restore | A backup includes database state, n8n data, binary data storage if used, and the encryption key; restore has been tested outside production. | The only backup is a SQL dump or a volume archive you have never restored. | Restore test notes: version, source backup, target instance, and one workflow smoke test. |
| Upgrade rollback | The image tag is pinned, release notes are checked, and a rollback plan exists before pulling a new n8n image. | The stack uses latest in production and upgrades happen without a backup snapshot. | Pinned image tag, backup timestamp, and post-upgrade smoke test list. |
If one gate fails, fix that gate before adding workers, affiliate traffic, or paid automations. Scaling a fragile Compose setup usually makes the incident harder to explain.
Version awareness
Last reviewed 2026-06-10
Key Facts
- Best fit
- Docker Compose is best for repeatable multi-service deployments.
- Persistent storage
- n8n data still needs a persistent volume.
- Source repository
- n8n points users to the n8n-hosting repository for Compose configurations.
- Public exposure
- Bind the n8n container port to localhost or a private network when a reverse proxy terminates HTTPS.
- Upgrade channel
- n8n documents stable and beta release channels; production Compose stacks should still test and pin a known-good version before upgrades.
Production Diagnostic Matrix
Turn checks into a brief| Exact symptom or log | Likely cause | First check | Wrong fix to avoid | Verification |
|---|---|---|---|---|
| Container restarts after upgrade or compose pull | Missing required env var, incompatible database migration, or invalid compose service config. | Run docker compose ps and inspect the last 100 n8n log lines. | Do not delete volumes to make the container start. | Container stays healthy and existing workflows/credentials remain visible. |
| Workflows disappear after recreating container | n8n data was stored inside the container instead of a persistent volume or external database. | Check compose volumes and whether /home/node/.n8n is persisted. | Do not create new workflows until you confirm whether old data exists in a volume or backup. | A restart preserves workflows, credentials, executions, and settings. |
| Production URL is wrong in Docker deployment | WEBHOOK_URL, N8N_HOST, N8N_PROTOCOL, or reverse proxy labels are missing or inconsistent. | Inspect only the public URL-related env vars and the proxy route. | Do not expose the container directly on the public internet to bypass proxy config. | Webhook URLs in the editor use the intended HTTPS domain. |
| Webhook works only after opening port 5678 publicly | The reverse proxy is not routing the request path, forwarded headers, or TLS origin correctly. | Check docker compose config for the port binding, then compare the proxy access log with an external curl to the HTTPS webhook URL. | Do not keep 0.0.0.0:5678 exposed as the production fix. | The HTTPS domain reaches n8n through the proxy, generated webhook URLs stay public and HTTPS, and direct public access to 5678 is closed. |
| Database or volume permission errors after moving hosts | File ownership, mounted path, or Postgres credentials changed during migration. | Check volume mount paths, container UID expectations, and database auth logs. | Do not chmod everything recursively without a backup. | n8n starts and can read previous workflows after one restart. |
| Backup exists but restore does not work | Backup missed the database, .n8n folder, encryption key, or binary data storage. | Verify restore in a disposable environment before touching production. | Do not assume a SQL dump alone is enough when binary data or SQLite is used. | A restored test instance can open workflows, decrypt credentials, and run a smoke test. |
| Upgrade breaks a previously working workflow | Node behavior, credential schema, API response shape, or custom node compatibility changed. | Compare failing execution before/after upgrade and check release notes for affected nodes. | Do not immediately downgrade without checking database migration and rollback safety. | Smoke tests for triggers, credentials, expressions, and outbound calls pass on the target version. |
Still blocked after these checks?
Use the brief to decide whether to keep fixing this setup, move the workload to n8n Cloud, or rebuild the self-hosted path on cleaner infrastructure.
Recommended Steps
- Start from an official or clearly maintained Compose example.
- Define the n8n service with image, ports, environment variables, and volumes.
- Add Postgres when you need a more production-oriented database setup.
- Keep secrets out of the committed Compose file by using an environment file or secret manager.
- Run the stack, then verify the UI and persistence.
- Keep a redacted launch evidence packet that proves routing, persistence, backup, and version choices without exposing secrets.
Verification
- docker compose ps shows the n8n service healthy or running.
- The n8n UI is reachable at the configured host and port.
- Restarting the stack does not delete workflows.
- docker compose config shows the expected n8n image, public URL variables, and N8N_PROXY_HOPS before deployment.
- The public HTTPS URL reaches the reverse proxy while the raw n8n port is not exposed publicly.
First Commands / Checks
docker compose ps docker compose logs n8n --tail=100 docker compose exec n8n printenv WEBHOOK_URL N8N_HOST N8N_PROTOCOL N8N_EDITOR_BASE_URL N8N_PROXY_HOPS docker compose config | sed -n '/ports:/,/environment:/p' docker compose config --services
docker compose ps
docker compose exec n8n printenv WEBHOOK_URL N8N_HOST N8N_PROTOCOL N8N_PROXY_HOPS DB_TYPE N8N_DEFAULT_BINARY_DATA_MODE Safe Copyable Config
services:
n8n:
image: docker.n8n.io/n8nio/n8n:stable
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
- N8N_HOST=automation.example.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://automation.example.com/
- N8N_PROXY_HOPS=1
- GENERIC_TIMEZONE=UTC
- TZ=UTC
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
- N8N_RUNNERS_ENABLED=true
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data: services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n_app
- POSTGRES_PASSWORD=REDACTED_CHANGE_ME
volumes:
- postgres_data:/var/lib/postgresql/data
n8n:
image: docker.n8n.io/n8nio/n8n:stable
restart: unless-stopped
depends_on:
- postgres
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n_app
- DB_POSTGRESDB_PASSWORD=REDACTED_CHANGE_ME
- N8N_ENCRYPTION_KEY=REDACTED_GENERATE_ONCE
- N8N_HOST=automation.example.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://automation.example.com/
- N8N_PROXY_HOPS=1
- GENERIC_TIMEZONE=UTC
- TZ=UTC
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
- N8N_RUNNERS_ENABLED=true
volumes:
- n8n_data:/home/node/.n8n
volumes:
postgres_data:
n8n_data: N8N_HOST=automation.example.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://automation.example.com/
N8N_PROXY_HOPS=1
GENERIC_TIMEZONE=UTC
TZ=UTC
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
N8N_RUNNERS_ENABLED=true
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n_app
DB_POSTGRESDB_PASSWORD=REDACTED_CHANGE_ME
N8N_ENCRYPTION_KEY=REDACTED_GENERATE_ONCE Warnings
- A Compose file is not a full production security plan.
- Back up both the database and the n8n data directory before upgrades.
- Do not let an unreviewed floating image update reach production; promote a tested stable or specific n8n image tag with rollback notes.
- Do not publish port 5678 directly to the internet just to make webhooks work; fix the reverse proxy, forwarded headers, and WEBHOOK_URL instead.
Best For
- Repeatable self-hosted deployments that need more than one service.
- Teams adding Postgres, Redis, reverse proxy, or backup jobs.
- Users who want deployment settings stored in files rather than one long Docker command.
Not For
- One-off local testing where a single Docker command is enough.
- Users who do not yet understand volumes, env files, and container networking.
- Managed-hosting use cases where infrastructure ownership is unwanted.
Common Mistakes
- Committing .env files that contain encryption keys, database passwords, or API credentials.
- Keeping workflows in a container filesystem instead of a volume.
- Using Compose without a backup and restore process.
- Changing env values without recreating or restarting the affected services.
- Publishing 5678 on all interfaces and bypassing the reverse proxy when WEBHOOK_URL or forwarded headers are wrong.
Examples
services:
n8n:
image: docker.n8n.io/n8nio/n8n
ports:
- "127.0.0.1:5678:5678"
env_file:
- .env
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data: TZ=Asia/Shanghai
GENERIC_TIMEZONE=Asia/Shanghai
N8N_HOST=automation.example.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://automation.example.com/ Workflow survives container recreate: yes
Credentials survive container recreate: yes
WEBHOOK_URL uses public HTTPS domain: yes
Reverse proxy forwards host and proto headers: yes
Raw 5678 port is not publicly reachable: yes
.env is ignored by git: yes
Backups include n8n volume and external database: yes
Restore test has been attempted: yes
Upgrade rollback plan exists: yes Small single instance: persistent n8n volume may be enough
Production-critical instance: plan Postgres and backups
Queue mode or multiple workers: Postgres plus Redis is expected
High-volume workflows: test memory, execution retention, and worker behavior before launch FAQ
Should I put secrets directly in docker-compose.yml?
Avoid it. Use an ignored .env file, platform secrets, or your deployment system's secret manager.
When should I add Postgres?
Add Postgres when the instance matters, workflow volume grows, queue mode is planned, or you want a more explicit database backup process.
Does Compose solve HTTPS?
Not by itself. You still need a reverse proxy, TLS termination, or another HTTPS layer.
Should I use the stable image tag in production?
Use stable only as a reviewed release channel, not as an automatic upgrade strategy. Test the target release, record the version, keep a backup, and promote a fixed known-good tag or digest when the workflow is production-critical.