Migrate from the All-in-One Container
How to migrate your data from the legacy surfsense all-in-one Docker image to the current multi-container setup
The original SurfSense all-in-one image (ghcr.io/modsetter/surfsense:latest, run via docker-compose.quickstart.yml) stored all data — PostgreSQL, Redis, and configuration — in a single Docker volume named surfsense-data. The current setup uses separate named volumes and has upgraded PostgreSQL from version 14 to 17.
Because PostgreSQL data files are not compatible between major versions, a logical dump and restore is required. This is a one-time migration.
This guide only applies to users who ran the legacy docker-compose.quickstart.yml (the all-in-one surfsense container). If you were already using docker/docker-compose.yml, you do not need to migrate.
Option A — One command (recommended)
install.sh detects the legacy surfsense-data volume and handles the full migration automatically — no separate migration script needed. Just run the same install command you would use for a fresh install:
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/install.sh | bashWhat it does automatically:
- Downloads all SurfSense files (including
migrate-database.sh) into./surfsense/ - Detects the
surfsense-datavolume and enters migration mode - Stops the old all-in-one container if it is still running
- Starts a temporary PostgreSQL 14 container and dumps your database
- Recovers your
SECRET_KEYfrom the old volume - Starts PostgreSQL 17, restores the dump, runs a smoke test
- Starts all services
Your original surfsense-data volume is never deleted — you remove it manually after verifying.
After it completes
- Open http://localhost:3000 and confirm your data is intact.
- Once satisfied, remove the old volume (irreversible):
docker volume rm surfsense-data - Delete the dump file once you no longer need it as a backup:
rm ./surfsense_migration_backup.sql
If the migration fails mid-way
The dump file is saved to ./surfsense_migration_backup.sql as a checkpoint. Simply re-run install.sh — it will detect the existing dump and skip straight to the restore step without re-extracting.
Option B — Manual migration script (custom credentials)
If you launched the old all-in-one container with custom database credentials (POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB environment variables), the automatic path will use wrong credentials. Run migrate-database.sh manually first:
# 1. Extract data with your custom credentials
bash ./surfsense/scripts/migrate-database.sh --db-user myuser --db-password mypass --db-name mydb
# 2. Install and restore (detects the dump automatically)
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/install.sh | bashOr download and run if you haven't run install.sh yet:
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/migrate-database.sh -o migrate-database.sh
bash migrate-database.sh --db-user myuser --db-password mypass --db-name mydbMigration script options
| Flag | Description | Default |
|---|---|---|
--db-user USER | Old PostgreSQL username | surfsense |
--db-password PASS | Old PostgreSQL password | surfsense |
--db-name NAME | Old PostgreSQL database | surfsense |
--yes / -y | Skip confirmation prompts (used automatically by install.sh) | — |
Option C — Manual steps
For users who prefer full control or whose platform doesn't support bash scripts (e.g. Windows without WSL2).
Step 1 — Stop the old all-in-one container
Before mounting the surfsense-data volume into a new container, stop the existing one to prevent two PostgreSQL processes from writing to the same data directory:
docker stop surfsense 2>/dev/null || trueStep 2 — Start a temporary PostgreSQL 14 container
docker run -d --name surfsense-pg14-temp \
-v surfsense-data:/data \
-e PGDATA=/data/postgres \
-e POSTGRES_USER=surfsense \
-e POSTGRES_PASSWORD=surfsense \
-e POSTGRES_DB=surfsense \
pgvector/pgvector:pg14Wait ~10 seconds, then confirm it is healthy:
docker exec surfsense-pg14-temp pg_isready -U surfsenseStep 3 — Dump the database
docker exec -e PGPASSWORD=surfsense surfsense-pg14-temp \
pg_dump -U surfsense surfsense > surfsense_backup.sqlStep 4 — Recover your SECRET_KEY
docker run --rm -v surfsense-data:/data alpine cat /data/.secret_keyStep 5 — Set up the new stack
mkdir -p surfsense/scripts
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/docker-compose.yml -o surfsense/docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/.env.example -o surfsense/.env.example
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/postgresql.conf -o surfsense/postgresql.conf
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/init-electric-user.sh -o surfsense/scripts/init-electric-user.sh
chmod +x surfsense/scripts/init-electric-user.sh
cp surfsense/.env.example surfsense/.envSet SECRET_KEY in surfsense/.env to the value from Step 4.
Step 6 — Start PostgreSQL 17 and restore
cd surfsense
docker compose up -d db
docker compose exec db pg_isready -U surfsense # wait until ready
docker compose exec -T db psql -U surfsense -d surfsense < ../surfsense_backup.sqlStep 7 — Start all services
docker compose up -dStep 8 — Clean up
docker stop surfsense-pg14-temp && docker rm surfsense-pg14-temp
docker volume rm surfsense-data # only after verifying migration succeededTroubleshooting
install.sh runs normally with a blank database (no migration happened)
The legacy volume was not detected. Confirm it exists:
docker volume ls | grep surfsense-dataIf it doesn't appear, the old container may have used a different volume name. Check with:
docker volume ls | grep -i surfsenseExtraction fails with permission errors
The script detects the UID of the data files and runs the temporary PG14 container as that user. If you see permission errors in ./surfsense-migration.log, run migrate-database.sh manually and check the log for details.
Cannot find /data/.secret_key
The all-in-one entrypoint always writes the key to /data/.secret_key unless you explicitly set SECRET_KEY= as an environment variable. If the key is missing, the migration script auto-generates a new one (with a warning). You can update it manually in ./surfsense/.env afterwards. Note that a new key invalidates all existing browser sessions — users will need to log in again.
Restore errors after re-running install.sh
If surfsense-postgres volume already exists from a previous partial run, remove it before retrying:
docker volume rm surfsense-postgres