n8n

n8n Docker Compose: Production Setup Guide

A step-by-step n8n Docker Compose guide with a copy-paste Postgres config, encryption keys, HTTPS, backups, and upgrades. Self-host n8n today.

S
Santhej Kallada
Founder, TaskifyLabs
Updated June 21, 2026
9 min read
Featured image for: n8n Docker Compose: Production Setup Guide

Running n8n with n8n Docker Compose is the cleanest way to self-host the automation platform without juggling manual docker run flags, volume mounts, and database connection strings by hand. A single docker-compose.yml file captures the whole stack — the n8n container, a PostgreSQL database, persistent volumes, and environment variables — so you can spin the whole thing up, tear it down, or move it to a new server with one command. This guide walks through a production-grade setup step by step, including a PostgreSQL backend, encryption keys, reverse-proxy notes, and the upgrade path.

At TaskifyLabs we run dozens of client automations on self-hosted n8n, and Compose is what we reach for first. It is reproducible, version-controllable, and easy to hand off. Below we give you a copy-paste compose file, explain every line that matters, and flag the mistakes that cause data loss or broken webhooks.

Why use n8n Docker Compose instead of plain Docker?

The primary reason to choose n8n Docker Compose over a raw docker run command is reproducibility. With Compose, your entire configuration lives in a text file you can commit to Git, review, and redeploy identically on any host. A bare docker run command works for a five-minute test, but it does not survive a server reboot, an upgrade, or a handoff to a teammate.

Compose also makes multi-container setups trivial. A serious n8n deployment is rarely just one container — it pairs n8n with a database and, often, a reverse proxy. Compose defines all of these services, their networks, and their startup order in one place.

What you get with the Compose approach

  • One-command lifecycle: docker compose up -d starts everything; docker compose down stops it cleanly.
  • Named volumes that persist your workflows, credentials, and database across container restarts and image upgrades.
  • A private network so n8n talks to Postgres by service name, never exposing the database to the internet.
  • Declarative environment config instead of a wall of -e flags you will forget.

If you are still deciding whether to self-host at all, our breakdown of n8n cloud pricing versus self-hosting covers the cost trade-offs in detail.

What do you need before you start?

You need three things: a host with Docker installed, a basic grasp of the terminal, and a domain name if you want webhooks and OAuth to work reliably.

Prerequisites checklist

  1. A Linux server (a $5–$10/month VPS is plenty for most teams) or a local machine for testing.
  2. Docker Engine 20.10+ and the Docker Compose v2 plugin. Confirm with docker --version and docker compose version. Note the space — modern Compose is docker compose, not the older docker-compose.
  3. At least 1 GB of RAM. n8n is light at idle but Node.js workflows with large payloads can spike memory.
  4. A domain or subdomain (for example n8n.yourcompany.com) pointed at your server's IP if this is going to production.

If Docker is not installed, the official get-docker.sh convenience script handles most Linux distributions in one command. We will assume Docker and the Compose plugin are ready from here on.

What does a complete n8n docker-compose setup look like?

Here is a production-oriented docker-compose.yml that pairs n8n with PostgreSQL. This is the n8n postgres docker compose pattern we recommend over the default SQLite store, because Postgres handles concurrent executions and large workflow histories far better.

Create a project directory, then save this as docker-compose.yml:

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=changeme_strong_password
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U n8n -d n8n']
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    ports:
      - '5678:5678'
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=changeme_strong_password
      - N8N_ENCRYPTION_KEY=generate_a_long_random_string_here
      - N8N_HOST=n8n.yourcompany.com
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.yourcompany.com/
      - GENERIC_TIMEZONE=Europe/London
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:
  n8n_data:

That is the full n8n compose file. The next sections explain the parts that trip people up.

How do the environment variables actually work?

The environment block is where self-hosted n8n succeeds or fails. Each variable maps to a specific behavior, and getting two of them wrong — the encryption key and the webhook URL — causes the most common support tickets.

The variables that matter most

  • DB_TYPE=postgresdb plus the DB_POSTGRESDB_* values tell n8n to use Postgres instead of the bundled SQLite file. The host is postgres — the service name from the compose file, resolved over the private network, not localhost.
  • N8N_ENCRYPTION_KEY encrypts stored credentials. Set it explicitly and never change it. If you omit it, n8n generates one inside the volume; if you later lose that volume, every saved credential becomes unreadable.
  • N8N_HOST, N8N_PROTOCOL, and WEBHOOK_URL must reflect your public domain. If WEBHOOK_URL is wrong, external services will call back to the wrong address and your trigger nodes will silently never fire.
  • GENERIC_TIMEZONE controls how Cron and Schedule nodes interpret times. Set it to your operating timezone to avoid jobs running an hour off.

Generate a strong encryption key with openssl rand -hex 24 and paste the result into N8N_ENCRYPTION_KEY. Store it somewhere safe — a password manager — because you will need it if you ever rebuild the stack from a database backup.

How do you start and verify the stack?

Bring everything up in detached mode, then confirm both containers are healthy before you trust the instance.

  1. From the directory containing your compose file, run docker compose up -d. Compose pulls the images, creates the network and volumes, and starts Postgres first thanks to the depends_on health condition.
  2. Check status with docker compose ps. Both services should show running, and postgres should report healthy.
  3. Tail the logs with docker compose logs -f n8n. Wait for the line Editor is now accessible via before opening the UI.
  4. Visit http://your-server-ip:5678 (or your domain behind a proxy). Create the owner account on first load.

