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
healthcheckwithpg_isreadyandcondition: service_healthymake n8n wait for the database to be ready before it starts. - The fixed
PGDATAkeeps the volume at the same path across PostgreSQL versions.postgres:18changed 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.
- 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).
- Start the new PostgreSQL installation using the same
N8N_ENCRYPTION_KEYas the old one. If you never set the key, it is in theconfigfile of the old volume (/home/node/.n8n/config). - 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
- Take a database backup with
pg_dump. - To update safely, follow how to update n8n.
- To scale, see n8n in queue mode with Redis.
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.