Skip to main content
Teramont Logo
How to Update n8n in Docker Without Losing Workflows or Credentials
Back to Blog

How to Update n8n in Docker Without Losing Workflows or Credentials

Mizael Segovia

8/10/2026 ·Mizael Segovia· 18 min read ·

0 views

Direct answer: you can update self-hosted n8n Docker without losing workflows when the replacement container connects to the exact same database, persistent volume or bind mount, and encryption key. A controlled update does not begin by assuming n8n erased data: inventory the live state, create a consistent backup, pin a stable version, deploy it, and validate the instance before retiring the restore point.

The official n8n database documentation says SQLite stores credentials, execution history, and workflows by default, while PostgreSQL is also supported. The recoverable unit is therefore not just the container image. It is the database, the persistent storage, and the original N8N_ENCRYPTION_KEY or configuration file that holds that key.

The short version: a seven-step safe update

  1. Inventory the Compose project, service names, image, current version, database, and actual mounts.
  2. Identify where N8N_ENCRYPTION_KEY comes from without displaying its value.
  3. Schedule a maintenance window and stop every n8n process that can write data.
  4. Archive the volume or bind mount and take a consistent SQLite backup or PostgreSQL pg_dump.
  5. Pin one verified stable release instead of relying on a floating tag.
  6. Run docker compose pull and docker compose up -d, then test container state, logs, HTTP, and real workflows.
  7. Keep the previous version and backup until testing is complete. If the update fails, restore the pre-update database before starting the older image whenever schema compatibility is uncertain.

The commands below are documented, adaptable procedures. Replace every placeholder and verify the names, paths, authentication method, and Compose context in your environment. They are not presented as commands run by Teramont.

When to update—and when to wait

n8n recommends staying current, reading the release notes, and trying to update at least once a month. That is a maintenance cadence, not a reason to deploy every new tag directly to production.

Proceed in a controlled windowWait and resolve this first
The target is marked stable and you reviewed the changes that affect your setup.The target is a pre-release or contains breaking changes you have not tested.
You have a consistent backup, the encryption key source, and a tested restore path.You cannot prove which volume, database, or key the current instance uses.
The host has room for the backup, new image, and a separate restored copy.Disk space is tight, the database is unhealthy, or you cannot preserve two states.
You can pause or drain executions, queues, schedules, and webhook traffic.Long-running or waiting executions and peak traffic cannot be interrupted safely.

As a time-stamped example, n8n GitHub Releases listed 2.31.6 as the stable release published July 24, 2026, while 2.32.5 was a pre-release. Recheck the latest stable release immediately before your change window; do not treat that number as a permanent recommendation.

SQLite versus PostgreSQL: what must survive

Backup matrix for a routine n8n update
AreaSQLitePostgreSQL
How to identify itDB_TYPE is absent or selects SQLite; verify the actual database path and mount.DB_TYPE=postgresdb plus DB_POSTGRESDB_* settings; identify host, database, user, and schema without printing passwords.
Primary dataThe SQLite file—commonly under /home/node/.n8n unless configured otherwise—and any journal or WAL state.A PostgreSQL database in another Compose service or on an external server.
Consistent backupStop n8n before archiving the mount, or use an explicitly consistent SQLite backup method. A copy of a live file is not a guarantee.pg_dump takes a consistent snapshot; stopping n8n writers before the final dump gives you a clean cutover point with no later writes.
Also preserveThe config or key source, filesystem binary data, and community nodes when stored in the mount.The key source, n8n mount, filesystem binary data, and community nodes; they are not part of the database dump.
Conservative rollbackThe previous image plus a new volume or directory restored from the pre-update archive.The previous image plus a new database restored from the pre-update dump and the same key.

A routine update does not require moving from SQLite to PostgreSQL. n8n supports both. Treat a database-engine migration as a separate change driven by operational requirements, not as an automatic prerequisite for upgrading the application.

Prerequisites before touching production

  • Access to the production Compose directory and the exact Compose and .env files it uses.
  • Permission to inspect mounts, stop services, create backups, and restore into separate resources.
  • Free space for the backup, target image, and a restored copy without deleting the failed state.
  • The current version, target stable version, and every relevant release note between them.
  • A test list covering the UI, representative workflows, credentials, webhooks, schedules, workers, and task runners where applicable.
  • A communicated maintenance window and explicit go-forward versus rollback criteria.

In queue mode or a multi-worker deployment, identify and drain every process that can write; stopping only the main service is not enough. Add custom binary-data paths, object storage, extra mounts, and community nodes to the inventory.

