Skip to Content

Production Guide: Deploy n8n with Docker Compose + Traefik + PostgreSQL on Ubuntu

Build a resilient self-hosted automation stack with secure ingress, backups, and practical production operations.

Running business automations on cloud-only SaaS can become expensive, hard to audit, and difficult to customize for internal security controls. Teams that process customer data, financial events, or regulated workflows often need predictable hosting, encrypted traffic, and repeatable backup routines they can verify at any time. This guide shows a production-oriented way to deploy n8n on Ubuntu using Docker Compose, Traefik, and PostgreSQL so you can operate automation pipelines with clear ownership and lower platform risk.

The approach follows a practical pattern that works for real operations: dedicated service directories, explicit secrets, controlled image updates, TLS termination at the edge, health checks, and restore-tested backups. Instead of only showing a quick demo stack, we focus on what you need when workflows become business critical: stability during restarts, straightforward monitoring hooks, and troubleshooting paths for the failures teams actually see in production.

By the end of this deployment, you will have a publicly reachable n8n instance behind Traefik with persistent storage, PostgreSQL for durable state, and an operational checklist you can hand to another engineer. You can start small on a single VM and later evolve to a higher-availability topology while keeping the same runtime conventions.

Architecture and flow overview

Traffic enters through Traefik on ports 80/443. Traefik handles certificate lifecycle and forwards requests to the n8n web container on the internal Docker network. n8n stores execution metadata and workflow state in PostgreSQL. Persistent volumes retain database and application data across container restarts and host reboots. A scheduled backup task archives PostgreSQL dumps and n8n data directories to a timestamped retention folder.

  • Ingress layer: Traefik with HTTPS routing and optional middleware hardening.
  • Application layer: n8n container with explicit host/protocol settings.
  • Data layer: PostgreSQL with strong credentials and volume persistence.
  • Operations: backup script + restore test + deterministic upgrade path.

Prerequisites

  • Ubuntu 22.04/24.04 server with at least 2 vCPU, 4 GB RAM, and 40+ GB disk.
  • A DNS record (for example n8n.example.com) pointing to your server IP.
  • Docker Engine and Docker Compose plugin installed.
  • Open inbound ports 80 and 443 on firewall/security group.
  • A non-root sudo user and a plan for off-host backups.

Step-by-step deployment

1) Prepare host user and directory structure

Create a dedicated runtime path so configuration, compose files, and backups remain organized. Keeping a consistent layout makes incident response faster and avoids ad-hoc edits scattered across the filesystem.

sudo useradd --system --create-home --home-dir /opt/n8n --shell /usr/sbin/nologin n8nsvc
sudo mkdir -p /opt/n8n/{traefik,database,data,backups,compose}
sudo chown -R $USER:$USER /opt/n8n
cd /opt/n8n

If copy is unavailable in your browser, select the block and copy manually.

2) Create environment file with strong secrets

Store sensitive values in an environment file with restrictive permissions. Generate unique credentials and never commit this file to source control. For team operations, place secret ownership under your vault process and rotate on schedule.

cat > /opt/n8n/.env << 'EOF'
DOMAIN=n8n.example.com
TZ=UTC
POSTGRES_DB=n8n
POSTGRES_USER=n8n
POSTGRES_PASSWORD=$(openssl rand -base64 36)
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=$(openssl rand -base64 24)
EOF
chmod 600 /opt/n8n/.env

If copy is unavailable in your browser, select the block and copy manually.

3) Define Docker Compose stack

This compose file keeps Traefik, PostgreSQL, and n8n on a shared bridge network. Labels define HTTPS routing and certificate resolver behavior. Health checks provide quick signals for readiness and reduce blind restart loops.

cat > /opt/n8n/compose/docker-compose.yml << 'EOF'
services:
  traefik:
    image: traefik:v3.1
    command:
      - --api.dashboard=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - [email protected]
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /opt/n8n/traefik:/letsencrypt
    restart: unless-stopped

  postgres:
    image: postgres:16
    env_file: /opt/n8n/.env
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - /opt/n8n/database:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped

  n8n:
    image: n8nio/n8n:latest
    env_file: /opt/n8n/.env
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_HOST=${DOMAIN}
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://${DOMAIN}
      - TZ=${TZ}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_BASIC_AUTH_ACTIVE=${N8N_BASIC_AUTH_ACTIVE}
      - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)
      - traefik.http.routers.n8n.entrypoints=websecure
      - traefik.http.routers.n8n.tls.certresolver=le
      - traefik.http.services.n8n.loadbalancer.server.port=5678
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - /opt/n8n/data:/home/node/.n8n
    restart: unless-stopped

networks:
  default:
    name: n8n_net
EOF

If copy is unavailable in your browser, select the block and copy manually.

4) Boot stack and validate initial health

Bring services up in detached mode, then verify container status and logs. First boot can take a little longer while certificates are requested and database migrations run.

cd /opt/n8n/compose
docker compose --env-file /opt/n8n/.env up -d
docker compose ps
docker compose logs --tail=80 n8n

If copy is unavailable in your browser, select the block and copy manually.

5) Harden service startup with systemd

Systemd integration gives explicit lifecycle control and clean restarts during host maintenance. This is especially useful when your platform team standardizes on service units for production applications.

