Skip to main content
Lifetime license included with every purchase
n8n dockerself-hostingworkflow automationn8n setup

How to Run n8n in Docker: A Production Setup Guide

Set up n8n in Docker with PostgreSQL, Nginx, and SSL in 30 minutes. Covers the exact Docker Compose file and env vars that hold up under production load.

Nn8n Marketplace Team·June 2, 2026·8 min read

The official n8n docker quickstart gets you to a login screen. What it doesn't cover is the configuration that keeps n8n running six months later without silent failures, lost credentials, or executions piling up in an SQLite file that's grown too large to query.

n8n Docker setups fail in predictable ways: SQLite under concurrent load, missing persistent volume mounts, webhook URLs that stop resolving after a server restart, or an N8N_ENCRYPTION_KEY that auto-regenerates and orphans every stored credential overnight. This guide covers all of it.

The setup below is tested against n8n v1.x on a $20 DigitalOcean Droplet (2 vCPU, 4 GB RAM) handling 500+ executions per day without issues.

What You Can Run Once the Stack Is Live

Self-hosted n8n unlocks workflow types that would cost real money on cloud platforms:

  • High-frequency polling (every 60 seconds, not every 15 minutes)
  • Executions that fan out across 10,000+ items in a single run
  • Webhooks receiving payloads from any external source without IP allowlisting
  • Long-running Code nodes calling slow APIs without cloud execution timeouts
  • Credentials stored encrypted on your own infrastructure
  • Multi-user instances where different teams work without seeing each other's workflows

The Competitor Price Intelligence template shows what becomes practical: it polls competitor pages every hour, runs a comparison against a Sheets baseline, and Slacks the diff. On n8n Cloud, that cadence costs per execution. On a self-hosted instance, it's free beyond the server bill.

Before you start

You'll need Docker and Docker Compose installed on a Linux server (Ubuntu 22.04 works well), plus a domain name with an A record pointing to the server's IP. The guide assumes SSH access is in place.

The n8n Docker Compose Structure

A production n8n Docker stack runs three services: the n8n container, PostgreSQL, and an Nginx reverse proxy. Here's the skeleton before any configuration:

services:
  postgres:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data

  n8n:
    image: n8nio/n8n
    ports:
      - "5678:5678"
    depends_on:
      - postgres
    volumes:
      - n8n_data:/home/node/.n8n

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - n8n

volumes:
  postgres_data:
  n8n_data:

That's the structure. The rest is configuration — and that's where the official docs leave you on your own.

n8n Docker Configuration: Step by Step

1. Set the Encryption Key Before Anything Else

Don't let n8n auto-generate N8N_ENCRYPTION_KEY. If the container restarts and auto-generation picks a different value, every stored credential silently breaks. No error. No warning. The workflows just stop authenticating. Generate the key yourself:

openssl rand -hex 32

Store the output somewhere permanent before the next step. You'll set it as:

N8N_ENCRYPTION_KEY=<your-generated-key>

This is the one configuration mistake that can't be undone without re-entering every credential manually.

2. Configure PostgreSQL

The n8n container connects to Postgres via standard environment variables. The full set you need:

DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=<strong-password>

Set the matching variables on the Postgres service:

POSTGRES_DB=n8n
POSTGRES_USER=n8n
POSTGRES_PASSWORD=<same-strong-password>

One mistake that trips people up: using localhost for DB_POSTGRESDB_HOST instead of the service name postgres. Inside Docker Compose, containers resolve each other by service name. localhost refers to the container itself.

3. Set the Webhook URL

WEBHOOK_URL=https://n8n.yourdomain.com

Without this, n8n generates webhook URLs using the container's internal hostname, which can't be reached from outside. Stripe, GitHub, and every other external sender won't reach an internal container address. Set this to your public domain before creating any webhook-triggered workflows — you can't rename a webhook URL after a workflow is deployed without breaking existing integrations.

4. Wire the Nginx Config

Nginx handles SSL termination and proxies traffic to n8n on port 5678. A working config:

server {
    listen 443 ssl;
    server_name n8n.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://n8n:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_read_timeout 120s;
    }
}

The 120-second proxy_read_timeout matters. Without it, Nginx closes long-running webhook executions before they finish, and the sender gets a timeout even when the workflow completed successfully. Particularly relevant for any workflow that calls a slow external API synchronously.

5. SSL with Certbot

docker run --rm \
  -v /etc/letsencrypt:/etc/letsencrypt \
  -v /var/www/certbot:/var/www/certbot \
  certbot/certbot certonly --webroot \
  --webroot-path /var/www/certbot \
  -d n8n.yourdomain.com

Worth the 5 minutes. Then set up a cron job to renew every 60 days. Certbot certificates expire after 90 days, and a lapsed cert breaks all incoming webhooks without any n8n error message pointing at the real cause.

Volume mounts are non-negotiable

The n8n_data volume at /home/node/.n8n stores workflows, credentials, and execution metadata. Skip the named volume mount and every container restart wipes the instance clean. Verify with docker volume ls before assuming it's working. A missing volume is silent until you restart the container and find the dashboard empty.

Production Patterns That Break Without Warning

