# n8n with PostgreSQL and an external task runner, loopback only, behind Cloudflare Access. # # Kit template for guide doc 2 (build step "n8n: where it fits"). Based on the official n8n-hosting # "withPostgres" compose file and the n8n docs as of October 2026 (n8n 2.43.6 stable). # # Secrets in ./.env (chmod 600), created on the server by you: # POSTGRES_PASSWORD= # N8N_ENCRYPTION_KEY= encrypts stored credentials; back it up with the database. # Without it a restored database cannot decrypt its credentials. # RUNNERS_AUTH_TOKEN= shared by n8n and its task runner # # Licence: n8n uses the Sustainable Use License. Internal business use and personal use are allowed; # hosting n8n for clients to build their own workflows is not. Read docs.n8n.io/n8n-community-license. name: n8n services: postgres: image: postgres:18 restart: unless-stopped environment: POSTGRES_USER: n8n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env} POSTGRES_DB: n8n PGDATA: /var/lib/postgresql/data # keep: without it Postgres 18 writes outside the volume volumes: - n8n_db:/var/lib/postgresql/data networks: [n8n] healthcheck: test: ["CMD-SHELL", "pg_isready -h localhost -U n8n -d n8n"] interval: 5s timeout: 5s retries: 10 n8n: image: docker.n8n.io/n8nio/n8n:2.43.6 restart: unless-stopped environment: DB_TYPE: postgresdb DB_POSTGRESDB_HOST: postgres DB_POSTGRESDB_PORT: "5432" DB_POSTGRESDB_DATABASE: n8n DB_POSTGRESDB_USER: n8n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set in .env} N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set in .env} N8N_HOST: n8n.example.com N8N_PROTOCOL: https N8N_WEBHOOK_URL: https://n8n.example.com/ # public base for webhooks and form links N8N_PROXY_HOPS: "1" # one proxy in front (cloudflared) GENERIC_TIMEZONE: UTC TZ: UTC N8N_PUBLIC_API_DISABLED: "true" # turn on only if you use the API N8N_DIAGNOSTICS_ENABLED: "false" N8N_VERSION_NOTIFICATIONS_ENABLED: "false" N8N_RUNNERS_MODE: external # Code nodes run in the separate runner container N8N_RUNNERS_AUTH_TOKEN: ${RUNNERS_AUTH_TOKEN:?set in .env} N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0 NODES_EXCLUDE: '["n8n-nodes-base.executeCommand","n8n-nodes-base.localFileTrigger","n8n-nodes-base.readWriteFile"]' EXECUTIONS_DATA_PRUNE: "true" EXECUTIONS_DATA_MAX_AGE: "168" # hours of execution history to keep volumes: - n8n_data:/home/node/.n8n # keep even with Postgres: it holds the key file and logs ports: - "127.0.0.1:5678:5678" networks: [n8n, ai] mem_limit: 4g cpus: 4 security_opt: - no-new-privileges:true depends_on: postgres: { condition: service_healthy } n8n-runner: image: n8nio/runners:2.43.6 # must match the n8n version restart: unless-stopped environment: N8N_RUNNERS_AUTH_TOKEN: ${RUNNERS_AUTH_TOKEN:?set in .env} N8N_RUNNERS_TASK_BROKER_URI: http://n8n:5679 networks: [n8n] depends_on: [n8n] volumes: n8n_db: {} n8n_data: {} networks: n8n: {} ai: # The network LiteLLM and Qdrant are on, so workflows reach them by name (http://litellm:4000). # With the kit's core compose file this is "ai-core_app". external: true name: ai-core_app