AIFlowPost

How to Run n8n With Docker

Updated 2026-09-24 · 1116 words

How to Run n8n With Docker — AIFlowPost cover

This page may contain affiliate links. If you buy through them we may earn a commission at no extra cost to you.

Direct answer: for a quick look, docker run with a named volume gets n8n up in under a minute. For anything you intend to keep, use Docker Compose with a Postgres service instead of the default SQLite database, a named volume so data survives container recreation, and a pinned image version rather than latest. Get the webhook-related environment variables right before wiring up your first trigger, since that's the step that trips up almost everyone on their first Docker deploy. Here's exactly how we run it.

Step 1: Try it with a single command first

Before building anything permanent, confirm Docker itself is working and get a feel for n8n's editor with one command:

docker run -it --rm --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n

This pulls n8n's official image, exposes the editor on http://localhost:5678, and stores data in a named volume (n8n_data) so it survives if you stop and restart the container. It defaults to SQLite, which is fine for this step and wrong for anything else, SQLite doesn't handle concurrent writes well once you're running real workflows against it in production.

Use this step to confirm the port isn't already taken by something else on your machine and that you can log in and build a throwaway workflow. Once that works, move to Compose for anything you actually plan to keep running.

Step 2: Write a Compose file with Postgres from the start

Create a docker-compose.yml with two services, Postgres and n8n, and point n8n at it with DB_TYPE=postgresdb plus the matching DB_POSTGRESDB_HOST, DB_POSTGRESDB_DATABASE, DB_POSTGRESDB_USER, and DB_POSTGRESDB_PASSWORD variables. Give the Postgres service a named volume for its data directory, and give n8n its own named volume mounted at /home/node/.n8n for encryption keys and local files.

Starting on Postgres costs maybe fifteen extra minutes of Compose configuration over sticking with SQLite, and it saves you a live-data migration the first time SQLite's concurrency limits actually bite. Our full self-hosting guide walks through server sizing and the reverse-proxy layer on top of this if you're taking n8n to production rather than just running it locally or on a home server.

Step 3: Pin the image version, never pull latest

Reference a specific tag, docker.n8n.io/n8nio/n8n:1.6x.x style, not latest, in your Compose file. Pulling the newest image on every docker compose up means an unannounced breaking change in a new n8n release can hit your production workflows with zero warning the next time the container restarts, whether that's a deliberate update or just a server reboot. Bump the version deliberately, read the release notes for breaking changes first, and test in a non-production environment if you're running anything business-critical.

Step 4: Set the webhook and host variables before wiring up triggers

This is the single most common failure point for anyone running n8n in Docker. Set N8N_HOST to the domain or address n8n should present itself as, N8N_PROTOCOL to https (or http if you're only testing locally), and WEBHOOK_URL to the full public URL n8n should hand out for webhook-triggered nodes. Skip WEBHOOK_URL specifically and n8n constructs webhook addresses from its own container-internal view of itself, so every webhook node you build shows a URL that no external service, Stripe, a form tool, a CRM, can actually reach. This is exactly as easy to miss as it sounds, and it's the number one self-hosted n8n complaint across the community for a reason.

Step 5: Mount volumes correctly and check file permissions

n8n's official image runs as a non-root user by default, which is good practice but occasionally causes permission errors on bind-mounted host directories that don't already have the right ownership. Named Docker volumes (as opposed to bind mounts to a host path) sidestep this in most setups, since Docker manages the underlying permissions for you, which is why the examples above use named volumes rather than a ./data:/home/node/.n8n bind mount. If you do need a bind mount, for easier host-side backups, for example, set the directory's ownership to match the container's node user before first start.

Step 6: Restart, update, and back up without losing data

To update, pull the new pinned tag and recreate the container: docker compose pull && docker compose up -d if you're on Compose. Your workflows, credentials, and execution history live in the volume (and in Postgres, if you're using it), not in the container itself, so recreating the container is safe as long as the underlying volumes are intact. Back up the Postgres volume specifically if you've moved off SQLite, a nightly pg_dump shipped somewhere off the container host is the minimum viable version of this, and it's worth testing a restore once on a throwaway setup before you trust it.

Single container vs. Compose stack

Single docker run Docker Compose
Setup time Under a minute 20–30 minutes
Database SQLite (default) Postgres (recommended)
Good for A first look, throwaway testing Anything you plan to keep or rely on
Multiple services (reverse proxy, Postgres) Awkward, multiple long commands One file, one command
Config reproducibility Easy to mistype flags each run Versioned in the Compose file
Recommended for production No Yes, plus a reverse proxy and TLS

Common pitfalls to avoid

Beyond the WEBHOOK_URL issue in Step 4, the next most common mistake is running the quick docker run command from Step 1 well past the exploration phase, on SQLite, with no version pin, and discovering the concurrency and reliability limits only once real workflows depend on it. The second is a bind-mount permission error that looks like a crash on first start but is actually the container's non-root user unable to write to the mounted host directory; named volumes avoid this in almost every setup we've tested.

Docker gets n8n running reliably and repeatably, but it isn't the whole production picture on its own: there's no HTTPS, no reverse proxy, and no backup schedule baked into a Compose file by default. If you're taking this past local testing, our how to self-host n8n guide picks up exactly where this one stops, server sizing, a reverse proxy for HTTPS, and a real backup routine. For how n8n compares to other self-hosted options before you commit infrastructure time, our n8n vs Activepieces comparison and best open source automation tools roundup are both worth reading first. n8n's own Docker installation docs stay current with image tags and variable names faster than any third-party guide can.

Start with the single command to confirm Docker works, move to Compose with Postgres before you build anything real, pin your version, and get the webhook variables right before your first trigger. That order avoids the two mistakes that account for most of the "n8n in Docker isn't working" reports we've seen.

Frequently asked questions

What's the fastest way to try n8n with Docker?

A single command: `docker run -it --rm --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n`. It pulls the image, starts n8n on port 5678, and stores its SQLite data in a named Docker volume. That's enough to explore the editor and build a test workflow, but treat it as disposable, not a base to build real automations on.

Do I need Docker Compose, or is a single container enough?

A single container is fine for trying n8n out. The moment you want to add Postgres, set environment variables reliably, or run n8n alongside a reverse proxy, Compose is worth the extra file: it defines all of that in one place and restarts the whole stack with one command instead of several long `docker run` flags you'll mistype eventually.

Why did my webhook stop working after I put n8n behind Docker?

Almost always a missing or wrong `WEBHOOK_URL`. Without it, n8n builds webhook addresses from its own internal view of itself inside the container, which no external service can reach. Set `WEBHOOK_URL` to the full public HTTPS address n8n should hand out, and `N8N_HOST` plus `N8N_PROTOCOL` to match, before wiring up any webhook-triggered node.

How do I update n8n running in Docker without losing my workflows?

Pull the new image tag, then recreate the container (`docker compose up -d` if you're on Compose, or `docker stop` and a fresh `docker run` with the same volume otherwise). Your workflows and credentials live in the volume, not the container itself, so recreating the container is safe as long as the volume, and the Postgres data if you're using it, stays intact. Read the release notes before jumping more than a minor version.

↑ Back to top