n8n is the workflow automation tool people reach for when Zapier's per-task pricing stops making sense. Self-hosting it removes execution caps entirely - but most self-hosting guides stop at docker run n8n, which gives you a single instance that falls over the first time two heavy workflows run at once.
This guide sets up n8n the way it should run in production: queue mode, with the editor and webhooks on a main instance, execution on horizontally scalable workers, and Postgres plus Valkey behind it. One honest disclosure up front: Miget is our platform, and we run our own growth automation on an n8n instance deployed exactly this way - this is the setup we operate daily, not a lab exercise.
Why queue mode instead of a single container
The default n8n container does everything in one process: serves the editor, receives webhooks, schedules crons, and executes workflows. Under load, a long-running execution blocks the rest.
Queue mode splits this:
| Service | Role | Public |
|---|---|---|
n8n (main) | editor + API + webhooks + scheduler | yes (HTTPS) |
n8n-worker x2 | workflow execution (BullMQ consumers) | no |
| Postgres | workflow definitions, credentials, execution data | no |
| Valkey (Redis-compatible) | the execution queue | no |
Workers are stateless - binary data lives in the database in the default mode - so scaling means adding worker replicas, nothing else. Each worker processes up to 10 concurrent executions by default (tunable with worker --concurrency=N).
Deploy it in three steps
The whole stack is a portable Docker Compose file maintained in the deployable.sh stacks catalogue - the same file runs locally with docker compose up or on Miget as a Compose Stack.
1. Create a Compose Stack in app.miget.com pointing at https://github.com/deployable-sh/stacks, path n8n. Miget reads the compose file, detects the four services, and provisions the db and queue entries as managed Postgres and managed Valkey - real platform services with backups available, not containers you babysit.
2. Set one variable: N8N_ENCRYPTION_KEY. Generate it with:
openssl rand -hex 24
This key encrypts every credential n8n stores. It must never change once set - n8n cannot decrypt saved credentials with a new key. Store a copy somewhere safe.
3. Deploy. Connection settings between the main instance, workers, Postgres, and Valkey are auto-wired from the managed services' credentials. Every public service gets a TLS domain automatically.
After the first deploy, one follow-up: add a WEBHOOK_URL variable on the n8n app set to https://<your-n8n-domain>/ and redeploy, so webhook and OAuth callback URLs use the real public domain instead of an internal one.
Prefer to try it locally first? Same repo:
git clone https://github.com/deployable-sh/stacks
cd stacks/n8n
cp .env.example .env # set N8N_ENCRYPTION_KEY
docker compose up -d
open http://localhost:5000
No lock-in either way - the base compose.yaml is plain Docker Compose, and the Miget-specific bits live in a separate compose.miget.yaml overlay.
What it actually costs
The stack's default sizing is deliberately production-shaped: 1 GiB for the main instance, 1 GiB per worker (x2), a managed Postgres at 1 GiB RAM / 5 GiB storage, and a 256 MiB Valkey - about 4.25 GiB total. On Miget that fits the $25/month plan (4 GiB RAM, 2 vCPU) with the Fair Scheduler sharing capacity between services that are rarely all busy at once.
Because Miget charges for the compute plan rather than per service, the databases and workers add $0 to the bill - the whole stack is one flat price. For lighter workloads you can trim to a single worker and smaller instances and run on a smaller plan; for heavier ones, raise worker replicas without touching anything else.
Compare that structure with per-service platforms, where main + two workers + Postgres + Redis means five billed line items - the math is the same one we walk through in the Render pricing breakdown.
Production notes from running it ourselves
Things we learned operating our own n8n on this exact setup:
- Pin your version.
n8nio/n8n:latesttracks upstream, which is fine until a breaking change lands mid-week. Pin a tested tag, and always upgrade the main instance and workers together - they must run the same version. - The encryption key is forever. Back it up the day you set it. Losing it means re-entering every stored credential.
- Set
WEBHOOK_URLbefore wiring external services. Slack, GitHub, and OAuth integrations will otherwise register callbacks against the wrong host. N8N_SECURE_COOKIEstaystruebehind TLS (the Miget overlay sets this); it isfalseonly for plain-HTTP local development.- Workers scale, the main instance does not need to. Our automation runs scheduled syncs, report generation, and webhook-driven flows - the main instance idles while workers do the lifting. Scale
deploy.replicason the worker, not the main. - Postgres is where the state lives. Treat it accordingly: the managed instance supports backups, and if your workflows are business-critical, review the PostgreSQL HA options.
Self-hosted n8n vs n8n Cloud
n8n Cloud is the hosted offering with plans metered by workflow executions. Self-hosting under n8n's Sustainable Use License is free for internal business use (you cannot resell n8n itself as a service - check the license for specifics), which means:
- No execution caps - a workflow that runs every minute costs the same compute as one that runs daily
- Your data stays in your stack - credentials, execution logs, and payloads never leave your database
- You own the upgrade cadence - pin versions, test, then roll forward
The trade-off is that you operate it. The setup above shrinks that burden to: watch the version tag, keep the encryption key safe, and let the platform run the databases.
Frequently Asked Questions
Is self-hosting n8n free?
The software is free to self-host for internal business use under n8n's Sustainable Use License. Your only cost is infrastructure - on Miget, a production queue-mode stack runs on the $25/month plan, and a trimmed setup on less.
How much RAM does n8n need?
A single light instance runs in 512 MiB, but production queue mode wants roughly 1 GiB for the main instance and 1 GiB per worker, plus Postgres. The default stack sizing (about 4.25 GiB total) is comfortable for real workloads.
How do I scale n8n executions?
Add workers. In queue mode, workers are stateless BullMQ consumers - raise deploy.replicas on the worker service (or scale the worker app on Miget) and each new replica picks up executions from the same queue. Default concurrency is 10 executions per worker.
Can I run scheduled workflows (crons) on self-hosted n8n?
Yes - the main instance schedules them and workers execute them, with no per-run fees. We run our own scheduled automations this way; see also how cron jobs work on a PaaS.
How do I upgrade self-hosted n8n safely?
Pin an image tag, read the release notes, upgrade main and workers together (same version), and take a database backup first. Never change N8N_ENCRYPTION_KEY during an upgrade.
What to read next
- Docker Compose Stacks: Deploy Multi-Service Apps From One File - how the stack deployment mechanism works
- Deploy 138 Self-Hostable Stacks With deployable.sh - the full catalogue this n8n stack comes from
- Free PostgreSQL Hosting with Public Access - the managed Postgres that backs this setup