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

Jump to checks

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

Open Docker checklist

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.

Compare tools
  1. Start from an official or clearly maintained Compose example.
  2. Define the n8n service with image, ports, environment variables, and volumes.
  3. Add Postgres when you need a more production-oriented database setup.
  4. Keep secrets out of the committed Compose file by using an environment file or secret manager.
  5. Run the stack, then verify the UI and persistence.
  6. 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

Check container state Use first when n8n is down, restarting, or behaving differently after a deploy.
docker compose ps
Secrets note
This lists service names and status only; it should not print credential values.
Verification
n8n, database, Redis, and worker services are running or the failing service is obvious.
Read recent n8n logs Use when the editor, webhook, or startup path fails.
docker compose logs n8n --tail=100
Secrets note
Review before sharing; remove tokens, private hostnames, and customer payloads.
Verification
The log contains a timestamped error, migration message, or clean startup line.
Check public URL variables only Use when webhook URLs show localhost, http, the wrong domain, or a proxy/header mismatch.
docker compose exec n8n printenv WEBHOOK_URL N8N_HOST N8N_PROTOCOL N8N_EDITOR_BASE_URL N8N_PROXY_HOPS
Secrets note
Do not run a full env dump in public channels; print only these non-secret routing names.
Verification
Values point to the intended HTTPS public domain, N8N_PROXY_HOPS matches the number of reverse proxies, and the proxy forwards host/proto headers.
Confirm the raw n8n port is not public Use before launch or after a rushed webhook routing fix.
docker compose config | sed -n '/ports:/,/environment:/p'
Secrets note
This should show service routing only; review output before sharing in case labels include private hostnames.
Verification
The n8n port is bound to 127.0.0.1, a private network, or omitted behind an internal proxy; it is not published as 0.0.0.0:5678.
Create a redacted launch evidence packet Use before production traffic, after a host move, or before an upgrade review.
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
Secrets note
Print only non-secret routing and runtime names. Do not include N8N_ENCRYPTION_KEY, database passwords, OAuth secrets, provider tokens, or full env dumps.
Verification
The packet proves the expected services exist, n8n is running, the public HTTPS URL is configured, proxy hops are intentional, and the storage/database mode is known.

Safe Copyable Config

Minimal Docker Compose for a single n8n instance Use for a small self-hosted instance before adding queue mode or external Postgres.
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:
Docker Compose with Postgres for n8n data Use when SQLite is no longer enough or when planning queue mode later.
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:
.env.example with redacted production placeholders Use as a review checklist for self-hosted n8n environment variables.
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

Compose skeleton for n8n with persistent storage This is a minimal pattern to adapt, not a complete hardened production stack.
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:
Example .env values Keep this file private if it contains secrets.
TZ=Asia/Shanghai
GENERIC_TIMEZONE=Asia/Shanghai
N8N_HOST=automation.example.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://automation.example.com/
Pre-launch Docker Compose checks Run this before sending real webhooks or production schedules into the instance.
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
When to add Postgres and Redis Keep the first deployment simple unless the operational need is real.
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.

Sources