Skip to content

AI

How to self-host n8n with Docker Compose and PostgreSQL

A working Docker Compose setup for self-hosted n8n with PostgreSQL, HTTPS behind a reverse proxy, the environment variables that matter, and how to back it up.

By · Published · 3 min read

Short answer: run n8n and PostgreSQL as two services in Docker Compose, set a fixed encryption key, point WEBHOOK_URL at your public HTTPS address, put a reverse proxy in front, and back up both the database and the encryption key. SQLite is fine for a trial. Use PostgreSQL for anything you rely on.

What do you need first?

  • A Linux server or VPS with Docker and the Compose plugin.
  • A domain name pointing at it, for example n8n.example.com.
  • A reverse proxy that handles TLS. Caddy is the least work.

What does the Compose file look like?

services:
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: n8n
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n"]
      interval: 5s
      retries: 10

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    depends_on:
      db: { condition: service_healthy }
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: db
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}
      DB_POSTGRESDB_DATABASE: n8n
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_HOST: n8n.example.com
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://n8n.example.com/
      GENERIC_TIMEZONE: Asia/Kolkata
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  db_data:
  n8n_data:

Put the secrets in a .env file next to it. Generate the key once with openssl rand -hex 32 and never change it.

DB_PASSWORD=change-this-long-random-value
N8N_ENCRYPTION_KEY=paste-the-64-hex-characters-here

Why does the encryption key matter so much?

n8n encrypts the credentials you save, such as API keys and OAuth tokens, with that key. If you lose it, or if n8n generates a new one after you rebuild the container, every saved credential becomes unreadable and you must re-enter them all. Setting it explicitly and storing a copy outside the server avoids that.

How do you add HTTPS?

The Compose file binds n8n to 127.0.0.1 so it is not reachable directly from the internet. Caddy in front needs three lines.

n8n.example.com {
    reverse_proxy 127.0.0.1:5678
}

Caddy obtains and renews the certificate on its own. Webhook triggers and OAuth callbacks need the correct public URL, which is what WEBHOOK_URL is for. If your webhook URLs show localhost in the editor, that variable is missing or wrong.

How do you start and update it?

docker compose up -d
docker compose logs -f n8n

# update
docker compose pull n8n
docker compose up -d

Pin a specific version tag instead of latest once you have workflows you depend on, and read the release notes before bumping it.

How do you back it up?

  • Database: docker compose exec db pg_dump -U n8n n8n | gzip > n8n-$(date +%F).sql.gz on a schedule, copied off the machine.
  • Encryption key: stored in a password manager.
  • Workflows: you can also export them as JSON or keep them in Git. A restore is only proven when you have done one. The same reasoning is in my restore drill post.

What should you secure?

  • Create the owner account immediately after first start. Until you do, anyone who reaches the URL can.
  • Do not expose the port directly. Keep it behind the proxy.
  • Use strong credentials and consider restricting the editor to your IP or a VPN, while leaving only the webhook paths public.
  • Remember that a workflow can run code and call any URL from your server. Treat editor access as server access.

More general hardening is in the self-hosting security guide.

When should you move to queue mode?

The single-process setup handles light and moderate use. If you run many concurrent executions or long jobs, n8n supports queue mode with Redis and separate worker processes. Do not start there. Move when executions start waiting.

References

Author

Raktim Ranjit is a software engineer and the founder of NodeDR Infotech. He builds and maintains the software described here.

Have something in mind?

Let’s build something useful.

Tell me about the idea, product, or workflow you’re working through.

Tap to say hello