n8n with Docker: the Compose file that actually works in 2026

Most n8n Docker guides were written for 1.x and quietly break on 2.x. Here is a Compose file that runs today, the environment variables that actually matter, and the six things version 2.0 changed under everyone's feet.

Dmytro Chervonyi

Dmytro Chervonyi

Co-founder & CMO, livemy.app

Last updated

9

min.

Reading time

TABLE OF CONTENTS

item

n8n Docker Compose Setup That Actually Works (2026)

AI Summary

Running n8n with Docker in 2026 means running n8n 2.x, and most published Compose files predate it. The current image is docker.n8n.io/n8nio/n8n, the data volume is n8n_data:/home/node/.n8n, and the port is 5678. Six things changed in 2.0 that break older setups: MySQL and MariaDB are no longer supported storage backends (migrate to Postgres or SQLite first); the legacy SQLite driver is gone and connection pooling is mandatory via DB_SQLITE_POOL_SIZE; task runners are on by default and N8N_RUNNERS_ENABLED is deprecated, so guides telling you to set it are stale; the main image no longer bundles task runners and external mode needs the separate n8nio/runners image; the in-memory binary data mode is removed in favour of N8N_DEFAULT_BINARY_DATA_MODE; and Code nodes can no longer read environment variables because N8N_BLOCK_ENV_ACCESS_IN_NODE defaults to true. N8N_CONFIG_FILES and QUEUE_WORKER_MAX_STALLED_COUNT were removed outright. The guide gives a working Compose file with Postgres, the environment variables that matter, an upgrade checklist for 1.x users, and the five things that quietly break a self-hosted n8n in production — webhook URL mismatches, a lost encryption key, cron timezone drift, an unbacked-up volume, and a reverse proxy that terminates SSL without passing the host through.

Why most n8n Docker guides are now wrong

If you searched for an n8n Compose file and found one that tells you to set N8N_RUNNERS_ENABLED=true, it was written for n8n 1.x. That variable is deprecated in 2.0 — task runners are on by default now, and you no longer need to set it at all.

n8n's 2.x line is the current stable release, and the jump from 1.x removed several things people's Compose files still reference. Copy an old file and you get one of two outcomes: it starts and silently behaves differently, or it refuses to start and the error message doesn't explain why.

This guide is the current shape: where to get a Compose file that isn't stale, the environment variables that actually matter, and the six changes to check before you upgrade an existing instance.

Skip the manual setup: livemy.app has a one-click n8n template — Postgres, persistent storage and free SSL wired up, live in about two minutes. The rest of this guide is for people who want to run it themselves.

Start from the official file, not from a blog post

n8n maintains its own hosting examples at github.com/n8n-io/n8n-hosting. The docker-compose/withPostgres example is the one you want: n8n plus Postgres, with a .env file for credentials. It is maintained alongside the product, which is the one thing a blog post can't promise.

Clone it, edit the .env, run docker compose up -d. Then make four changes, because the example is deliberately generic:

  • Pin the image tag. The image is docker.n8n.io/n8nio/n8n. Pin a version rather than tracking latest, so an upgrade is a decision you make, not something that happens during an unrelated restart.

  • Set the encryption key explicitly. Add N8N_ENCRYPTION_KEY with a 32-character random string. Without it n8n generates one into the data folder, and a restore onto a fresh volume makes every credential unreadable.

  • Bind to localhost if the box is public. Change the port mapping from 5678:5678 to 127.0.0.1:5678:5678 so only your reverse proxy can reach n8n.

  • Add a healthcheck to Postgres and a depends_on condition so n8n doesn't start before the database accepts connections. Without it, the first boot after a reboot is a coin flip.

Note what does not belong in the file any more: task runner configuration. On 2.x it is on by default. If a guide has you adding runner variables here, that guide predates 2.0.

The environment variables that actually matter

