
Your n8n runs in Docker on a VPS and keeps everything in the SQLite file it created on first start. Then a forum thread says production needs PostgreSQL. Do you have to switch, and can you do it without losing workflows, credentials and logins? This guide gives n8n's own answer, shows what makes either database grow, and moves a Docker Compose install to PostgreSQL with the export and import commands n8n built for it.
Quick answer: SQLite is n8n's default and fine for one n8n instance with a few users: since n8n 2.0 it runs in WAL mode with a single write connection, and n8n Cloud runs its Starter and Pro plans on it. Move to PostgreSQL (16, 17 or 18) when you want queue mode with workers, when more than a handful of people or round-the-clock workflows depend on n8n, or when backups must run without stopping it. MySQL and MariaDB support ended with n8n 2.0. On either database, leave execution pruning on: by default it deletes executions after 14 days or once there are more than 10,000. To migrate, stop n8n, run n8n export:entities, point n8n at PostgreSQL while keeping the same .n8n volume, and run n8n import:entities --truncateTables=true.
What n8n Supports
n8n keeps workflows, credentials and past executions in one database. Its database page lists two:
- SQLite, used when nothing is set. The file is
database.sqlitein the.n8nfolder, which the Docker image keeps at/home/node/.n8n. - PostgreSQL, with
DB_TYPE=postgresdband theDB_POSTGRESDB_*variables. n8n supports the two newest maintained major versions (17 and 18, as of July 2026) plus 16. Amazon Aurora is experimental; AlloyDB, CockroachDB and YugabyteDB aren't supported.
MySQL and MariaDB were deprecated in n8n 1.0 and dropped in 2.0; the MySQL node in your workflows still works. Installing PostgreSQL with apt instead of Docker? Ubuntu 24.04 ships 16, which is on n8n's list. Debian 12 ships 15, which isn't, so on Debian use the PostgreSQL apt repository or the Docker image below.
When SQLite Is Enough
SQLite lets any number of readers in at once but only one writer at a time, and other writers queue up (when to use SQLite). n8n 2.0 made its pooling driver the only SQLite driver: WAL mode, one write connection and a pool of read connections, which n8n's benchmarks put at up to 10 times faster than the old driver. n8n Cloud runs its Starter, Pro and legacy Enterprise plans on SQLite; only Enterprise Scaling uses PostgreSQL.
n8n's Docker Compose guide draws the line: "SQLite is fine for trying things out, but for a production instance that must handle more than a handful of users or workflows running around the clock use Postgres instead." So SQLite fits one n8n container that you and a few colleagues use, as long as two things hold:
- The
.n8nvolume sits on the VPS's own disk. WAL "does not work over a network filesystem" (SQLite WAL), so no NFS or SMB mount. - You can stop n8n for backups. n8n's backup page warns that copying the file while n8n writes to it "can produce an inconsistent backup". Our n8n backup guide shows the stop, copy and restore.
When You Need PostgreSQL
Queue mode. In queue mode the main instance passes each execution ID to Redis, and a worker loads the workflow from the database and writes the result back. n8n says queue mode with SQLite "isn't recommended" and a distributed setup over SQLite "isn't supported". Workers on another server would reach the database over the network, a case SQLite's own checklist answers with "choose client/server". Multi-main setups, an Enterprise feature, also require PostgreSQL and Redis.
More users and round-the-clock workflows. That is n8n's own line, quoted above.
Backups while n8n runs. pg_dump "makes consistent exports even if the database is being used concurrently" and doesn't block readers or writers, so a nightly dump needs no downtime. Our PostgreSQL backup guide sets that up.
| SQLite | PostgreSQL | |
|---|---|---|
| Setup | Nothing, it's the default | A Postgres service and the DB_ variables |
| Queue mode with workers | Not recommended | Supported |
| Backup while n8n runs | Stop n8n, or use a snapshot tool | pg_dump, live |
| Space freed by pruning | Reused; VACUUM shrinks the file | Reused; VACUUM FULL returns it |
| What to back up | The .n8n volume | The .n8n volume (it holds the key) and a dump |
Execution Data: What Fills Either Database
What grows is execution history: execution_entity stores each saved execution and execution_data holds the workflow as it ran plus its data (database tables). By default n8n saves every successful, failed and manual execution, and pruning is on: it deletes finished executions older than EXECUTIONS_DATA_MAX_AGE (336 hours) or, oldest first, once there are more than EXECUTIONS_DATA_PRUNE_MAX_COUNT (10,000). Running, waiting and annotated executions are never pruned.
To store less, add this to the n8n service's environment: block and run sudo docker compose up -d. Failed executions stay saved, the default, so you can still debug them. If your block is a list of - KEY=value lines, write the new lines in that form, because Compose accepts a map or a list but not both in one block:
EXECUTIONS_DATA_SAVE_ON_SUCCESS: none
EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS: "false"
EXECUTIONS_DATA_MAX_AGE: 168 # hours, so 7 days
Pruning frees space inside the database, not on the disk:
- SQLite reuses it for new executions. To shrink the file, set
DB_SQLITE_VACUUM_ON_STARTUP: "true"for one restart, then remove it. The VACUUM blocks startup while it runs and can need twice the file's size in free disk space. - PostgreSQL's autovacuum also marks space for reuse without returning it to the system.
VACUUM FULLrewrites a table at its minimum size, but it locks the table and needs room for the copy (routine vacuuming), so stop n8n before you run it.
Check the sizes from the folder with your compose file; the last two commands are for PostgreSQL only:
sudo docker system df -v # "Local Volumes" lists each volume's size
set -a && . ./.env && set +a # loads POSTGRES_USER and POSTGRES_DB into your shell
sudo docker compose exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT pg_size_pretty(pg_database_size(current_database())) AS total, pg_size_pretty(pg_total_relation_size('execution_data')) AS execution_data;"
Move From SQLite to PostgreSQL
These steps assume a Docker Compose install with the service called n8n and its .n8n volume mounted at /home/node/.n8n, as in n8n's examples. They work the same on Ubuntu 24.04 and Debian 12. The command line docs describe export:entities and import:entities for this job: export from one database type, import into another. Both still log "This feature is currently under development", so take the backup in Step 1.
Step 1: Check the Version and Back Up
cd ~/n8n # the folder with your compose file
sudo docker compose exec -u node n8n n8n --version
sudo docker compose exec -u node n8n n8n export:entities --help
If n8n doesn't know the command, update first, as a separate job; n8n 2.45.7, the stable release on 30 October 2026, has it. If your image tag says latest or nothing, pin the version you just printed, as our n8n update guide shows: the import only accepts an export from the same database migration state, so both steps need the same version. Then stop n8n and copy the .n8n volume as in Step 3 of the backup guide.
Step 2: Export From SQLite
sudo docker compose stop
sudo docker compose run --rm --no-deps n8n export:entities --outputDir=/home/node/.n8n/db-export
docker compose run starts a one-off container with the service's configuration and volumes but without its ports; --no-deps leaves other services stopped, --rm removes the container afterwards, and the image's entrypoint passes the arguments to n8n.
The export code reads each table in pages of 500 rows, not as one snapshot, so keep n8n stopped while it runs. It encrypts the rows with your instance's key and leaves entities.zip in that folder, inside the volume, where the import finds it. It deletes the output folder before writing, so never point --outputDir at /home/node/.n8n itself. And it skips execution history unless you add --includeExecutionHistoryDataTables=true.
Step 3: Add PostgreSQL to the Compose File
Add three lines to .env, with a long random password of your own:
POSTGRES_USER=n8n
POSTGRES_PASSWORD=change-me
POSTGRES_DB=n8n
Add the database service and its volume from n8n's Compose guide, merging volumes: with any you already have:
volumes:
db-storage:
services:
postgres:
image: postgres:18
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
PGDATA: /var/lib/postgresql/data
volumes:
- db-storage:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
Then add these to the n8n service, merging with its environment: and depends_on: if it has them:
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}
depends_on:
postgres:
condition: service_healthy
Keep the PGDATA line: Postgres 18 moved its default data folder, and n8n warns that without the line "your database starts empty". The missing ports: entry is deliberate: n8n reaches the database by its service name on the project's network (Compose networking), and nothing outside the server needs port 5432.
Step 4: Start PostgreSQL and Import
sudo docker compose up -d --wait postgres
sudo docker compose run --rm --no-deps n8n import:entities --inputDir=/home/node/.n8n/db-export --truncateTables=true
The import runs with the same volume, so the same key decrypts the export. Like other n8n commands that use the database, it first runs n8n's migrations, which create the tables in the empty database. The import code then checks that the newest migration in the export matches the database's and loads all rows in one transaction. --truncateTables matters: those migrations already wrote a placeholder owner and settings rows, and without the flag n8n logs "Not all tables are empty, skipping import" and stops without an error. Look for "Migration validation passed" and "Import completed successfully" in the output.
Step 5: Start n8n and Test
sudo docker compose up -d
curl -i http://127.0.0.1:5678/healthz/readiness
Status 200 means the database is "connected and migrated" (monitoring docs); the curl assumes port 5678 is published on the host, as in n8n's examples. Log in with your usual account. If n8n asks you to set up an owner account, it is looking at an empty database: check the DB_ variables and the import output. Open a few workflows, run one node that uses a saved credential, and check that your active workflows are still active.
database.sqlite stays in the volume, and n8n ignores it while DB_TYPE is postgresdb. To roll back, run sudo docker compose stop, remove the DB_ lines (DB_TYPE defaults to sqlite) and start again; anything that ran on PostgreSQL in between stays there. Once you're sure, delete the export, which holds a copy of all your data:
sudo docker compose exec -u node n8n rm -r /home/node/.n8n/db-export
From now on, back up both the database and the .n8n volume, which still holds the encryption key.
If the Import Fails
"Cannot import data from different migration states." The export and the import ran different n8n versions, for example because a newer image arrived in between. Run Step 2 again with the image the import used, then Step 4.
entities.zip file not found in The --inputDir doesn't match the --outputDir of the export.
permission denied to set parameter "session_replication_role" The import switches off foreign key checks with that setting, which only superusers or roles with the SET privilege may change (PostgreSQL docs). The image makes POSTGRES_USER a superuser, but the separate n8n role in n8n's withPostgres example isn't one. Grant it for the import, and run the same command with REVOKE ... FROM afterwards:
set -a && . ./.env && set +a
sudo docker compose exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "GRANT SET ON PARAMETER session_replication_role TO $POSTGRES_NON_ROOT_USER;"
Which One Fits
- Stay on SQLite if one n8n container serves you and a few colleagues, you plan no workers, and a short stop for nightly backups is fine.
- Move to PostgreSQL when you add queue mode workers, when more than a handful of people or round-the-clock workflows rely on n8n, or when backups must run without downtime.
- Start new production installs on PostgreSQL. n8n's Compose guide says to "swap in Postgres" for a production instance, and you skip the migration later.
On both, keep pruning on and watch the disk. n8n and PostgreSQL fit on one server: a Space-Node VPS for n8n and AI agents is KVM with full root access and NVMe storage, so Docker Compose and every command in this guide work, and you can upgrade to a bigger plan from the client area when the data grows. For sizing, see our n8n requirements guide.
FAQ
Can n8n still use MySQL or MariaDB?
Not since n8n 2.0. If an instance still runs on one, move it while it's on 1.x: in n8n 1.123 export:entities still reads MySQL and MariaDB, and import:entities writes to PostgreSQL or SQLite. Upgrade after that.
Which PostgreSQL version should I pick?
18 for a new install, as in n8n's examples; 16 and 17 are supported too. Don't raise the major version of an existing Postgres container by changing its tag: Postgres can't open a data directory written by an older major and stops with database files are incompatible with server. Dump and restore instead, as our PostgreSQL backup guide shows.
Do I lose execution history when I migrate?
By default, yes: the export skips the execution tables because "they can be very large". Add --includeExecutionHistoryDataTables=true in Step 2 to keep them. With default pruning that's at most 14 days or 10,000 executions, plus any annotated ones.
Does n8n on PostgreSQL also need Redis? Only in queue mode, where Redis is the message broker between the main instance and the workers. A single n8n instance talks to PostgreSQL directly.
Can I go back to SQLite later?
Yes. import:entities accepts SQLite as a target too, so the same two commands work in the other direction. Shortly after the move, the rollback in Step 5 is quicker.
Sources
All opened on 30 October 2026:
- n8n docs: Choose n8n's database, Database variables, Install using Docker Compose, Use the command line, Enable queue mode, Manage execution data, Executions variables, Understand the database, Back up and restore, Monitor n8n, User management, v2.0 breaking changes, v3.0 breaking changes
- n8n on GitHub: releases, export:entities, import:entities, export service, import service, base command, cipher, database config, CreateUserManagement migration, Docker entrypoint, database types in 1.123.91, n8n-hosting withPostgres
- SQLite: Appropriate uses, Write-ahead logging, VACUUM
- PostgreSQL 18 docs: pg_dump, Routine vacuuming, Size functions, current_database, session_replication_role, GRANT, REVOKE, Debian downloads
- Packages: Debian 12 postgresql, Ubuntu 24.04 postgresql, postgres Docker image docs
- Docker docs: docker compose run, docker compose up, docker compose exec, docker system df, Compose networking, Compose services: environment