Skip to content
SelfHostBR, home page
Guide

n8n with Docker Compose and PostgreSQL: step by step

Run n8n with PostgreSQL 18 in Docker Compose: a complete compose.yaml, a fixed encryption key, a healthcheck and the memory you can expect.

MMarlon Published on 6 min read
On this page

Short answer: add a postgres:18 service to the same compose.yaml as n8n and point n8n at it with DB_TYPE=postgresdb and the DB_POSTGRESDB_* variables. n8n creates the tables by itself on the first start.

n8n’s default is SQLite, which is fine for personal use. PostgreSQL pays off when the number of executions grows, when you want to back up with the database’s own tools, or when you plan to use queue mode, which is not recommended with SQLite. The documentation supports PostgreSQL versions 16, 17 and 18 (docs).

Create the .env file

In a new folder (for example ~/n8n), create the .env file:

POSTGRES_USER=n8n
POSTGRES_PASSWORD=change-this-password
POSTGRES_DB=n8n
N8N_ENCRYPTION_KEY=replace-with-a-long-random-key

Generate the key with openssl rand -hex 32 and keep a copy off the server. It encrypts the credentials saved in the database: without it, a PostgreSQL backup cannot recover them (see the backup guide).

Create the compose.yaml

services:
  postgres:
    image: postgres:18
    restart: always
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - db_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: n8nio/n8n:stable
    restart: always
    environment:
      - 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}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - GENERIC_TIMEZONE=Europe/Berlin
      - TZ=Europe/Berlin
    ports:
      - "127.0.0.1:5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  db_data:
  n8n_data:

The points that matter:

  • The healthcheck with pg_isready and condition: service_healthy make n8n wait for the database to be ready before it starts.
  • The fixed PGDATA keeps the volume at the same path across PostgreSQL versions. postgres:18 changed the default directory, and the official n8n example also pins this path (n8n-hosting).
  • Port 5678 is bound to 127.0.0.1. For outside access with HTTPS, use a reverse proxy, such as the Traefik setup in the Docker Compose guide.
  • PostgreSQL publishes no port at all: only n8n talks to it, over the internal Compose network.

Start it and check

docker compose up -d
docker compose ps
docker compose logs n8n --tail 20

After about 30 to 40 seconds, the log shows Editor is now accessible via: http://localhost:5678. To confirm n8n is using PostgreSQL:

docker compose exec postgres psql -U n8n -d n8n -c "\dt" | head

The list should include tables such as workflow_entity, execution_entity and credentials_entity.

How much memory to reserve

Idle, right after starting, n8n sits around 370 MiB and PostgreSQL around 60 MiB. The database of a fresh instance takes about 16 MB. In other words, PostgreSQL adds little: a 2 GB VPS handles light to moderate use well. To size with room to spare, see the RAM and CPU requirements and the calculator.

I already use SQLite. How do I migrate?

The n8n CLI has a pair of commands built for this: export:entities reads every SQLite table and import:entities writes into a database of another type, such as PostgreSQL (docs). Unlike the workflow export, it also carries users, projects and settings, and published workflows stay published.

  1. On the old installation (SQLite), export and copy the folder out of the container:
docker compose exec n8n n8n export:entities --outputDir=/home/node/.n8n/migration
docker compose cp n8n:/home/node/.n8n/migration ./migration

To carry the execution history too, add --includeExecutionHistoryDataTables=true (the file can get large).

  1. Start the new PostgreSQL installation using the same N8N_ENCRYPTION_KEY as the old one. If you never set the key, it is in the config file of the old volume (/home/node/.n8n/config).
  2. Stop the new n8n, copy the folder in and import:
docker compose stop n8n
docker compose cp ./migration n8n:/home/node/.n8n/migration
docker compose run --rm n8n import:entities --inputDir=/home/node/.n8n/migration --truncateTables=true
docker compose start n8n

--truncateTables wipes whatever the first start created in the new database; use it only on an installation that has no data yet.

Next steps

Frequently asked questions

Which PostgreSQL versions does n8n support?

According to the documentation, versions 16, 17 and 18.

Do I need to create the n8n tables in PostgreSQL?

No. Just create the database and the user; n8n creates the tables by itself on the first start.

How much memory does PostgreSQL use alongside n8n?

On a fresh, idle instance, PostgreSQL sits around 60 MiB and n8n around 370 MiB.

M

Written by

Marlon

I like automation and write here what I learn building automations with n8n. Spotted a mistake or an outdated detail? Let me know.

Keep reading