If n8n keeps restarting, the logs almost always point to a database connection error — usually a password mismatch between the two services or a host value that is not the Postgres service name.

How do you put n8n behind HTTPS with a reverse proxy?

For anything beyond local testing you should terminate TLS in front of n8n rather than exposing port 5678 directly. A reverse proxy gives you HTTPS, a clean domain, and a single public entry point.

Reverse-proxy options

  • Caddy is the simplest: a two-line Caddyfile (n8n.yourcompany.com { reverse_proxy n8n:5678 }) gets you automatic Let's Encrypt certificates with zero manual renewal.
  • Traefik integrates natively with Compose via container labels and is excellent if you run several services on one host.
  • Nginx is the most familiar but requires you to manage certificates with Certbot yourself.

Whichever you pick, add the proxy as another service in the same compose file so it shares the private network and can reach n8n by service name. Then drop the public ports mapping on the n8n service — only the proxy needs to be internet-facing. Keep N8N_PROTOCOL=https and the WEBHOOK_URL on https:// so generated webhook and OAuth URLs match what the outside world sees.

How do you back up and restore an n8n Docker Compose deployment?

Because everything lives in two named volumes, backups are straightforward: dump the Postgres database and archive the n8n data volume.

  1. Database dump: docker compose exec postgres pg_dump -U n8n n8n > n8n_backup.sql. This captures all workflows, executions, and credential records.
  2. Volume archive: stop the stack and tar the volume, or use a helper container to copy /var/lib/docker/volumes/<project>_postgres_data and _n8n_data to safe storage.
  3. Restore: recreate the stack with the same N8N_ENCRYPTION_KEY, then pipe the SQL dump back in with psql. Without the original key, restored credentials will not decrypt.

Automate this with a nightly cron job that runs pg_dump and ships the file off-server. We treat the encryption key and the database dump as a pair — losing either one breaks a restore.

How do you upgrade n8n safely?

n8n ships updates frequently, and Compose makes upgrades a three-step ritual. The golden rule: back up first, then pull, then recreate.

  1. Run your database backup (see above) so you can roll back if a workflow behaves differently.
  2. Pull the new image: docker compose pull n8n.
  3. Recreate the container: docker compose up -d. Your volumes persist, so workflows and credentials carry over untouched.

Pinning a specific version tag instead of :latest — for example n8nio/n8n:1.62.0 — is the safer production habit. It makes upgrades intentional and prevents an unexpected restart from jumping several versions at once. Read the release notes between your current and target versions, since major bumps occasionally change node behavior.

What are the most common n8n docker compose mistakes?

Most failures we see fall into a short list of avoidable errors. Knowing them up front saves hours.

  • No explicit encryption key. The single most damaging mistake. Always set N8N_ENCRYPTION_KEY.
  • Wrong WEBHOOK_URL. Triggers from Slack, Stripe, GitHub, and similar services fail silently because callbacks go to the wrong address.
  • Using localhost for the database host. Inside Compose, containers reach each other by service name (postgres), never localhost.
  • Forgetting named volumes. Without n8n_data and postgres_data declared and mounted, a docker compose down followed by an image change can wipe your work.
  • Exposing port 5678 publicly with no auth or TLS. Put it behind a proxy and enable authentication.
  • Running on SQLite in production. Fine for a demo, but it struggles with concurrent executions; the n8n docker-compose Postgres pattern above avoids this.

If you want to extend the instance further, our guide to connecting n8n to the Model Context Protocol shows how to wire AI agents into your self-hosted workflows, and our roundup of real n8n workflow examples gives you tested automations to import once the server is live.

When should you self-host versus pay someone to run it?

Self-hosting with Compose is the right call when you want full data control, unlimited executions, and no per-step billing. It is the wrong call when nobody on the team owns server uptime, patching, and backups — an automation that silently dies at 2 a.m. costs more than the hosting it saved.

A reasonable middle path: self-host the platform, but bring in specialists to design the workflows and the deployment hardening. That is exactly the kind of work we do through our AI automation service — we stand up production n8n stacks, wire in monitoring, and ship working automations in around 14 days. If you are new to the tool entirely, start with our primer on what n8n is and how it works before committing to an architecture.

The honest trade-off is operational ownership. Compose removes the complexity of configuring n8n; it does not remove the responsibility of running it. Decide who owns that responsibility before you go live.

Self-hosting n8n with Docker Compose is mostly a one-time setup investment that pays off every day afterward. Once your docker-compose.yml is committed, your encryption key is stored safely, and your nightly backups are running, the platform becomes boring infrastructure — which is exactly what you want from automation that quietly runs your business. Get the Postgres backend, the encryption key, and the webhook URL right on day one, and the rest is just docker compose pull and docker compose up -d for years to come.

S
Written by
Founder, TaskifyLabs
Read more from Santhej

Questions

People also ask

For ops teams

Ready to ship in 14 days?

20-minute scoping call. Fixed-price quote on the call. Live software in 14 days.

Or read more for ops teams