📁 last Posts

n8n Self-Hosted with Docker: The Complete Installation Guide

n8n self-hosted Docker installation guide - Docker Compose, Postgres, and production setup
Figure 1: A production-ready n8n instance, running entirely on your own server.

💡 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.

The Direct Answer

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:

  1. Use Postgres, not the default SQLite, for anything beyond quick testing.
  2. Store data in named volumes so updates never wipe your workflows.
  3. Set your timezone explicitly — scheduled workflows silently run at the wrong time otherwise.
  4. 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.

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.

MethodBest forTrade-off
Docker (this guide)Production self-hosting, easy updates, consistent environmentRequires basic server and Docker familiarity
npm (direct install)Local development, quick testing on your own machineYou manage Node.js versions and dependencies yourself
n8n CloudZero server management, fastest startMonthly 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.

💡
Verify Docker is ready before continuing: run 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

1

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.

2

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.

3

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.

4

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.

5

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.

⚠️
Don't skip the healthcheck-based dependency. A common shortcut in quick-start guides is starting n8n and Postgres at the same time with no dependency check. n8n usually recovers on its own after a retry, but it's an unnecessary failure on first boot that a proper depends_on with a healthcheck avoids entirely — see the full config in section 8.
n8n setup screen after successful Docker Compose installation
Figure 2: The n8n owner account setup screen — your signal that the container stack is running correctly.

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:

Caddyfile (minimal example)
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 effortNone — works out of the boxOne extra service in Docker Compose
Concurrent writesPoor — can lock or error under loadHandles concurrent executions reliably
Good forQuick local testing, evaluating n8nAny real, ongoing automation — including everything in this site's Templates library
Backup storyA single file, easy to copy but easy to forgetStandard database backup tooling (pg_dump, managed snapshots)
📌
If you've already followed a guide that skipped Postgres, you're not stuck — n8n supports migrating from SQLite to Postgres later. It's more work than starting with Postgres from day one, which is why this guide's config includes it by default rather than as an afterthought.

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:

Backup command
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.

Diagram comparing SQLite and Postgres database options for self-hosted n8n
Figure 3: Postgres adds one extra service to your stack in exchange for reliability SQLite can't match under real load.

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.

⚠️
This is covered in full, with the exact fix, in this site's dedicated Webhook URL shows Localhost guide. The short version: set 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.

❌ Error 1: Containers start but n8n won't load in the browser
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.
❌ Error 2: All workflows and credentials disappear after a restart
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.
❌ Error 3: Confusion over the Community Edition license activation prompt
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.
❌ Error 4: Permission errors on the local-files directory
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.
❌ Error 5: Running the update command doesn't actually update n8n
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.
❌ Error 6: Scheduled workflows run at the wrong time
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.
❌ Error 7: "Connection refused" to the Postgres database on first boot
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.
❌ Error 8: n8n instance is publicly reachable with no login required
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.

Safe update sequence
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.

💡
Consider pinning a specific version tag rather than always tracking 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

docker-compose.yml
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:
.env
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
✅
What makes this config production-ready, not just functional: Postgres instead of SQLite (section 4), a healthcheck gating n8n's startup so it never races Postgres (Error 7), named volumes for both services so updates never wipe data (Error 2), timezone set explicitly (Error 6), and basic auth enabled by default (Error 8) — every fix from section 6 is already applied here, not left as an exercise for the reader.
⚠️

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

Is Docker the best way to install n8n?

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.

Do I need Postgres, or is SQLite enough?

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.

Why does n8n say my webhook URL shows localhost after installing with Docker?

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.

How much RAM does n8n need to run in Docker?

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.

How do I update n8n without losing my workflows?

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.

Do I need a domain name to self-host n8n with Docker?

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.

What happens if I forget to enable authentication on my n8n instance?

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.

Can I run n8n and Postgres on the same server safely?

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

Ahmed Ayari — TriggerWorkflow Every workflow and template on this site runs on the exact Docker Compose stack in this guide. I wrote the error section from real server logs, not documentation — including the timezone bug that quietly broke a client's invoice reminders for a week before anyone noticed. Read more guides by Mr.Ayari

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.

Comments