Two failure modes appear regularly in n8n Docker deployments. Neither logs a clear error until the damage is done.

Pattern 1: No execution pruning. n8n stores every execution result in the database by default. A workflow running every minute generates 1,440 records per day — that's 525,600 records per year, per workflow. Query times degrade steadily, and the PostgreSQL database grows without bound. Costly. Add these two variables:

EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168

168 is hours (7 days). Adjust based on how long you need execution history. The pruning runs in the background and doesn't affect running workflows.

Pattern 2: Queue mode disabled under load. By default, n8n processes executions inline — the main process runs the workflow directly. Above roughly 50 concurrent executions, this creates a backlog that grows faster than it drains. Enable queue mode:

EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis

That requires adding a Redis service to the Compose stack. It's a 15-minute change, and it's worth making before the instance gets busy rather than after a backlog causes visible failures.

Production env var checklist

N8N_ENCRYPTION_KEY, DB_TYPE plus all DB_POSTGRESDB_* vars, WEBHOOK_URL, N8N_HOST, N8N_PORT, EXECUTIONS_DATA_PRUNE, EXECUTIONS_DATA_MAX_AGE — these are the minimum for a stable deployment. Add EXECUTIONS_MODE and QUEUE_BULL_REDIS_HOST once you're running queue mode.

Nodes You'll Use Most After Setup

NodePurpose
n8n-nodes-base.scheduleTrigger v1.2Cron-based triggers; use maxTries to cap runaway executions
n8n-nodes-base.httpRequest v4.2Outbound API calls; configure per-request timeouts separately from Nginx
n8n-nodes-base.webhook v2Receive external payloads; requires correct WEBHOOK_URL env var
n8n-nodes-base.code v2JavaScript transforms; the jsCode parameter replaces the old Function node
@n8n/n8n-nodes-langchain.openAi v2.1AI generation; output path is $json.output[0].content[0].text
n8n-nodes-base.postgresDirect reads and writes to the same Postgres container

Don't copy Code node snippets from 2022 or earlier tutorials. The old Function node used a different runtime and different item iteration. Syntax that worked in pre-1.0 n8n silently fails in n8n-nodes-base.code v2 in ways that aren't obvious from the execution log. That's a common breakage when people migrate older workflows onto a fresh Docker instance.

Getting Your First Workflow Running

  1. Clone or create your docker-compose.yml with the three services above.
  2. Create a .env file with the eight required variables. Don't commit it.
  3. Run docker compose up -d and check that all three services are healthy: docker compose ps.
  4. Confirm Postgres is accepting connections: docker compose exec postgres psql -U n8n -c '\l'.
  5. Access n8n at your domain, create the first user account, and add your API credentials.
  6. Browse n8n workflow templates and pick one that solves an immediate problem.
  7. Import it, replace the YOUR_* credential placeholders, and run a test execution.

The Content Distributor template is a good first workflow to validate the full stack — it uses a Schedule trigger (tests scheduleTrigger), an OpenAI node (tests credential handling), and HTTP Request nodes (tests outbound network). If all three work, the instance is solid.

Browse workflow templates ready to deploy on your self-hosted n8n

Staying Current Without Surprises

Updates are two commands:

docker compose pull n8n
docker compose up -d --force-recreate n8n

n8n runs database migrations on startup automatically. You don't need manual SQL. But take a pg_dump before any major version bump. Minor bumps (1.68 → 1.69) are generally safe. Major bumps aren't.

For anything handling business-critical workflows, pin the image to a minor version (n8nio/n8n:1.68.1) rather than latest. Unpinned images update silently on docker compose pull and can introduce breaking changes in node output paths or execution behavior without a visible changelog entry in your terminal.

The n8n self-hosting setup guide covers n8n's queue mode architecture and worker configuration in more depth. If you're building webhook-triggered workflows once the instance is running, the n8n webhook automation guide covers every trigger pattern n8n supports.

See templates that work on any self-hosted n8n instance

The hard part isn't running n8n in Docker. It's the configuration gaps that only surface under production conditions: encryption keys generated once and stored, volume mounts confirmed before they're needed, execution pruning turned on before the database grows past manageable size. Get those right on day one and the instance runs without touching it for months.

FAQ

Common questions

What database should I use for n8n in Docker?
PostgreSQL is the recommended choice for any production n8n deployment. SQLite works fine for local testing but has file-locking issues under concurrent executions and doesn't support n8n's queue mode. Use the official postgres:15 image alongside n8n in a Docker Compose stack.
Can I run n8n in Docker without a domain name?
Yes. n8n runs on port 5678 by default and is accessible at http://localhost:5678 or via the host's IP address for internal use. A domain and SSL certificate are only required when you need webhook triggers from external services like Stripe or GitHub.
How do I update n8n in Docker?
Pull the latest image with docker pull n8nio/n8n, then recreate the container with docker compose up -d --force-recreate. n8n runs database migrations automatically on startup, so schema changes apply without manual SQL. Always back up your PostgreSQL database before pulling a major version bump.
Stop reading. Start running.

Get the workflow templates this guide is built on

Import-ready n8n JSON, step-by-step setup, and tested end-to-end. One-time payment, own it forever.