sudo tee /etc/systemd/system/n8n-compose.service > /dev/null << 'EOF'
[Unit]
Description=n8n Docker Compose Stack
After=docker.service network-online.target
Requires=docker.service

[Service]
Type=oneshot
WorkingDirectory=/opt/n8n/compose
ExecStart=/usr/bin/docker compose --env-file /opt/n8n/.env up -d
ExecStop=/usr/bin/docker compose --env-file /opt/n8n/.env down
RemainAfterExit=yes
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now n8n-compose.service

If copy is unavailable in your browser, select the block and copy manually.

6) Add backup and retention automation

A deployment is only production-ready when recovery is tested. This backup script captures PostgreSQL dumps and n8n config/data snapshots. Tune retention to your compliance and recovery objectives.

cat > /opt/n8n/backups/backup.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
source /opt/n8n/.env
TS=$(date +%Y%m%d-%H%M%S)
DEST=/opt/n8n/backups/$TS
mkdir -p "$DEST"

# PostgreSQL dump
docker exec $(docker ps --filter name=postgres --format '{{.ID}}')   pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" > "$DEST/postgres.sql"

# n8n data archive
tar -czf "$DEST/n8n-data.tar.gz" -C /opt/n8n data

# retention: keep last 14
ls -1dt /opt/n8n/backups/* | tail -n +15 | xargs -r rm -rf
EOF
chmod +x /opt/n8n/backups/backup.sh
( crontab -l 2>/dev/null; echo "25 2 * * * /opt/n8n/backups/backup.sh" ) | crontab -

If copy is unavailable in your browser, select the block and copy manually.

Configuration and secrets handling best practices

Use independent credentials for database, basic auth, and n8n encryption so a single leak does not compromise all layers. Keep your .env file outside repositories and limit read permissions to deployment operators only. When rotating secrets, update values in one maintenance window and perform a controlled restart.

For external integrations, prefer credential objects inside n8n rather than hardcoding API tokens in workflow nodes. Pair this with scoped API keys from upstream systems and document ownership for each secret. If your environment supports a centralized secret manager, map secrets into containers at runtime and remove static secrets from disk.

For compliance-sensitive environments, add immutable logs for administrative actions and enforce MFA in your identity provider. Even when n8n is private, auditability and principle-of-least-privilege are essential to prevent silent drift.

Verification checklist

  • DNS resolves the chosen hostname to your Ubuntu server.
  • HTTPS loads with a valid certificate and no mixed-content errors.
  • n8n login is reachable and basic auth is enforced (if enabled).
  • Database health check remains healthy after restart.
  • A sample workflow executes and writes data correctly.
  • Backup script runs, archives are created, and test restore succeeds.
curl -I https://n8n.example.com
docker compose -f /opt/n8n/compose/docker-compose.yml ps
docker compose -f /opt/n8n/compose/docker-compose.yml logs --tail=100 postgres
ls -lah /opt/n8n/backups | head

If copy is unavailable in your browser, select the block and copy manually.

Common issues and fixes

Traefik returns 404 for the n8n host

Verify the domain in DOMAIN exactly matches the request host. Confirm Traefik labels are applied and that port 5678 is exposed internally. Also check whether another router is catching the same host rule.

Certificate issuance fails

Ensure ports 80 and 443 are publicly reachable and not blocked by cloud firewalls. If your server is behind another reverse proxy, validate forwarding rules and avoid duplicate ACME challengers on the same domain.

n8n starts but workflows fail to save

This usually points to PostgreSQL connectivity or permission issues. Re-check database credentials in .env, then inspect PostgreSQL logs for authentication failures and schema errors.

Webhook URLs point to localhost

Set N8N_HOST, N8N_PROTOCOL, and WEBHOOK_URL explicitly, then restart n8n. Old values can persist from prior runs if environment files are inconsistent.

Backups exist but restore fails under pressure

Run a scheduled restore drill on a staging VM. Validate that SQL dumps load cleanly and that n8n data archives restore expected credentials and workflow metadata.

FAQ

Can I run n8n without PostgreSQL in production?

You can start with SQLite for quick testing, but PostgreSQL is the better production path for reliability, backup tooling, and concurrent workload stability.

How much CPU and memory should I reserve?

For small-to-mid workloads, 2 vCPU and 4 GB RAM is often sufficient. Increase resources when parallel executions and heavy integrations rise.

Should I pin image versions instead of using latest?

Yes. Pin versions for controlled upgrades, especially in production. Move to a newer version only after validating migration behavior in staging.

How do I rotate encryption and auth secrets safely?

Rotate one layer at a time during a maintenance window, snapshot first, and verify workflow execution immediately after restart.

What is the safest way to expose n8n to the internet?

Use HTTPS with a maintained reverse proxy, enforce strong authentication, and restrict admin access by IP or VPN when possible.

Can I scale this architecture later?

Yes. Keep state in PostgreSQL and persistent volumes now, then migrate ingress and app layers to orchestrated environments when your traffic or reliability targets grow.

Related internal guides

Talk to us

If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.

Contact Us

Production Guide: Deploy Outline with Docker Compose + Nginx + PostgreSQL on Ubuntu
A production-focused Outline deployment with secure networking, secret handling, backups, verification, and incident-ready troubleshooting.