---
title: Migrate from the All-in-One Container
description: 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.

<Callout type="warn">
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.
</Callout>

---

## 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:

```bash
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/install.sh | bash
```

**What it does automatically:**

1. Downloads all SurfSense files (including `migrate-database.sh`) into `./surfsense/`
2. Detects the `surfsense-data` volume and enters migration mode
3. Stops the old all-in-one container if it is still running
4. Starts a temporary PostgreSQL 14 container and dumps your database
5. Recovers your `SECRET_KEY` from the old volume
6. Starts PostgreSQL 17, restores the dump, runs a smoke test
7. Starts all services

Your original `surfsense-data` volume is **never deleted** — you remove it manually after verifying.

### After it completes

1. Open [http://localhost:3000](http://localhost:3000) and confirm your data is intact.
2. Once satisfied, remove the old volume (irreversible):
   ```bash
   docker volume rm surfsense-data
   ```
3. Delete the dump file once you no longer need it as a backup:
   ```bash
   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:

```bash
# 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 | bash
```

Or download and run if you haven't run `install.sh` yet:

```bash
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 mydb
```

### Migration 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 — Start a temporary PostgreSQL 14 container

```bash
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:pg14
```

Wait ~10 seconds, then confirm it is healthy:

```bash
docker exec surfsense-pg14-temp pg_isready -U surfsense
```

### Step 2 — Dump the database

```bash
docker exec -e PGPASSWORD=surfsense surfsense-pg14-temp \
  pg_dump -U surfsense surfsense > surfsense_backup.sql
```

### Step 3 — Recover your SECRET\_KEY

```bash
docker run --rm -v surfsense-data:/data alpine cat /data/.secret_key
```

### Step 4 — Set up the new stack

```bash
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/.env
```

Set `SECRET_KEY` in `surfsense/.env` to the value from Step 3.

### Step 5 — Start PostgreSQL 17 and restore

```bash
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.sql
```

### Step 6 — Start all services

```bash
docker compose up -d
```

### Step 7 — Clean up

```bash
docker stop surfsense-pg14-temp && docker rm surfsense-pg14-temp
docker volume rm surfsense-data   # only after verifying migration succeeded
```

---

## Troubleshooting

### `install.sh` runs normally with a blank database (no migration happened)

The legacy volume was not detected. Confirm it exists:

```bash
docker volume ls | grep surfsense-data
```

If it doesn't appear, the old container may have used a different volume name. Check with:

```bash
docker volume ls | grep -i surfsense
```

### Extraction 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:

```bash
docker volume rm surfsense-postgres
```
