Self-hosted n8n Docker Compose, Postgres, VPS, and NAS Production Deployment Guide - XBSTACK

Self-Hosted n8n Deployment Guide: Docker Compose, Postgres, VPS, and NAS Production Baseline

Release Date
2026-05-28
Reading Time
7分钟
Content Size
8,893 chars
n8n
workflow-automation
self-hosted
docker
postgres
Laboratory Note

This article documents my real-world experiments in the lab. I believe that building your own digital assets with AI is the ultimate moat for developers.

Quick Answer

  • For low-frequency personal use, starting with SQLite is acceptable; however, for long-term operation, multi-user environments, or preparation for Queue Mode expansion, using Postgres directly is more stable.
  • Production deployments must pin the n8n image version and N8N_ENCRYPTION_KEY. The database and Redis should not be exposed directly to the public internet.
  • After configuring the reverse proxy, verify both WEBHOOK_URL and N8N_EDITOR_BASE_URL to ensure Webhooks, OAuth callbacks, and editor links use the correct domain.
  • Backups involve more than copying Docker volumes: you must save the database, n8n data directory, encryption key, and deployment configuration, and actually test the recovery process.
  • Upgrade to Queue Mode only when a single instance experiences persistent queuing, CPU saturation, or requires execution isolation. The main instance and Workers must share the same database, Redis, and encryption key.

Who Should Read This

  • Developers evaluating n8n / workflow-automation / self-hosted / docker for production use.
  • Indie builders who need a practical implementation path instead of another generic concept article.
  • Readers comparing architecture trade-offs, risks, tooling boundaries and next actions.

2026-07 Update Notes: This article has been refactored from a “working Docker example” to a production baseline. We removed easily outdated plan pricing and usage quotas, eliminated the latest image and database public port examples, and added version pinning, Editor URL configuration, private networking, recovery drills, and Queue Mode upgrade boundaries.

The Key Point: The Minimal Production Stack for Self-Hosted n8n

公网或内网入口
  ↓
Caddy / Nginx / Cloudflare Tunnel
  ↓
n8n 主实例
  ↓
Postgres

This minimal setup is suitable for individual developers, small teams, and AI workflows with low to medium concurrency. It excludes Redis and Worker components, aiming to first ensure that the domain name, credentials, database, and backups are correctly configured.

When you encounter persistent queuing, task execution competing for the main instance’s resources, or the need for horizontal scaling, upgrade to:

反向代理
  ↓
n8n Main ── Redis ── n8n Worker × N
      └──────── Postgres ────────┘

Don’t cram Queue Mode, multiple Workers, object storage, and complex monitoring all into a single Compose file right from the start. Deployment complexity should be triggered by actual workload, not by tutorial length.

How to Choose Between VPS, NAS, SQLite, and Postgres

ScenarioRecommended DatabasePublic AccessQueue Mode Needed?
Local trial, small number of manual workflowsSQLiteNot requiredNo
Long-term personal use, scheduled tasks, webhooksPostgresVPS reverse proxy or controlled tunnelNot yet
NAS internal deployment, no public IPPostgresCloudflare Tunnel / controlled reverse proxyNot yet
Multi-user usage, steadily growing execution volumePostgresOfficial domain + HTTPSMonitor queues and resources before deciding
Multiple Workers, horizontal scalingPostgresOfficial domain + HTTPSYes, requires Redis + Queue Mode

The n8n official documentation states that self-hosted instances default to SQLite but also support Postgres. Queue Mode officially recommends Postgres and explicitly advises against combining it with SQLite. Choosing a database isn’t about “SQLite will definitely break”; rather, it depends on your requirements for persistent operation, scalability, and recovery.

Four Things to Prepare Before Deployment

  1. A fixed domain name, e.g., n8n.example.com.
  2. An N8N_ENCRYPTION_KEY that remains unchanged across container rebuilds.
  3. Database passwords saved separately, never committed to the Git repository.
  4. A specific image version or digest, avoiding the use of latest.

Recommended directory structure:

n8n-stack/
├── compose.yml
├── .env              # 仅保存在服务器,不提交
├── .env.example      # 只保留变量名
└── backups/

.gitignore must include at least:

.env
backups/
*.sql

Docker Compose Production Baseline

The configuration below intentionally omits mapping Postgres’s 5432 port. n8n communicates with the database over a private Docker network; externally, only n8n behind the reverse proxy needs to be accessible.

services:
  postgres:
    image: ${POSTGRES_IMAGE:?pin POSTGRES_IMAGE}
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-n8n}
      POSTGRES_USER: ${POSTGRES_USER:-n8n}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - backend

  n8n:
    image: ${N8N_IMAGE:?pin N8N_IMAGE}
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}
      DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}

      N8N_HOST: ${N8N_HOST:?set N8N_HOST}
      N8N_PROTOCOL: https
      N8N_PORT: 5678
      N8N_EDITOR_BASE_URL: https://${N8N_HOST}/
      WEBHOOK_URL: https://${N8N_HOST}/
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY}

      EXECUTIONS_DATA_PRUNE: "true"
      EXECUTIONS_DATA_MAX_AGE: ${EXECUTIONS_DATA_MAX_AGE:-168}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-Asia/Shanghai}
      TZ: ${GENERIC_TIMEZONE:-Asia/Shanghai}
    volumes:
      - n8n_data:/home/node/.n8n
    expose:
      - "5678"
    networks:
      - frontend
      - backend