1. Inventory the real instance and mount

Run the inventory from the production directory with the same -f, --env-file, and project-name options used in normal operations. A different Compose project name or working directory can generate different volume names. The official n8n Docker Compose guide persists /home/node/.n8n, but you must confirm your effective source rather than assume it.

cd /path/to/the-n8n-project
docker compose config -q
docker compose config --services
docker compose config --images
docker compose config --volumes
docker compose ps
docker compose exec -T <N8N_SERVICE> n8n --version
docker inspect "$(docker compose ps -q <N8N_SERVICE>)" --format '{{range .Mounts}}{{println .Type .Name .Source "->" .Destination}}{{end}}'
docker compose exec -T <N8N_SERVICE> sh -lc 'echo "DB_TYPE=${DB_TYPE:-sqlite}"'
docker volume inspect <N8N_DATA_VOLUME>

Expected result: Compose validates without printing the full resolved configuration; you recognize the correct service, current version, and a persistent mount targeting the intended path. docker compose exec takes a Compose service name. docker inspect takes a container ID or name. Do not confuse them.

Also record the SQLite path or, for PostgreSQL, the Compose database service and logical database name. For external PostgreSQL, record the endpoint and authentication mechanism without copying passwords into tickets or logs.

Locate the encryption key without revealing it

n8n documents N8N_ENCRYPTION_KEY as the key used to encrypt credentials. When no custom key is supplied, n8n generates one at first launch and saves it in the configuration directory. The database may still contain workflows without that key, but credentials cannot be decrypted correctly.

docker compose exec -T <N8N_SERVICE> sh -lc 'if [ -n "$N8N_ENCRYPTION_KEY" ]; then echo "N8N_ENCRYPTION_KEY is present in the container environment"; else echo "No environment key detected; verify the persisted /home/node/.n8n/config file"; fi'
docker compose exec -T <N8N_SERVICE> sh -lc 'if [ -s /home/node/.n8n/config ]; then echo "A persisted configuration file exists"; else echo "No configuration file was found at the usual path"; fi'

These checks report presence only. Do not run printenv, env, a full environment docker inspect, or unfiltered docker compose config output in a recorded or shared terminal: those can disclose secrets. Back up the real key source—restricted .env, managed secret, or persisted configuration file—without printing it.

2. Build the restore point

Create a private backup directory and record the current version, effective image list, and declarative files. Replace <COMPOSE_FILE> with the actual filename. An .env copy may contain secrets, so restrict it, encrypt the backup at rest, and move a verified copy off the host.

export BACKUP_DIR="$PWD/backups/n8n-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
docker compose exec -T <N8N_SERVICE> n8n --version > "$BACKUP_DIR/n8n-version-before.txt"
docker compose config --images > "$BACKUP_DIR/images-before.txt"
install -m 600 <COMPOSE_FILE> "$BACKUP_DIR/compose-before.yaml"
if [ -f .env ]; then install -m 600 .env "$BACKUP_DIR/environment-before.backup"; fi

CLI exports are an extra layer, not the whole backup

The official n8n CLI documentation supports workflow and credential exports. Use them as a second, readable or versionable layer before shutdown, not as a substitute for the database, key, and files. Normal credential exports remain encrypted. Avoid --decrypted by default because it writes secrets in plaintext.

docker compose exec -T <N8N_SERVICE> n8n export:workflow --backup --output=/home/node/.n8n/cli-backup/workflows/
docker compose exec -T <N8N_SERVICE> n8n export:credentials --backup --output=/home/node/.n8n/cli-backup/credentials/

Expected result: export files are created without displaying credential values. Include that directory in the mount archive. Exports retain entity IDs, so a later import can overwrite objects with matching IDs.

3. Stop writers and back up persistent storage

Explicitly stop the main n8n service and every worker or writer. The examples use <N8N_SERVICE> <N8N_WORKER_SERVICE>: remove the second placeholder when there are no workers, or append every service name when there are several. Keep <POSTGRES_SERVICE> running for pg_dump; do not include it in the stop command. For a named volume, this example uses a pinned helper image; make it available before the maintenance window and replace all placeholders.

docker compose stop <N8N_SERVICE> <N8N_WORKER_SERVICE>
docker run --rm -v <N8N_DATA_VOLUME>:/source:ro -v "$BACKUP_DIR":/backup alpine:3.22 sh -c 'cd /source && tar -czf /backup/n8n-data.tgz .'
tar -tzf "$BACKUP_DIR/n8n-data.tgz" > /dev/null
test -s "$BACKUP_DIR/n8n-data.tgz"

