Skip to content

Back up and restore

The volume names have the prefix of the Compose project, mcp-platform_ by default (the value of COMPOSE_PROJECT_NAME if you set it). List them with docker volume ls.

Volume Content Back up
secrets The internal CA and certificates, every generated password and key, the OpenBao AppRole yes
openbao-keys The OpenBao unseal key and the provisioner token yes
openbao-data OpenBao storage (Raft): users’ stored credentials, OAuth client secrets, Transit keys yes, as a snapshot
keycloak-db Keycloak database: realm, users, OTP yes, as a dump
gateway-db Gateway database: servers, approvals, scope mapping, overrides, audit yes, as a dump
broker-db Broker database: systems, connections yes, as a dump
gateway-redis, gateway-redis-replica, broker-redis Short-lived state: MCP client sessions (1 h idle), pending confirmations of destructive tools (about 4 min), rate-limit counters (1 min), account connections in progress (10 min), locks and replay markers no: start them empty

Outside the volumes, keep .env, certs/ (including tls.key) and servers.yaml. Service account keys that you wrote to OpenBao with bao kv put are in openbao-data.

Run these commands in the directory of compose.yaml, with the platform running.

  1. Dump the three databases:

    Terminal window
    for db in gateway-postgres:gateway keycloak-postgres:keycloak broker-postgres:broker; do
    docker compose exec -T "${db%%:*}" pg_dump -U "${db##*:}" "${db##*:}" > "${db##*:}.sql"
    done
  2. Save a snapshot of OpenBao:

    Terminal window
    docker compose exec -e BAO_CACERT=/trust/ca.crt openbao-unseal sh -c \
    'BAO_TOKEN=$(cat /keys/provisioner-token) bao operator raft snapshot save /tmp/openbao.snap && cat /tmp/openbao.snap' > openbao.snap
  3. Pack the secrets and openbao-keys volumes:

    Terminal window
    docker run --rm -v mcp-platform_secrets:/s:ro -v mcp-platform_openbao-keys:/k:ro -v "$PWD":/b alpine tar czf /b/secrets.tgz -C / s k
  4. Copy gateway.sql, keycloak.sql, broker.sql, openbao.snap, secrets.tgz, .env and certs/ to the backup location.

Restore onto the bundle of the same version, on the same host or a new one. Replace the volume prefix if your project name differs.

  1. If the platform already runs on this host, delete it together with its volumes:

    Terminal window
    docker compose down -v
  2. Restore the two secret volumes from the tarball:

    Terminal window
    docker volume create mcp-platform_secrets
    docker volume create mcp-platform_openbao-keys
    docker run --rm -v mcp-platform_secrets:/s -v mcp-platform_openbao-keys:/k -v "$PWD":/b alpine tar xzf /b/secrets.tgz -C /
  3. Put .env and certs/ in place.

  4. Start the databases, then load the dumps into them:

    Terminal window
    docker compose up -d keycloak-postgres gateway-postgres broker-postgres
    docker compose exec -T gateway-postgres psql -U gateway gateway < gateway.sql
    docker compose exec -T keycloak-postgres psql -U keycloak keycloak < keycloak.sql
    docker compose exec -T broker-postgres psql -U broker broker < broker.sql

    If the services need a moment, wait until docker compose ps shows the three databases healthy before you load.

  1. Start OpenBao alone. Do not start openbao-unseal yet: with the restored key it refuses to initialise an empty server, and everything that waits for it stays down.

    Terminal window
    docker compose up -d openbao
  2. Load the snapshot. Initialise the empty server once with a throwaway key, unseal it with that key, restore the snapshot and restart OpenBao:

    Terminal window
    docker compose exec openbao bao operator init -key-shares=1 -key-threshold=1
    docker compose exec openbao bao operator unseal # paste "Unseal Key 1" from the init output
    docker compose exec -T openbao sh -c 'cat > /tmp/openbao.snap' < openbao.snap
    read -rs BAO_TOKEN && export BAO_TOKEN # paste "Initial Root Token" from the init output
    docker compose exec -e BAO_TOKEN openbao bao operator raft snapshot restore -force /tmp/openbao.snap
    unset BAO_TOKEN
    docker compose exec openbao rm -f /tmp/openbao.snap
    docker compose restart openbao

    The snapshot replaces the throwaway key and root token. From now on OpenBao opens with the restored key from openbao-keys. Use a snapshot younger than 32 days: the provisioner token in it expires after 768 hours without renewal, and without it openbao-unseal cannot provision OpenBao.

  3. Start the rest:

    Terminal window
    docker compose up -d

    docker compose logs openbao-unseal ends with openbao-unseal: unsealed and provisioned.

  1. docker compose ps shows every service healthy.
  2. You can sign in to the portal, and the servers appear in Administration.
  3. A user who connected an account before the backup can call a tool without connecting again. If users get credential_required, the OpenBao data was not restored.

openbao-keys lets anyone who can read Docker volumes on the host unseal OpenBao. To keep the key elsewhere, back up openbao-keys, then remove it from the host:

Terminal window
docker compose stop openbao-unseal
docker volume rm mcp-platform_openbao-keys

After every start, unseal by hand with bao operator unseal from a shell on the secrets network. The broker does not start until OpenBao is unsealed.