This tutorial guides you through self-hosting n8n using Docker Compose, covering setup, configuration, and maintenance for a automation server. It's for technical users familiar with n8n basics who need precise steps to deploy it on their own infrastructure.
Why this matters
Self-hosting n8n with Docker Compose avoids vendor lock-in and data privacy risks associated with cloud services, while enabling full customisation and scalability on your hardware. It sidesteps common traps like data loss from non-persistent storage or security exposures from default configurations, ensuring your automations run reliably without unexpected downtime or breaches.
Step-by-step
- Create a new directory for your n8n setup and navigate into it:
mkdir n8n-selfhost && cd n8n-selfhost. This organises your files and keeps the project isolated. - Generate a secure encryption key for n8n's credentials:
openssl rand -base64 32and note the output (e.g.,your-encryption-key-here). You'll use this to protect sensitive data in workflows. - Create a
.envfile to store environment variables:touch.env, then edit it with a text editor (e.g.,nano.env) and add variables likeN8N_ENCRYPTION_KEY=your-encryption-key-here,N8N_HOST=localhost,N8N_PORT=5678,N8N_PROTOCOL=http, andWEBHOOK_URL=http://localhost:5678/. For security, setN8N_BASIC_AUTH_ACTIVE=true,N8N_BASIC_AUTH_USER=admin, andN8N_BASIC_AUTH_PASSWORD=strongpassword123. - Draft the
docker-compose.ymlfile:touch docker-compose.ymland edit it to include the following configuration:
This sets up persistent storage via theversion: '3.8' services: n8n: image: docker.n8n.io/n8nio/n8n:latest restart: always ports: - '5678:5678' environment: - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} - N8N_HOST=${N8N_HOST} - N8N_PORT=${N8N_PORT} - N8N_PROTOCOL=${N8N_PROTOCOL} - WEBHOOK_URL=${WEBHOOK_URL} - N8N_BASIC_AUTH_ACTIVE=${N8N_BASIC_AUTH_ACTIVE} - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER} - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD} volumes: - n8n_data:/home/node/.n8n - /var/run/docker.sock:/var/run/docker.sock # Optional for Docker nodes networks: - n8n-network volumes: n8n_data: networks: n8n-network: driver: bridgen8n_datavolume and exposes port 5678. - Start the containers: Run
docker compose up -din the directory. Expect output confirming the service is starting; check logs withdocker compose logs -fto verify n8n is running without errors. - Access n8n in your browser at
http://localhost:5678(or your server's IP). Log in with the basic auth credentials from the.envfile. You'll see the n8n dashboard ready for workflows. - For scaling, add a worker service to the
docker-compose.ymlunder services:
Then restart withworker: image: docker.n8n.io/n8nio/n8n:latest command: worker environment: - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} volumes: - n8n_data:/home/node/.n8n depends_on: - n8n networks: - n8n-networkdocker compose up -d. This distributes execution load, but monitor resource usage as workers consume additional CPU and memory. - To upgrade, pull the latest image:
docker compose pull, thendocker compose up -d. Back up then8n_datavolume first withdocker volume lsand tools likedocker cpto avoid data loss during updates. - Harden security by editing
.envto includeN8N_SECURE_COOKIE=falsefor HTTP (switch to true with HTTPS), and use a reverse proxy like Traefik or Nginx for SSL. Restrict port access via firewall rules, e.g.,ufw allow from trusted-ip to any port 5678on Ubuntu.
Worked example
Consider a common pattern: automating daily email reports from a database. Start a new workflow in n8n and add a Cron node set to trigger daily at 9 AM (expression: 0 9 * * *). Connect it to a Postgres node configured with your database credentials (host, port, database, user, password) and a query like SELECT * FROM sales WHERE date = CURRENT_DATE to fetch today's data.
Next, add an IF node to check if rows exceed 10 (condition: {{ $json.length }} > 10); if true, route to a Google Sheets node to append the data (operation: Append, sheet ID from your Google auth). Finally, connect to an Email Send node (SMTP credentials set in n8n) with subject Daily Sales Report and body using expressions like {{ $json }} to include the data summary.
Activate the workflow and test via the Execute Workflow button. On schedule, it pulls data, logs it to Sheets if significant, and emails a report— all running securely on your self-hosted instance without external dependencies.
Common pitfalls
- Symptom: Workflows lose data or credentials after restart. Fix: Ensure the
n8n_datavolume is mounted correctly indocker-compose.yml; verify withdocker volume inspect n8n_dataand avoid overriding the/home/node/.n8npath. - Symptom: Webhooks fail with 404 errors. Fix: Set
WEBHOOK_URLin.envto match your external accessible URL (e.g., via domain); test by pinging the endpoint from outside the container. - Symptom: High load causes timeouts during executions. Fix: Scale by adding worker services as shown, but limit concurrent executions with
N8N_EXECUTIONS_TIMEOUT=300in.envand monitor viadocker stats. - Symptom: Basic auth doesn't prompt on access. Fix: Confirm
N8N_BASIC_AUTH_ACTIVE=trueand restart the container; if using a proxy, ensure it passes auth headers without stripping them. - Symptom: Upgrades break custom nodes. Fix: Install community nodes via the n8n UI or by mounting a custom volume for
/home/node/.n8n/custom, then re-pull images carefully to preserve them.
Related workflows in the catalog
Explore the AutomationFlows catalog for importable templates like "Database Backup to Cloud Storage," which uses Postgres and AWS S3 nodes for automated backups—ideal for extending your self-hosted setup. Another relevant one is "Slack Notifications from Email," integrating IMAP Email and Slack nodes to streamline alerts. With over 14,000+ workflows available, search for "self-hosted" or "Docker" tags to find more tailored to containerised environments.