For a bind mount, archive the source path returned by docker inspect after stopping the main service and every worker. $BACKUP_DIR must be outside <N8N_BIND_SOURCE>: if it is inside, abort and redefine or move the backup directory before continuing so the tar archive cannot try to include itself.

docker compose stop <N8N_SERVICE> <N8N_WORKER_SERVICE>
mkdir -p "$BACKUP_DIR"
BIND_SOURCE="$(realpath <N8N_BIND_SOURCE>)"
BACKUP_PATH="$(realpath "$BACKUP_DIR")"
case "$BACKUP_PATH/" in
  "$BIND_SOURCE/"*) echo 'ERROR: BACKUP_DIR is inside N8N_BIND_SOURCE; move it outside before continuing' >&2; exit 1 ;;
esac
tar --numeric-owner -C <N8N_BIND_SOURCE> -czf "$BACKUP_DIR/n8n-bind-data.tgz" .
tar -tzf "$BACKUP_DIR/n8n-bind-data.tgz" > /dev/null
test -s "$BACKUP_DIR/n8n-bind-data.tgz"

If SQLite is in that mount, stopping n8n before the archive avoids treating a live file copy as a consistent backup. If DB_SQLITE_DATABASE points to another mount, archive that source too. A successful tar listing proves that the archive is readable, not that the application can restore; a rehearsal restore is the stronger test.

PostgreSQL backup

With n8n writers stopped, take a custom-format dump. In this example <POSTGRES_SERVICE> is the Compose service name, not container_name. Do not put a password on the command line; use the established authentication method, a correctly protected .pgpass, or a managed secret.

docker compose stop <N8N_SERVICE> <N8N_WORKER_SERVICE>
docker compose exec -T <POSTGRES_SERVICE> pg_dump -U <DB_USER> -d <DB_NAME> --format=custom --no-owner > "$BACKUP_DIR/n8n-postgres.dump"
test -s "$BACKUP_DIR/n8n-postgres.dump"
docker compose exec -T <POSTGRES_SERVICE> pg_restore --list < "$BACKUP_DIR/n8n-postgres.dump" > /dev/null

Expected result: pg_dump exits successfully, the file is non-empty, and pg_restore --list can read it. The PostgreSQL documentation describes the custom format as a flexible archive for restore operations. For an external database, run a compatible pg_dump client from an authorized system and keep passwords out of the command line.

A PostgreSQL dump does not replace the n8n mount archive. An automatically generated key, filesystem binary data, or community nodes may still live there. Preserve both artifacts.

4. Pin the stable release before updating

Keep the image version in a variable or write the tag directly in Compose. The current and target versions must be unambiguous. If you use external task runners, pin a compatible runner image as well and follow the version-specific official instructions.

Keep n8n updates controlled

A well-organized VPS makes it easier to separate persistent data, backups, and image versions so your n8n recovery path stays clear.

Premium Character
Explore VPS Hosting
services:
  n8n:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:
N8N_VERSION=<TARGET_STABLE_VERSION>

Replace the placeholder with the stable release you verified. Retain <PREVIOUS_VERSION> and, when policy requires it, the image digest. Avoid combining the n8n change with a database-engine migration, proxy rewrite, Docker upgrade, and operating-system maintenance; separate changes make failures easier to attribute.

5. Update with Docker Compose

After validating the backup and stopping n8n, validate the model, pull only the n8n images you intend to change, and bring the project up. Add the task-runner service to pull where applicable; avoid turning this into an accidental PostgreSQL upgrade.

docker compose config -q
docker compose pull <N8N_SERVICE>
docker compose up -d
docker compose ps
docker compose logs --since=10m --tail=200 <N8N_SERVICE>
docker compose exec -T <N8N_SERVICE> n8n --version

Expected result: the service is Up or healthy, the reported version matches the target, and logs show no database connection, decryption, or migration failures. Docker documents that docker compose up recreates services when the image or configuration changes while preserving mounted volumes.

Do not use docker compose down -v for a routine update. Docker states that down -v removes declared named volumes and attached anonymous volumes. Do not run volume prune during the maintenance window either.

6. Validate in layers before calling it done

Container state, logs, and HTTP

n8n exposes /healthz and a readiness check by default. Adjust the URL if you changed N8N_ENDPOINT_HEALTH or deploy under a subpath.