n8n has a long list of settings. These are the ones that decide whether your instance works in production.

  • N8N_ENCRYPTION_KEY — encrypts stored credentials. Set it explicitly, and store it outside the server.

  • WEBHOOK_URL — the public URL n8n hands to Stripe, Calendly, GitHub and everything else. It is not inferred from the URL you type in the browser. Full public URL, trailing slash.

  • N8N_HOST and N8N_PROTOCOL — the domain and scheme n8n believes it is running on. Behind a proxy terminating TLS, protocol is https even though the container speaks plain HTTP internally.

  • GENERIC_TIMEZONE and TZ — set both, to the same IANA zone. The first controls when cron nodes fire, the second the container clock. Setting only one is how "every weekday at 9am" becomes 9am UTC.

  • DB_TYPE=postgresdb plus DB_POSTGRESDB_HOST, DB_POSTGRESDB_PORT, DB_POSTGRESDB_DATABASE, DB_POSTGRESDB_USER and DB_POSTGRESDB_PASSWORD. Without these n8n falls back to SQLite.

  • N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true — locks down the settings file. Current n8n expects this on.

  • N8N_DEFAULT_BINARY_DATA_MODE — where binary data lives: filesystem, database or s3. Relevant the moment your workflows move files.

The data volume mounts at /home/node/.n8n and the container listens on port 5678. Those two are worth memorising — they show up in every backup script and proxy config you will write.

Six things n8n 2.0 changed under everyone's feet

This is the part missing from most guides, and the reason a Compose file that worked last year may not work now. Check all six before upgrading an existing instance.

1. MySQL and MariaDB are gone

n8n no longer supports them as storage backends. If your 1.x instance runs on MySQL, migrate to PostgreSQL before you upgrade the image, not after.

2. The legacy SQLite driver was removed

The pooling driver is now mandatory and runs in WAL mode. If you're staying on SQLite, set DB_SQLITE_POOL_SIZE (the default is 2) and test before upgrading. For anything with concurrent executions, this is the moment to move to Postgres anyway.

3. Task runners are on by default

Code node executions now run through task runners in secure mode, and N8N_RUNNERS_ENABLED is deprecated. If you're on 1.x, set it to true there first and confirm your Code nodes still behave — then upgrade.

4. The main image no longer bundles runners

n8nio/n8n ships without them. If you run task runners in external mode, you need the separate n8nio/runners image as its own service. Single-container setups are unaffected.

5. Code nodes can't read environment variables

N8N_BLOCK_ENV_ACCESS_IN_NODE now defaults to true. Any workflow whose Code node reads process.env stops seeing values. You can set it to false, but the better fix is moving those values into credentials.

6. Two variables were removed outright

N8N_CONFIG_FILES is gone — move those settings into environment variables or a .env file. QUEUE_WORKER_MAX_STALLED_COUNT is gone too; delete it from your config or the container complains about an unknown setting.

The 1.x to 2.x upgrade checklist

In order. Skipping step one is how people lose credentials.

  1. Back up the database and the n8n data volume. Verify the backup restores somewhere else before you touch anything.

  2. Confirm N8N_ENCRYPTION_KEY is set explicitly and written down outside the server.

  3. If you're on MySQL or MariaDB, migrate to PostgreSQL first.

  4. If you're staying on SQLite, set DB_SQLITE_POOL_SIZE=2.

  5. On 1.x, set N8N_RUNNERS_ENABLED=true and run your Code-node workflows for a day.

  6. Check your workflows for Code nodes reading process.env. Move those values to credentials.

  7. Remove N8N_CONFIG_FILES and QUEUE_WORKER_MAX_STALLED_COUNT from your config.

  8. Pull the new image, restart, and re-test one webhook and one scheduled workflow end to end.

Five things that quietly break a Dockerised n8n

1. WEBHOOK_URL doesn't match the public URL

The most common self-hosted n8n failure, and it fails silently: the workflow looks fine, but the webhook you registered at the other end points at localhost:5678. Set WEBHOOK_URL to the full public URL with a trailing slash, redeploy, re-register one webhook and watch it fire.

2. The encryption key wasn't written down

