AutomationFlowsTutorials › Self-host n8n with Docker Compose

Self-host n8n with Docker Compose

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

  1. 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.
  2. Generate a secure encryption key for n8n's credentials: openssl rand -base64 32 and note the output (e.g., your-encryption-key-here). You'll use this to protect sensitive data in workflows.
  3. Create a .env file to store environment variables: touch.env, then edit it with a text editor (e.g., nano.env) and add variables like N8N_ENCRYPTION_KEY=your-encryption-key-here, N8N_HOST=localhost, N8N_PORT=5678, N8N_PROTOCOL=http, and WEBHOOK_URL=http://localhost:5678/. For security, set N8N_BASIC_AUTH_ACTIVE=true, N8N_BASIC_AUTH_USER=admin, and N8N_BASIC_AUTH_PASSWORD=strongpassword123.
  4. Draft the docker-compose.yml file: touch docker-compose.yml and edit it to include the following configuration:
    version: '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: bridge
    This sets up persistent storage via the n8n_data volume and exposes port 5678.
  5. Start the containers: Run docker compose up -d in the directory. Expect output confirming the service is starting; check logs with docker compose logs -f to verify n8n is running without errors.
  6. Access n8n in your browser at http://localhost:5678 (or your server's IP). Log in with the basic auth credentials from the .env file. You'll see the n8n dashboard ready for workflows.
  7. For scaling, add a worker service to the docker-compose.yml under services:
     worker:
     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-network
    Then restart with docker compose up -d. This distributes execution load, but monitor resource usage as workers consume additional CPU and memory.
  8. To upgrade, pull the latest image: docker compose pull, then docker compose up -d. Back up the n8n_data volume first with docker volume ls and tools like docker cp to avoid data loss during updates.
  9. Harden security by editing .env to include N8N_SECURE_COOKIE=false for 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 5678 on 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

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.