SnowLuma
Deployment

Migrate Docker persistent data

Safely move an existing Docker deployment to the current persistent storage layout

The current image uses a consistent persistent storage layout. Docker does not copy data between named volumes automatically, so an existing deployment must be migrated once when moving from the previous layout.

This procedure preserves SnowLuma and OneBot configuration, the QQ login state, and local QQ data. It does not delete the previous volumes.

Before you start

  • Keep the previous container. Do not run docker compose down or remove it yet.
  • Never run docker compose down -v; it removes Compose-managed volumes.
  • The container must be stopped during the copy, so allow for a short interruption.
  • Keep enough free disk space for the old and new data to coexist during migration.

1. Identify the existing container

The commands below use the default container name, snowluma. Change the first line if you use another name.

CONTAINER=snowluma
docker inspect "$CONTAINER" >/dev/null
docker inspect "$CONTAINER" \
  --format '{{range .Mounts}}{{printf "%-24s %-8s %s\n" .Destination .Type .Name}}{{end}}'

The output must include these container paths:

/app/snowluma-data
/app/.config
/app/.local/share

Stop if any path is missing and check the existing deployment configuration.

2. Stop the container

Stopping the container prevents configuration databases or QQ login data from changing during the copy.

docker stop "$CONTAINER"

Do not remove the container. The copy command reads its existing mounts directly, including volume names generated by Compose.

3. Create the current volumes

docker volume create qq-gateway-data
docker volume create qq-client-config
docker volume create qq-client-data

Confirm that every destination volume is empty:

docker run --rm \
  -v qq-gateway-data:/check/gateway \
  -v qq-client-config:/check/config \
  -v qq-client-data:/check/share \
  alpine:3.21 sh -ec '
    for path in /check/gateway /check/config /check/share; do
      [ -z "$(ls -A "$path")" ] || {
        echo "Destination volume is not empty: $path" >&2
        exit 1
      }
    done
  '

If this fails, do not overwrite the destination. First determine whether it belongs to an earlier incomplete migration.

4. Copy and verify the data

The following command mounts the previous container data read-only, copies it into the current volumes, and verifies every regular file. It may download the small alpine image on first use.

docker run --rm \
  --volumes-from "${CONTAINER}:ro" \
  -v qq-gateway-data:/migration/gateway \
  -v qq-client-config:/migration/config \
  -v qq-client-data:/migration/share \
  alpine:3.21 sh -ec '
    copy_tree() {
      source_path="$1"
      target_path="$2"
      label="$3"

      [ -d "$source_path" ] || {
        echo "Missing source directory: $source_path" >&2
        exit 1
      }

      (cd "$source_path" && tar -cf - .) |
        (cd "$target_path" && tar -xpf -)

      (cd "$source_path" && find . -type f -exec sha256sum {} \; | sort) > "/tmp/${label}.source"
      (cd "$target_path" && find . -type f -exec sha256sum {} \; | sort) > "/tmp/${label}.target"
      cmp "/tmp/${label}.source" "/tmp/${label}.target"
      echo "Verified: $label"
    }

    copy_tree /app/snowluma-data /migration/gateway gateway
    copy_tree /app/.config /migration/config config
    copy_tree /app/.local/share /migration/share share
  '

The command must print:

Verified: gateway
Verified: config
Verified: share

If any step fails, stop the migration. The previous data remains in its original volumes.

5. Update the Compose configuration

Use the current mounts and explicit volume names:

services:
  snowluma:
    volumes:
      - qq-gateway-data:/app/data
      - qq-client-config:/app/.config
      - qq-client-data:/app/.local/share

volumes:
  qq-gateway-data:
    name: qq-gateway-data
  qq-client-config:
    name: qq-client-config
  qq-client-data:
    name: qq-client-data

Pull and start the current image:

docker compose pull
docker compose up -d

For a docker run deployment, remove the stopped container, recreate it with the same ports and environment, and use these mounts:

-v qq-gateway-data:/app/data \
-v qq-client-config:/app/.config \
-v qq-client-data:/app/.local/share

6. Verify the migration

Confirm each item before considering the migration complete:

  1. The container remains running without repeated startup errors.
  2. The WebUI accepts the existing credentials and shows the previous settings.
  3. QQ keeps its login state without requiring another QR login.
  4. OneBot listeners, tokens, and bot connections match the previous deployment.
  5. A test message can be received and sent successfully.
docker ps --filter "name=${CONTAINER}"
docker logs --tail 100 "$CONTAINER"

Keep a rollback path

Keep the previous Compose file and volumes until the service has remained healthy through at least one container restart. To roll back, stop the current container, restore the previous Compose file and image version, and start it again.

This procedure never deletes the previous volumes.

Bind mounts and multiple accounts

Host directories

If step 1 reports bind mounts, the data already lives in host directories and does not need to be copied into named volumes. Keep the host source directory and change only the application-data destination from /app/snowluma-data to /app/data. The two QQ destinations remain unchanged.

Multiple accounts

Additional QQ account destinations remain /app/qq-acct2, /app/qq-acct3, and so on. To rename those volumes as well, create one new volume per account, repeat the copy and verification process, and keep each account on its own complete HOME.

Was this page helpful?

Last updated on

On this page