networks:
  frontend:
  backend:
    internal: true

volumes:
  postgres_data:
  n8n_data:

Why Pin Images

Pin the N8N_IMAGE and POSTGRES_IMAGE images from .env to verified versions or digests. When upgrading, update the test environment first, verify that database migrations and critical workflows function correctly, and then proceed to update the production environment.

错误:N8N_IMAGE=docker.n8n.io/n8nio/n8n:latest
正确思路:N8N_IMAGE=经过测试并锁定的版本或 digest

This article avoids hardcoding a specific current version as the “always recommended version,” since version numbers change. On deployment day, verify the target version against the n8n Release Notes and official upgrade documentation.

Three Environment Variables That Determine Deployment Success

N8N_ENCRYPTION_KEY

n8n uses this to encrypt credentials in the database. The official documentation states that a random key is automatically generated on first startup, but you can also provide a custom key. In production, store your custom value in a password manager or a controlled secret store.

It must satisfy:

  • Remain unchanged after container rebuilds.
  • Be migrated along with the server during migration.
  • Be identical across the main instance, Workers, and Webhook Processor in Queue Mode.
  • Perform a full database backup and review the official rotation instructions before rotating the key.

Never write the actual value into Compose files, articles, screenshots, or Git repositories.

WEBHOOK_URL

This determines the base URL for production webhooks as seen by external systems. Services like GitHub, Stripe, and Slack must be able to reach this HTTPS address from the public internet.

After deployment, verify:

编辑器生成的生产 Webhook 是否以正式域名开头
测试路径 /webhook-test/ 与生产路径 /webhook/ 是否被混用
工作流是否已启用
反向代理是否保留 Host 与 HTTPS 协议信息

N8N_EDITOR_BASE_URL

The official documentation defines this as the public URL used to access the editor. It is also used for emails sent by n8n and SAML redirect addresses. Even if webhooks are functioning correctly, a mismatched Editor URL can cause login emails, OAuth flows, or admin links to point to internal network addresses.

Reverse Proxy: Expose Only n8n, Not the Database

Using Caddy as an example:

n8n.example.com {
    reverse_proxy n8n:5678
}

Actual deployment also requires checking:

  • Whether DNS points to the correct entry point.
  • Whether HTTPS certificates are renewing normally.
  • Whether upload size limits and proxy timeouts accommodate long-running tasks.
  • Whether WebSocket, SSE, or streaming responses are being interrupted.
  • Whether the admin entry point requires additional access controls.

Postgres and Redis should only be reachable on a private network. Do not expose 5432 or 6379 directly to the public internet just for “convenient remote connections”; use SSH tunnels, VPNs, or controlled jump hosts when maintenance is required.

NAS Deployment: The focus is on the entry point and permissions, not the Docker interface

The differences between NAS and VPS deployments mainly lie in three areas.

1. No public IPv4

Use Cloudflare Tunnel or another entry point with TLS and access control. Do not expose the NAS management panel, Docker Socket, or database ports simultaneously.

2. Mount directory permissions

First, confirm whether the user inside the container can read and write to the persistent directories. Do not treat chmod 777 as a long-term solution; it is more robust to explicitly set the directory owner, UID/GID, and minimum permissions.

3. Limited resources

AI nodes, large file parsing, Code nodes, and concurrent Webhooks can all consume significant memory. First, establish observability before deciding whether to limit container resources. When frequent OOM restarts occur, reduce concurrency, split large files, or migrate to a more suitable host rather than simply increasing the restart limit.

Backups: Preserve at least four categories of assets

Backing up only n8n_data is insufficient, nor is relying solely on pg_dump.

You need to save:

  1. Postgres databases.
  2. n8n data volumes.
  3. Secure copies of N8N_ENCRYPTION_KEY and other secrets.
  4. Compose files, reverse proxy configurations, and version settings.

Database backup example:

docker compose exec -T postgres \
  pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
  > "backups/n8n-$(date +%F).sql"

Before restoring, stop the n8n instance that writes to the database, then import the SQL dump into an empty database. After restoration is complete, do not immediately declare success. At a minimum, verify:

  • Whether you can log in.
  • Whether existing Credentials can be decrypted.
  • Whether three critical workflows can be executed manually.
  • Whether Webhooks return expected results.
  • Whether scheduled tasks and time zones are correct.

Backups without a recovery drill are merely “potentially existing files.”

Upgrading: Backup First, Check Migrations, Then Regression Test Critical Workflows

Recommended sequence:

  1. Record the current image version and database backup timestamp.
  2. Export critical workflows or record their IDs.
  3. Start a test instance using the target version.
  4. Review Release Notes and Breaking Changes.
  5. Regression test Webhooks, OAuth, Credentials, Code nodes, and AI nodes.
  6. Update the production image.
  7. Keep references to the rollbackable old image and database backups.