Restore a backup onto a fresh volume without the same N8N_ENCRYPTION_KEY and every stored credential is unreadable. Not recoverable — you re-enter every API key by hand. Store the key in a password manager the day you create it.

3. The reverse proxy strips the host

nginx or Caddy terminates TLS and forwards to port 5678. If the proxy doesn't pass the original host and protocol headers through, n8n builds redirect and webhook URLs from the internal address. Symptoms: OAuth callbacks fail, webhook URLs look wrong in the UI.

4. The volume isn't in the backup

Plenty of people back up Postgres and stop there. The data volume holds the settings file and, if you didn't set the key explicitly, the encryption key itself. Back up both, or you have half a restore.

5. Nothing tells you when it stops

A container that exits at 3am takes your scheduled workflows with it, and n8n cannot alert you that n8n is down. Point an external monitor at the instance — a self-hosted Uptime Kuma on a different machine is the usual answer.

When Docker Compose is the wrong answer

Running the Compose file is twenty minutes. Running it in production is the reverse proxy, the certificate renewals, the version upgrades, the backup you test quarterly, and the monitoring that tells you when it stopped. That's the actual cost, and it's paid monthly in attention rather than dollars.

If that trade reads badly to you, the same Community Edition runs on a managed host with no meter on executions — our one-click n8n template is $10/month flat on Maker with a custom domain and SSL handled, backups a $5/month add-on. It is more expensive than a $5 VPS and buys back the maintenance. What it doesn't give you is root on the box: if you need to install system packages next to n8n, a VPS is still the right shape — see running n8n on a VPS for that route, or the full comparison of n8n hosting options if you're still choosing.

FAQ

What is the correct Docker image for n8n?

docker.n8n.io/n8nio/n8n. Pin a specific tag in production rather than tracking latest, so an upgrade is a decision you make rather than something that happens during a restart.

Do I still need N8N_RUNNERS_ENABLED?

No. It is deprecated from n8n 2.0 — task runners are enabled by default. It is still meaningful on 1.x, which is why so many guides mention it. If you are on 1.x and planning to upgrade, turning it on early is a good way to test Code-node behaviour before the version jump.

Do I need Postgres, or is SQLite fine?

SQLite is fine for a personal instance with a handful of workflows. It stops being fine as soon as workflows run concurrently, because writes lock the file. On 2.x the pooling driver softens this, but Postgres remains the answer for anything with real traffic. Switching later means a database migration, so it is cheaper to start there.

Where does n8n store data in Docker?

In /home/node/.n8n inside the container — mount a named volume there. That folder holds the settings file, and the auto-generated encryption key if you didn't set one. Workflows and credentials live in the database you configured.

How do I update n8n running in Docker?

Run docker compose pull then docker compose up -d. Back up the database and volume first, and read the breaking-changes notes for the major version you're moving to — the 1.x to 2.x jump in particular removed database backends and environment variables.

Can I run n8n and Postgres in one container?

You can, and you shouldn't. Two processes in one container means a restart takes both down, upgrades are entangled, and the database has no independent lifecycle. Two services in one Compose file is barely more work and behaves properly.

Run it, then decide who maintains it

Start from n8n's own Compose example, make the four changes above, and check the six breaking changes before you point it at an existing database. After that the question stops being technical: someone has to own the upgrades, the certificates, and the backups.

→ Deploy the n8n template on livemy.app · Community Edition, no execution meter · Custom domain and SSL on Maker at $10/month flat · Backups add-on $5/month.

Stuck on an upgrade or a webhook that won't fire? Email hello@livemy.app with your Compose file and the container log. Replies inside one business day.

Read next

Dmytro Chervonyi

Dmytro Chervonyi

,

Co-founder & CMO, livemy.app

Co-founder & CMO at livemy.app. 12 years as a CMO scaling SaaS from $0 to $10M+ ARR across marketing, sales, and infra products and tools. Now building the missing step between AI-built code and a live URL — for non-developers who'd rather ship than learn DevOps.

Build something.
We'll make it live.

Free to start. 2 minutes to deploy. One click to cancel.

No credit card · No commitment · Free tier forever