set -e
curl --fail --silent --show-error --output /dev/null --max-time 10 https://<N8N_HOST>/healthz
curl --fail --silent --show-error --output /dev/null --max-time 10 https://<N8N_HOST>/healthz/readiness
echo 'HTTP health checks completed successfully'

A successful HTTP response is necessary, not sufficient. The official n8n monitoring endpoints demonstrate technical availability; they do not prove every integration still works.

Functional checklist

TestWhat to confirm
UI and inventoryThe expected workflows, projects, users, and tags appear; you do not see a first-run instance.
CredentialsRun a non-destructive test workflow that uses representative credentials. Do not reveal or export the secret values.
Manual workflowA controlled run completes with the expected result and execution-data policy.
WebhooksA test request reaches the public hostname, passes the proxy and TLS layer, and triggers the correct workflow. If resolution differs from expectations, use this primer on how DNS works.
Schedules and publicationWorkflows that should be published remain published, with a plausible next run.
Queue modeWorkers, Redis, and task runners reconnect, and related images are version-compatible.
ResourcesNo restart loop, disk error, memory pressure, or unusual growth appears during observation.

Decide before the update how long to observe and which failure triggers rollback. Keep the backup through immediate tests and at least one representative webhook or scheduled-workflow cycle.

7. Perform an exact rollback if the update fails

Pinning the older image again may not be enough. A newer release can apply schema migrations, and running an older image against that migrated database may be unsafe. Read the release notes. When backward compatibility is not proven, restore the pre-update state and run the previous image against that restored copy.

Conservative SQLite rollback

Keep the failed volume for diagnosis. Create a new volume, restore the pre-update archive into it, then edit Compose to mount that volume with <PREVIOUS_VERSION>. Stop the main service and every worker before restoring; remove <N8N_WORKER_SERVICE> when there is no worker, or append every service name when there are several. This preserves the only copy of the failed state.

docker compose stop <N8N_SERVICE> <N8N_WORKER_SERVICE>
docker volume create <N8N_RESTORE_VOLUME>
docker run --rm -v <N8N_RESTORE_VOLUME>:/restore -v "$BACKUP_DIR":/backup:ro alpine:3.22 sh -c 'cd /restore && tar -xzf /backup/n8n-data.tgz'
docker volume inspect <N8N_RESTORE_VOLUME>

Set N8N_VERSION=<PREVIOUS_VERSION> and merge this fragment into the existing Compose file. Remove the worker service when it does not apply, or repeat the mount for every worker that needs it; retain all other service settings.

services:
  <N8N_SERVICE>:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    volumes:
      - n8n_restore_data:/home/node/.n8n
  <N8N_WORKER_SERVICE>:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    volumes:
      - n8n_restore_data:/home/node/.n8n

volumes:
  n8n_restore_data:
    external: true
    name: <N8N_RESTORE_VOLUME>

external: true and name: <N8N_RESTORE_VOLUME> force Compose to use the volume you just restored. Without an external name:, Compose can resolve a different project-prefixed volume and n8n may start as an empty instance. Validate the file and only then start the services:

docker compose config -q
docker compose up -d
docker compose ps

For a bind mount, extract the archive into a new directory and point Compose to it; do not extract over the failed directory. Verify that the restored key source matches the backup before startup.

Conservative PostgreSQL rollback

Create a uniquely named empty database, restore the dump, change DB_POSTGRESDB_DATABASE to that database, restore the prior n8n mount where needed, and pin the previous image. Remove <N8N_WORKER_SERVICE> from the command when no worker exists, or append every writer service. Do not stop <POSTGRES_SERVICE>: it must remain available for createdb and pg_restore.

docker compose stop <N8N_SERVICE> <N8N_WORKER_SERVICE>
docker compose exec -T <POSTGRES_SERVICE> createdb -U <DB_ADMIN_USER> --owner=<DB_USER> <RESTORE_DB_NAME>
docker compose exec -T <POSTGRES_SERVICE> pg_restore -U <DB_USER> -d <RESTORE_DB_NAME> --no-owner --exit-on-error < "$BACKUP_DIR/n8n-postgres.dump"
# Now set DB_POSTGRESDB_DATABASE=<RESTORE_DB_NAME>, retain the previous key, and use N8N_VERSION=<PREVIOUS_VERSION>.
docker compose config -q
docker compose up -d
docker compose ps

Run the same validation checklist after rollback. Data created after the backup cutoff will not be in the restored copy, which is why draining writers and recording the cutoff time matters. Do not delete the migrated database or failed volume until the incident is closed.

Common failure modes and safe diagnosis

