💡 Every Docker install guide for n8n stops at "it's running." None of them tell you what breaks the moment you actually start using it — the webhook that shows localhost, the workflow data that vanishes after a restart, the timezone that silently throws off every scheduled automation. This guide gets you running the production-ready way from the first command, and then covers exactly what to do about the eight things that go wrong next.
🔥 I self-host every workflow on this site's Templates library on the exact stack in this guide — Docker Compose, Postgres, persistent volumes, the works. Every error section below is something that actually happened on a real server, not a theoretical edge case copied from documentation. Whether you're setting up your first VPS or you've done this a dozen times and just want the config file, this guide is built to work for both.
To self-host n8n with Docker, install Docker and Docker Compose on your server, create a docker-compose.yml file defining n8n and a Postgres database, set your environment variables in a .env file, and run docker compose up -d.
The setup that actually matters for production:
- Use Postgres, not the default SQLite, for anything beyond quick testing.
- Store data in named volumes so updates never wipe your workflows.
- Set your timezone explicitly — scheduled workflows silently run at the wrong time otherwise.
- Set your real domain in the webhook URL before you need webhooks to work.
The complete, tested configuration is in section 8 — free to copy and deploy today.
What you'll find in this guide
1. Docker vs npm vs n8n Cloud — Pick the Right One First
Before touching a terminal, decide which installation path actually fits your situation. Getting this wrong means redoing the setup later.
| Method | Best for | Trade-off |
|---|---|---|
| Docker (this guide) | Production self-hosting, easy updates, consistent environment | Requires basic server and Docker familiarity |
| npm (direct install) | Local development, quick testing on your own machine | You manage Node.js versions and dependencies yourself |
| n8n Cloud | Zero server management, fastest start | Monthly cost, less infrastructure control, usage-based execution limits |
This guide focuses entirely on Docker because it's the option most people searching for a real, ongoing self-hosted setup actually need — and because it's the same stack this site's own automations and every downloadable template in the Workflow Templates library assumes you're running.
There's also a cost dimension worth being explicit about, in the same spirit as this site's Zero-Budget Lead Pipeline guide: n8n Cloud's starter tier runs roughly $20/month with capped executions, while a small VPS capable of running the Docker stack in this guide typically costs $5-10/month with no execution ceiling at all. For anyone running the automations this site covers — lead pipelines, invoice reminders, AI content workflows — that difference compounds fast once you're past the trial stage.
2. Prerequisites
- A VPS or server running Linux (Ubuntu 22.04 or newer is the most widely documented and supported choice).
- At least 1GB of RAM for light use; 2GB or more recommended once you're also running Postgres alongside n8n, which this guide's setup does.
- Docker and Docker Compose installed. Since Docker Engine 20.10, Compose ships as a plugin (
docker compose, no hyphen), so a separate install usually isn't needed on a recent Docker version. - A domain name pointed at your server's IP. Not strictly required to get a container running, but required for webhooks, OAuth-based credentials, and HTTPS to actually work — which means you'll need it very soon regardless.
Any mainstream VPS provider works fine here — n8n and Postgres together aren't resource-hungry at small scale. A budget 2GB RAM / 1 vCPU plan is a reasonable starting point for personal or small-team use; you can always resize later since the entire stack lives in Docker volumes, not tied to any specific server instance. If you're choosing between providers, prioritize ones with straightforward snapshot/backup features over marginal price differences — restoring from a snapshot after something goes wrong is worth far more than saving a couple of dollars a month.
docker --version and docker compose version. If either command fails, install Docker first — every step after this assumes both are already working.
3. Step-by-Step Installation
Create a project directory
Make a dedicated folder for your n8n setup and move into it: mkdir n8n-docker && cd n8n-docker. Everything else in this guide lives inside this one directory.
Create the .env file
This holds your secrets and configuration separately from the Docker Compose file itself, so you never commit passwords into version control by accident. The full content is in section 8.
Create the docker-compose.yml file
This defines two services: postgres for the database and n8n for the application itself, connected together with a healthcheck so n8n doesn't try to start before Postgres is actually ready to accept connections.
Start the stack
Run docker compose up -d from inside the project directory. Docker will download both images and start the containers in the background.
Verify it's running
Check container status with docker compose ps — both services should show as "healthy" or "running." Watch the logs with docker compose logs -f n8n if anything looks off. Visit your domain (or http://your-server-ip:5678 for a quick local test) to reach the n8n setup screen.
depends_on with a healthcheck avoids entirely — see the full config in section 8.
A Note on HTTPS and Reverse Proxies
Reaching n8n over plain HTTP on port 5678 works for a first test, but production use — and specifically anything involving OAuth credentials like Google or LinkedIn — requires HTTPS on a real domain. The standard approach is a reverse proxy (Caddy, Nginx, or Traefik) sitting in front of n8n, handling TLS certificates automatically and forwarding traffic to the container. Caddy is the simplest to get running correctly on a first attempt, since it issues and renews Let's Encrypt certificates with almost no manual configuration:
n8n.yourdomain.com {
reverse_proxy localhost:5678
}
That's the entire configuration for a working HTTPS setup with Caddy — it handles the certificate issuance and renewal on its own. Nginx and Traefik accomplish the same result with more configuration but finer control, and are worth considering once you're running more than a single service on the same server. A full walkthrough of hardening this setup (rate limiting, security headers, multi-service routing) is deliberately out of scope here — it deserves its own dedicated guide rather than a rushed afterthought in an installation article.
4. Postgres vs SQLite — Don't Skip This Decision
n8n defaults to SQLite if you don't configure a database at all. Most quick-start tutorials leave it there because it requires zero extra setup. That default is fine for a five-minute test — it's the wrong choice for anything you actually intend to rely on.
| SQLite (default) | Postgres (this guide) | |
|---|---|---|
| Setup effort | None — works out of the box | One extra service in Docker Compose |
| Concurrent writes | Poor — can lock or error under load | Handles concurrent executions reliably |
| Good for | Quick local testing, evaluating n8n | Any real, ongoing automation — including everything in this site's Templates library |
| Backup story | A single file, easy to copy but easy to forget | Standard database backup tooling (pg_dump, managed snapshots) |
Backing Up Your Data
With Postgres in the stack, backups are a standard database dump rather than hoping you remember to copy a single SQLite file. Run this from your host to produce a portable backup you can store off-server:
docker compose exec postgres pg_dump -U n8n -d n8n > n8n_backup_$(date +%Y%m%d).sql
Schedule this as a cron job on the host, and store the resulting file somewhere other than the same server — a backup that lives only on the machine it's backing up doesn't protect you from that machine failing.
5. The Next Problem You'll Hit (And Where to Fix It)
Here's what almost every Docker installation guide leaves out: getting the container running is not the same as having a working n8n instance. The single most common next issue, the moment you try to use a webhook-triggered workflow, is that the webhook URL shows localhost instead of your real domain — even though n8n is clearly running and reachable.
WEBHOOK_URL and N8N_HOST to your actual domain, not left at their defaults — both are already configured correctly in this guide's docker-compose.yml in section 8, but it's worth understanding why they matter before you hit the error blind.
If you're building your first real workflow after this install, the What is a Webhook guide is the natural next stop — it explains the concept this exact error is about, from zero.
6. 8 Common Errors During and After Installation
Most installation problems fall into one of three buckets: something about the environment configuration is missing or wrong, data persistence wasn't set up correctly, or a startup ordering issue caused a race condition between services. Knowing which bucket you're in narrows troubleshooting fast — check the logs first with docker compose logs -f before assuming the worst, since the actual error message usually points directly at one of the eight causes below.
Cause: Port 5678 is already in use by another process, or a firewall is blocking it.
✅ Fix: Run
docker compose logs n8n to check for a port binding error. If the port is taken, either free it or change the host-side port mapping in docker-compose.yml (for example, "8080:5678"). Separately, confirm your server's firewall allows inbound traffic on whichever port you're using.
Cause: Data was stored inside the container's writable layer instead of a named Docker volume, so recreating the container wiped it.
✅ Fix: Confirm
n8n_data:/home/node/.n8n is mapped as a named volume, not left unmapped. Check existing volumes with docker volume ls — if you don't see one for your n8n data, this is why it was lost, and there's no recovery for data that was never persisted.
Cause: On first login, n8n shows a prompt about activating a license key, which makes some self-hosters think they need to pay to continue.
✅ Fix: You don't. Skip or dismiss the prompt — without a license key, n8n runs as the free, fully-functional Community edition. The license key is only relevant if you're specifically licensing Enterprise features.
Cause: The container runs as a non-root user, and the mounted host directory doesn't grant it write access.
✅ Fix: Create the
local-files directory on the host before starting the stack, and check its ownership/permissions match what the container's user expects. This is a common trip-up specifically because it only surfaces once a workflow actually tries to read or write a file, not at startup.
Cause: Running only
docker compose pull downloads the new image but doesn't recreate the running container to use it.✅ Fix: Always follow a pull with
docker compose up -d, which recreates containers using the newly pulled image. Section 7 covers the full, safe update sequence.
Cause:
GENERIC_TIMEZONE was never set, so n8n defaults to UTC — which silently throws off every Schedule Trigger by however many hours separate UTC from where you actually are.✅ Fix: Set
GENERIC_TIMEZONE (and ideally TZ as well, for consistency with the underlying container) to your real timezone, like Africa/Tunis or Europe/London, before you rely on any time-based automation — including the reminder workflows in this site's Invoice Processing and Lead Pipeline guides.
Cause: n8n's container started before Postgres had finished initializing, so its first connection attempt failed.
✅ Fix: Use a healthcheck-based
depends_on (as in this guide's config) rather than a plain service name dependency, which only waits for the container to start, not for the database inside it to actually be ready to accept connections.
Cause: Basic authentication was never enabled, so anyone with the URL has full access to every workflow, credential, and execution log.
✅ Fix: Set
N8N_BASIC_AUTH_ACTIVE=true along with a username and password before exposing your instance to the public internet — never treat this as optional, even for a "just testing" deployment, since testing instances get discovered too.
A Worked Diagnostic Example
Say your n8n container shows as "running" in docker compose ps, but the browser just times out. Walk it in order: first, docker compose logs n8n — if it shows a successful startup with no errors, the problem is almost certainly network-level (firewall, security group, or DNS), not n8n itself. If the logs instead show a database connection error, check docker compose ps for Postgres specifically — if it's still starting or unhealthy, that's Error 7, and n8n is correctly refusing to run against a database that isn't ready yet. This kind of layered check — logs, then service health, then network — resolves the large majority of "it's just not working" reports faster than guessing at configuration changes one at a time.
7. Updating n8n Safely (Without Losing Data)
As long as your data lives in named volumes — which the config in this guide sets up by default — updating is low-risk. The sequence matters, though.
cd n8n-docker docker compose pull docker compose up -d docker compose logs -f n8n
Watch the logs after updating to confirm n8n starts cleanly before you consider the update finished. If something looks wrong, you can roll back by pinning the image tag in docker-compose.yml to the previous version and running up -d again.
latest, especially once you have production workflows running. This same lesson shows up in this site's LinkedIn integration guide — an unannounced version change breaking something that worked yesterday is a recurring theme in automation tooling generally, not just one integration.
If you're running anything business-critical, it's worth testing an update against a staging copy before touching production — even a second, temporary Docker Compose stack pointed at a copy of your data, torn down after the test. That's more setup than most personal automations need, but for a workflow that a client or team depends on, an hour of caution before an update beats an afternoon of recovery after one.
8. Complete Downloadable Configuration
This is the exact docker-compose.yml and .env pair referenced throughout this guide — Postgres included, healthcheck-gated startup, persistent volumes, and every environment variable from the error section already set correctly.
Production-Ready n8n Docker Stack
n8n + Postgres + persistent volumes · Deploy in 5 minutes
version: "3.8"
services:
postgres:
image: postgres:16
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
n8n:
image: docker.n8n.io/n8nio/n8n
restart: always
ports:
- "5678:5678"
environment:
- N8N_HOST=${N8N_HOST}
- N8N_PROTOCOL=https
- N8N_PORT=5678
- WEBHOOK_URL=https://${N8N_HOST}/
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- TZ=${GENERIC_TIMEZONE}
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- n8n_data:/home/node/.n8n
- ./local-files:/files
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
n8n_data:
POSTGRES_USER=n8n POSTGRES_PASSWORD=change_this_password POSTGRES_DB=n8n N8N_HOST=n8n.yourdomain.com GENERIC_TIMEZONE=Africa/Tunis N8N_BASIC_AUTH_USER=admin N8N_BASIC_AUTH_PASSWORD=change_this_password
Replace every value in .env — the placeholder passwords, your real domain in N8N_HOST, and your actual timezone — before running docker compose up -d. Never deploy this with the placeholder passwords still in place.
9. Frequently Asked Questions
For most self-hosted users, yes. Docker gives you a consistent, isolated environment that's easy to update and doesn't depend on managing Node.js versions directly on your server. The npm installation method is a reasonable alternative mainly for local development or testing.
SQLite (n8n's default) works fine for testing and very light personal use. For any production workflow, especially anything with concurrent executions or webhooks under load, Postgres is strongly recommended because SQLite does not handle concurrent writes well.
This happens when the WEBHOOK_URL and N8N_HOST environment variables aren't set to your real public domain. It's covered in full, with the exact fix, in this site's dedicated Webhook Localhost guide.
A minimum of 1GB RAM works for light personal use, but 2GB or more is recommended for anything beyond testing, especially if you're also running Postgres in the same Docker Compose stack.
As long as your data is stored in a named Docker volume (not inside the container itself), you can safely pull the latest image and recreate the container. Your workflows, credentials, and execution history live in the volume and persist through the update.
Not strictly for local testing, but yes for any real use — webhooks, OAuth-based credentials (Google, LinkedIn, and similar), and secure access all require a real public domain with HTTPS, not just a bare IP address.
Without N8N_BASIC_AUTH_ACTIVE set to true (or another authentication method), anyone who finds your instance's URL can access your workflows, credentials, and data. Always enable authentication before exposing n8n to the public internet.
Yes, this is the standard setup for small to medium deployments and is exactly what the Docker Compose configuration in this guide sets up. For larger production workloads, some teams move Postgres to a separate managed database service.
Continue Your n8n Journey
Your Own n8n Server, Running in Minutes
Download the production-ready Docker Compose config above, update the .env values, and run docker compose up -d.