Do not assume that automatic database migration means “upgrades are always risk-free.” Automatic migrations handle schema changes; they do not verify third-party nodes, expressions, OAuth flows, or business outputs.

When to Upgrade to Queue Mode

Watch for these signals first:

  • Execution wait times are continuously increasing.
  • The main instance’s CPU or memory is saturated over the long term.
  • A large task slows down the editor and other workflows.
  • You need Workers to run on separate machines.
  • You need rolling scaling or clearer fault isolation.

Key constraints of Queue Mode:

  • EXECUTIONS_MODE=queue.
  • Redis handles queue notifications.
  • Workers execute actual tasks.
  • The main instance and Workers access the same Postgres database.
  • All instances use the same N8N_ENCRYPTION_KEY.
  • The official recommendation is Postgres 13+; SQLite is not recommended for Queue Mode.

For full configuration details, continue reading: n8n Queue Mode, Redis, and Worker in Practice. The basic deployment guide only outlines upgrade boundaries and does not maintain two separate Compose files.

Pre-Launch Acceptance Checklist

[ ] n8n 和 Postgres 镜像已固定版本或 digest
[ ] .env 未提交到 Git
[ ] N8N_ENCRYPTION_KEY 已安全备份
[ ] Postgres 未映射公网端口
[ ] WEBHOOK_URL 使用正式 HTTPS 域名
[ ] N8N_EDITOR_BASE_URL 使用正式 HTTPS 域名
[ ] 反向代理能处理长请求和流式连接
[ ] EXECUTIONS_DATA_PRUNE 已按业务留存要求配置
[ ] 数据库备份已成功生成
[ ] 已在隔离环境完成一次恢复演练
[ ] 关键 Webhook、OAuth 和定时工作流已回归

Common Issues

Webhook Returns 404

First, distinguish between the following:

  • The test URL /webhook-test/ is only valid during the test listening period.
  • The production URL /webhook/ requires the workflow to be enabled.
  • If there is a domain or protocol error, check WEBHOOK_URL and proxy headers.
  • If the route is correct but the upstream is inaccessible, check DNS, TLS, firewalls, and tunnels.

Credentials Cannot Be Decrypted

The most common cause is that N8N_ENCRYPTION_KEY has changed or been lost. Do not delete the Credential record in the database as your first response; instead, recover the original key and verify that all instances are consistent.

Database Connection Failed

Check:

DB_TYPE
DB_POSTGRESDB_HOST
DB_POSTGRESDB_PORT
DB_POSTGRESDB_DATABASE
DB_POSTGRESDB_USER
数据库用户权限
Docker network
Postgres healthcheck

Worker Connection to Redis Timed Out

Ensure the main instance and the Worker use the same Queue configuration, that the Redis address is reachable within the container network, and that you haven’t mistakenly treated localhost as another container.

Official Documentation

Continue Reading

Topic path / AI workflows

Continue through the production automation path

The workflow hub connects self-hosting, queue mode, webhooks, retries, observability and n8n implementation cases into one production-oriented learning path.

Next Reading

View Hub →
workflow

n8n Webhook Production Deployment: Header Auth, Raw Body, WEBHOOK_URL, and Reverse Proxy Troubleshooting

Systematically deconstructs the critical configuration for self-hosted n8n Webhooks from testing to production deployment, covering Test URL vs. Production URL, Header Auth, JWT, Raw Body, Respond to Webhook, WEBHOOK_URL, N8N_PROXY_HOPS, reverse proxy, signature verification, idempotency deduplication, and security troubleshooting.

workflow

n8n Queue Mode + Redis in Practice: When to Offload Workflows to a Queue

A hands-on guide to deploying n8n Queue Mode, Redis, and Workers in production. Covers when to switch from regular mode to queue mode, how to split the main instance, workers, webhooks, Redis, and database, plus strategies for handling high concurrency, long-running tasks, webhook callbacks, and execution timeouts in AI workflows.

workflow

n8n Gmail Email Summary Automation: AI Extracts Action Items and Writes to Google Sheets

Build a Gmail email summarization workflow in n8n: use the Gmail Trigger to search and filter emails, invoke an AI agent to extract summaries, priorities, and action items, then deduplicate by Message ID before writing to Google Sheets.

workflow

n8n AI Workflow in Action: Slack Daily Digest Bot

A hands-on guide to building a daily briefing AI agent using self-hosted n8n, OpenAI, and the Slack API. Covers batch retrieval of Slack messages, filtering for short messages and system notifications, multi-thread context correlation, precise decision extraction by large language models, and automated push delivery.

Xiaobai

Xiaobai

Full-Stack AI Engineer

Xiaobai, a full-stack AI engineer building production Agent systems, product tools and independent software assets.

About Xiaobai & XBSTACK →

Liked this article?
Join the newsletter

Every issue condenses production AI engineering changes, real failures, reproducible experiments, useful tools and new XBSTACK assets. No generic news digest and no filler.

Comments