Scaling Your Self-Hosted n8n: Queue Mode, Postgres & Custom Docker Images
Most teams start their n8n self hosted journey with a simple Docker container β and hit a wall the moment workflows multiply. This guide picks up where basic setup leaves off, walking you through the architecture decisions that actually matter at scale: a Postgres-backed data layer, Redis-powered queue mode for horizontal worker scaling, and custom Docker images built for production workloads.
πΊ Prefer to watch? Full walkthrough on YouTube
What Youβll Learn
By the end of this n8n self hosting tutorial, youβll have a scalable, production-ready n8n stack with:
- Queue mode enabled with Redis for horizontal worker scaling
- Postgres as a reliable, scalable data layer replacing fragile local volumes
- A custom n8n Docker image that supports additional modules and custom nodes
- A local containerized baseline you can confidently promote to the cloud
This is Part 1 of a series. Future parts will cover taking this stack to the cloud using infrastructure-as-code tools like Pulumi and Terraform.
Why Scale Your n8n Deployment?
The default n8n docker setup β a single container with a local SQLite volume β works fine for personal use or early prototyping. But it has a hard ceiling. Every workflow execution runs in the same process as the editor, a single database file handles all your state, and thereβs no way to add capacity without downtime.
Hereβs what a properly scaled n8n self hosted setup unlocks:
- Horizontal scaling β Add worker containers on demand without touching the editor
- No execution bottlenecks β Workers process jobs in parallel instead of queuing behind the UI process
- Resilient data storage β Postgres handles concurrent reads/writes that SQLite simply canβt
- Deployment flexibility β A containerized, stateless worker model maps cleanly onto cloud infrastructure
- Custom node support β Build and ship your own nodes across a consistent image
Prerequisites
- Docker and Docker Compose installed
- Basic familiarity with the command line
- A text editor for editing config files
Step 1: Establish Your Local Baseline
Before scaling anything, you need a working local container to build on. This step is intentionally minimal β the goal isnβt to run n8n this way in production, but to confirm the image works and understand the default configuration before we layer on scalability components.
The fastest way to get started with n8n docker is to pull the official image and run it directly.
docker run -p 5678:5678 -v ./data_n8n:/home/node/.n8n n8nio/n8n:1.107.2
A few things to note here:
-p 5678:5678maps the default n8n port to your local machine-v ./data_n8n:/home/node/.n8npersists your data to a local folder- If you hit a permissions error on the volume folder, run
chmod 777 ./data_n8n
Once itβs running, navigate to http://localhost:5678 and you should see the n8n editor. Thatβs your first milestone done.
Step 2: Replace SQLite with Postgres
This is the first real scalability step. SQLite β n8nβs default database β is a single-file store that canβt handle concurrent writes from multiple workers. Before queue mode can work, you need Postgres in place as the shared data layer that both the editor and workers read from and write to simultaneously.
Weβll also move from raw docker run commands to a docker-compose.yml file, which makes managing a multi-service stack significantly easier.
docker-compose.yml (initial structure)
services:
n8n:
image: n8nio/n8n:1.107.2
container_name: dac-n8n-editor
ports:
- "5678:5678"
env_file:
- .env
volumes:
- ./data_n8n:/home/node/.n8n
postgres:
image: postgres:16
container_name: dac-postgres
ports:
- "5432:5432"
env_file:
- .env
volumes:
- ./data_postgres:/var/lib/postgresql/data
.env file
# n8n
N8N_RUNNERS_ENABLED=true
N8N_ENCRYPTION_KEY=your-random-key-here
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
# Database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n_user
DB_POSTGRESDB_PASSWORD=your-secure-password
# Postgres credentials
POSTGRES_DB=n8n
POSTGRES_USER=n8n_user
POSTGRES_PASSWORD=your-secure-password
Tip: Pay attention to any warnings that n8n logs on startup. They exist for good reason β address them one by one. The
N8N_RUNNERS_ENABLED, encryption key, and settings file permissions flags are all recommended by n8n for production setups.
Optional: Add a Makefile for convenience
up:
docker compose down && docker compose up -d
down:
docker compose down
Running make up cleanly shuts down existing containers before spinning everything back up, which prevents state conflicts during development.
Step 3: Enable Queue Mode for Horizontal Scaling
This is where n8n self hosted goes from βit worksβ to βit scales.β Queue mode separates the editor (the UI) from workers (execution engines), allowing you to run multiple workers in parallel.
How Queue Mode Works
Hereβs the execution flow once queue mode is active:
- A workflow is triggered β the editor creates an execution ID in Redis and stores the workflow data in Postgres
- Available workers pick up execution IDs from Redis and fetch the corresponding workflow data from Postgres
- Workers process the workflow, then update both Postgres (results) and Redis (status)
- The editor polls Redis for execution IDs marked as finished and updates the UI
This architecture means you can add more worker containers as your workload grows without touching the editor service.
Updated docker-compose.yml with Redis and Worker
services:
n8n-editor:
image: n8nio/n8n:1.107.2
container_name: dac-n8n-editor
ports:
- "5678:5678"
env_file:
- .env
n8n-worker:
image: n8nio/n8n:1.107.2
container_name: dac-n8n-worker
command: worker
env_file:
- .env
depends_on:
- redis
- postgres
redis:
image: redis:7
container_name: dac-redis
postgres:
image: postgres:16
container_name: dac-postgres
env_file:
- .env
volumes:
- ./data_postgres:/var/lib/postgresql/data
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
Queue mode environment variables (add to .env)
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
init-db.sql β ensure the database exists before n8n starts
One issue youβll run into: n8n and the worker will try to connect to Postgres before the database is fully initialized. Fix this with a simple SQL init script:
SELECT 'CREATE DATABASE n8n'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'n8n');
Mount this in your Postgres service as shown above. The container will execute it on first startup, guaranteeing the database is ready before your n8n services come online.
Step 4: Build a Custom n8n Docker Image
The stock n8n image is solid, but youβll eventually want to install additional npm modules or custom nodes. The clean way to do this in an n8n docker setup is to build your own image on top of the official one.
Dockerfile
FROM n8nio/n8n:1.107.2
# Label your build for easy introspection
LABEL com.myn8n.build.version="1.0.0"
LABEL com.myn8n.build.type="custom"
# Set the custom extensions path
ENV N8N_CUSTOM_EXTENSIONS="/home/node/.n8n/custom"
# Install your additional modules
RUN cd /usr/local/lib/node_modules/n8n && \
npm install global-constants
USER node
Add a build service to docker-compose.yml
services:
n8n-build:
build:
context: .
dockerfile: Dockerfile
args:
N8N_VERSION: 1.107.2
image: my-custom-n8n:1.107.2
profiles:
- build
Build your image:
docker compose --profile build build
Then update your n8n-editor and n8n-worker services to use my-custom-n8n:1.107.2 instead of the base image.
Verify your custom image
# Check your build labels
echo "Build version: $(docker inspect dac-n8n-editor --format='{{index .Config.Labels "com.myn8n.build.version"}}')"
echo "Build type: $(docker inspect dac-n8n-editor --format='{{index .Config.Labels "com.myn8n.build.type"}}')"
# Confirm installed modules
docker exec dac-n8n-editor npm list | grep n8n-nodes
If everything went correctly, your new module (e.g., global-constants) will now appear in the n8n editorβs node palette.
Full Architecture Summary
Hereβs what youβve built:
βββββββββββββββββββββββββββββββββββββββββββββββ
β Docker Compose Stack β
β β
β ββββββββββββββββ ββββββββββββββββββββ β
β β n8n Editor β β n8n Worker(s) β β
β β :5678 β β (scalable) β β
β ββββββββ¬ββββββββ ββββββββββ¬ββββββββββ β
β β β β
β ββββββββΌββββββββ ββββββββββΌββββββββββ β
β β Redis β β Postgres β β
β β (job queue) β β (workflow data) β β
β ββββββββββββββββ ββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββ
| Component | Role |
|---|---|
| n8n Editor | UI + workflow management |
| n8n Worker | Workflow execution (scale horizontally) |
| Redis | Job queue + execution status tracking |
| Postgres | Persistent workflow + execution data |
Whatβs Next
You now have a horizontally scalable n8n self hosted stack running locally β the same architectural pattern used in production cloud deployments. The gap between what youβve built here and a cloud-hosted version is smaller than youβd think.
In the next part of this series, weβll lift this exact stack to the cloud:
- Deploy to a cloud provider using Pulumi or Terraform
- Configure proper secrets management for production
- Set up TLS/HTTPS for the editor
- Add monitoring and alerting for worker health and queue depth
Quick Reference Commands
# Start the stack
make up
# Stop the stack
make down
# Check running containers
docker ps
# Tail n8n editor logs
docker logs -f dac-n8n-editor
# Tail worker logs
docker logs -f dac-n8n-worker
# Verify custom image labels
docker inspect dac-n8n-editor --format='{{json .Config.Labels}}'
Troubleshooting
Containers crash on startup
Check that your .env file has all required Postgres credentials and that DB_POSTGRESDB_HOST matches your Postgres service name in Docker Compose.
Workflows not executing in queue mode
Confirm EXECUTIONS_MODE=queue is set, Redis is reachable, and the worker container started successfully. Check worker logs for connection errors.
Custom modules not appearing in the editor
Verify N8N_CUSTOM_EXTENSIONS is set correctly in your Dockerfile and that the module installed without errors during the image build.
Permission errors on the data volume
Run chmod 777 ./data_n8n on the host machine before starting containers.
Found this useful? The next post in this series covers cloud deployment with infrastructure-as-code β taking the scalable n8n self hosted stack youβve built here and running it in production.