SymptomLikely area to verifySafe next action
First-run onboarding or no workflowsA different Compose project, .env, mount, database host, or database name.Stop the new instance and compare the saved inventory with docker compose ps, mounts, and non-secret settings. Do not conclude the data was deleted.
Workflows exist, but credentials failA different N8N_ENCRYPTION_KEY or a missing persisted config file.Restore the exact previous key source. Do not generate a replacement or print the old value.
Startup loop or migration errorAn incompatible jump, database permissions, low disk space, or interrupted connectivity.Preserve restricted logs, stop repeated retries, review release notes, and restore if no compatible remediation is known.
UI works, but webhooks failWEBHOOK_URL, reverse proxy, TLS, DNS, or path behavior changed.Test from outside the host and compare the public URL, forwarded headers, and resolution.
An unexpected version startsA floating tag, interpolation from another env file, or the wrong Compose context.Pin the version, run docker compose config --images, and record the effective image.
Workers or Code nodes failWorker or runner versions are incompatible, or instances have different secrets.Verify related image versions and that every instance receives the same encryption key without displaying it.

A monthly maintenance loop that makes the next update easier

  • Review the stable release and change notes at least monthly; use staging first when the impact warrants it.
  • Automate database and persistent-storage backups, monitor failures, and rehearse restores.
  • Version the Compose model, mount map, service names, image version, and secret locations without committing secret values.
  • Keep an off-host copy under a retention policy that matches your requirements.
  • Monitor disk, database growth, retained executions, restarts, and worker health.
  • Keep n8n, PostgreSQL, Docker, operating-system, proxy, and DNS changes in separate windows when practical.
  • Document RTO, RPO, the window owner, and the rollback threshold.

When the VPS itself deserves attention

A safe update needs room for two states, I/O headroom for the backup, and enough observability to diagnose a restore. If the current host cannot retain a second copy or rehearse recovery without exhausting resources, the constraint is bigger than the update command. When assessing VPS hosting, start with measured workload, clearly mapped persistent storage, off-host backups, and a reproducible recovery procedure rather than choosing from guesswork.

Frequently asked questions

Does updating n8n delete workflows?

It should not simply because the image changes when the correct persistent database is reused. If workflows appear “missing,” first verify that the new container did not start against another volume, database, path, or Compose project. Community reports are useful diagnostic signals, not a universal rate or cause.

Is copying the Docker volume enough?

It depends. With SQLite, the volume may contain the database, config, and key, but the copy must be consistent and include custom paths. With PostgreSQL, you also need a database dump. Binary data stored elsewhere needs its own backup policy.

Must I migrate from SQLite to PostgreSQL before updating?

No, not as a general rule. Keep the application update and any database migration separate unless official instructions for your exact release say otherwise.

Can I roll back by changing only the image tag?

Only when you have confirmed that the newer release did not leave the database in a backward-incompatible state. A conservative rollback combines the previous image with the database or volume restored from the pre-update point.

Do CLI exports replace a complete backup?

No. They are a useful additional layer, but they do not replace the full database, encryption key, binary data, and configuration. Avoid decrypted credential exports except in an exceptional, tightly protected procedure.

Conclusion: do not update until you can restore

The important question is not “which command downloads n8n?” but “can I prove which data this instance uses and return it to a known state?” Inventory first, stop writers, back up the database and key, pin a stable release, use pull and up -d, test both health and real automations, and retain a rollback that restores the previous state. If one of those pieces is missing, postpone the window; a delayed update is cheaper than improvised recovery.

Sources

  1. n8n Docs: Update n8n.
  2. n8n Docs: Use Docker Compose.
  3. n8n Docs: Supported databases settings and its current official location.
  4. n8n Docs: Deployment environment variables.
  5. n8n Docs: Use the command line.
  6. n8n on GitHub: Releases.
  7. n8n Docs: Monitor n8n.
  8. Docker Docs: docker compose config.
  9. Docker Docs: docker compose up.
  10. Docker Docs: docker compose down.
  11. Docker Docs: docker volume inspect.
  12. PostgreSQL Docs: pg_dump.
How to Update n8n in Docker Without Losing Workflows or Credentials
Generaln8nDockerCVE-2026-53359Server Administration
Did you like this article?Share it:

About the Author

Mizael Segovia

Mizael Segovia

CEO & Desarrollador Full Stack y DevOps en Teramont Host

Keep exploring related guides, news, and analysis.

CTA Pattern

Need Help with Your Server?

Our team is ready to help with any questions or issues you may have.

Contact Us