Data migration from docker-compose

Instructions for migrating data from an old server deployed from the citeck-community repository (docker-compose) to a new server deployed via Citeck Launcher v2 in server mode.

Intended for an administrator with root access to both servers. All steps are performed manually, with individual commands in the terminal.

Warning

If the installation is customized, commands and login/password values will need to be adjusted for your environment.

Conventions

  • On the source, commands use the docker compose syntax (Compose v2, plugin). If you have the legacy docker-compose (v1) installed, replace docker compose with docker-compose in every command.

  • Stage 2 commands (export) and prerequisites are executed from the citeck-community directory (where docker-compose.yaml resides), otherwise compose will not find the services.

  • On the target, launcher2 manages containers directly via Docker SDK (without compose) — it uses docker exec with real container names (citeck_postgres_default, citeck_mongo_default, citeck_zookeeper_default).

What is migrated and what is not

Migrated:

  • PostgreSQL — user databases (schema + data), selectively per the mapping table.

  • MongoDB — one database ecos-processciteck_eproc.

  • Zookeeper — contents of the version-2/ directory (snapshots + transaction log).

NOT migrated (i.e., out of scope for this guide — technically the data below can also be transferred if needed, but the step-by-step procedure for doing so is not described here):

  • The keycloak database — created on the target by launcher2 itself. On the source, the corresponding database is named ecos_identity (the ecos-identity-app service in community is an older version of Keycloak).

  • Users, roles, and Keycloak clients from the source (including the demo account admin/admin). If they were created manually — recreate them after migration. The admin on the target uses the password generated by the launcher on first startup (shown in the wizard once; re-issue via citeck setup admin-password).

  • RabbitMQ — queues and in-flight messages.

  • Proxy/nginx volumes — logs, Let’s Encrypt certificate cache.

  • Secrets (JWT, OIDC client secret, admin password) — launcher2 generates its own on first installation.

Compatibility matrix

Component

Source

Target

Migration type

PostgreSQL

12.7

17.5

logical dump (pg_dump -F c)

MongoDB

4.0

4.0.2

mongodump --archive

Zookeeper

3.8.2 (Bitnami)

3.9.4 (official)

copy of version-2/ from dataDir

RabbitMQ

skip

Keycloak DB

skip

PostgreSQL undergoes a major version jump (12 → 17). Physical copying of the data directory (volume) will not work — only a logical dump is required.

Database and user mapping

In launcher2, the convention is: db_name == username == password. On the target, user database passwords match the names of those databases.

PostgreSQL

Source DB

Source owner

Target DB

Target owner

ecos_apps

apps

citeck_eapps

citeck_eapps

ecos_uiserv

uiserv

citeck_uiserv

citeck_uiserv

ecos_integrations

integrations

citeck_integrations

citeck_integrations

ecos_model

model

citeck_emodel

citeck_emodel

ecos_notifications

notifications

citeck_notifications

citeck_notifications

ecos_history

history

citeck_history

citeck_history

ecos_process

process

citeck_eproc

citeck_eproc

ecos_camunda

camunda

citeck_camunda

citeck_camunda

ecos_edi

edi

citeck_edi

citeck_edi

Not migrated (ignore on source):

  • ecos_gateway — in launcher2, gateway does not have its own database.

  • ecos_identity — this is the database of the ecos-identity-app service, which in the community setup is Keycloak (just an older version). This aligns with the decision «do not migrate the keycloak database» — source users and roles are lost during migration (see «NOT migrated» in the introduction).

  • keycloak — created on the target by launcher2 itself.

MongoDB

Source DB

Target DB

ecos-process

citeck_eproc

Zookeeper

Source path (host)

Target path (host)

services/ecos-community-demo-data/zookeeper-app/data/version-2/

/opt/citeck/data/runtime/default/volumes/zookeeper2/data/version-2/ (snapshots) + /opt/citeck/data/runtime/default/volumes/zookeeper2/datalog/version-2/ (logs)

Notes:

  • Source (Bitnami) stores both snapshots and the transaction log in a single version-2/ directory.

  • Target (official zookeeper) separates them: data/version-2/ for snapshots and auxiliary files (acceptedEpoch, currentEpoch), datalog/version-2/ for the transaction log. If they are not separated, the target fails with SnapDirContentCheckException.

  • Storage in launcher2 is a bind-mount directory on the host (not a named docker volume). The exact path can be confirmed via docker inspect citeck_zookeeper_default --format '{{(index .Mounts 0).Source}}'.

Prerequisites

0. Back up source BEFORE any actions

This is a safety net in case the upgrade to 2026.1 (prerequisite 1) breaks the old server. The backup is NOT used for migration — only for rolling back the source.

Simplest option (with server shutdown):

docker compose stop
tar -czf citeck-community-backup-$(date +%F).tar.gz \
  services/ecos-community-demo-data/ \
  services/backups/
docker compose start

If the server cannot be stopped — pg_dumpall + mongodump + a hot copy of the zookeeper directory on running containers.

1. Source updated to release 2026.1

This eliminates the risk of schema/changelog desynchronization when loading into launcher2 (also 2026.1). In the citeck-community directory:

git pull
docker compose up -d

Wait until all microservices reach RUNNING / healthy — Liquibase must catch up with the changelog. You can verify with:

docker compose ps

2. Target prepared

citeck-launcher version ≥ 2.x is installed, bundle community:2026.1 or enterprise:2026.1 (depending on your license). First-run details are in Stage 1.

3. Free disk space

  • On source: ≥ data volume × 1.5 (for dumps).

  • On target: same.

4. Docker access

docker ps without sudo or via sudo on both servers.

Stage 1. Preparing the target (launcher2)

Goal of this stage: run the namespace once so that launcher creates the infrastructure (containers + docker volumes), initializes empty databases with users, and generates secrets.

1.1. Install launcher

If not yet installed:

curl -fsSL https://github.com/Citeck/citeck-launcher/releases/latest/download/install.sh | bash

In the TUI wizard, select bundle community:2026.1 or enterprise:2026.1 (matching your license). Write down the generated admin password — it is shown only once. If the password is lost, it can be re-issued after migration via citeck setup admin-password.

1.2. Wait for full startup

citeck status -w

All applications must be in RUNNING status. On a server with 16 GB RAM this takes 5–15 minutes (longer for enterprise). This step is critical: if webapps do not complete Liquibase on the empty databases in time, the import in Stage 4 will load into databases with an incomplete schema.

1.3. Stop all webapps, leaving infrastructure running

The webapp set in community and enterprise differs; no single list is provided.

  1. View the current list:

    citeck status
    
  2. Stop everything that does not belong to infrastructure. Infrastructure containers that remain running for the import: postgres and mongo (zookeeper is also infrastructure, but it will be stopped immediately before loading data in step 4.3).

    List the remaining applications (gateway, eapps, emodel, uiserv, history, notifications, integrations, eproc, transformations, proxy, etc. — as returned by citeck status):

    citeck stop <app1> <app2> <app3> ...
    

    The citeck stop <app> command marks the application as detached: after the next citeck start without arguments it will not start automatically. This ensures that no webapp writes to the database while the restore is in progress.

1.4. Credentials for import

No manual lookup required:

  • PostgreSQL: postgres / postgres (hardcoded in launcher2).

  • MongoDB: the root password is generated by the launcher and read via docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_PASSWORD. Import commands in Stage 4 use this approach.

Stage 2. Exporting from source

All commands in this stage are executed on the old server, from the citeck-community directory. Service names from docker-compose.yaml are used:

  • PostgreSQL — ecos-microservices-postgresql-app

  • MongoDB — mongodb-app

  • Zookeeper — zookeeper-app

Approach: run pg_dump / mongodump via docker compose exec -T, redirecting stdout directly to a file on the host. No intermediate docker cp and no temporary files inside the container.

The working directory for dumps in this guide is ~/citeck-migration/. Create subdirectories in advance:

mkdir -p \
  ~/citeck-migration/postgres \
  ~/citeck-migration/mongo \
  ~/citeck-migration/zookeeper

2.1. PostgreSQL

pg_dump parameters:

  • -F c — custom format (compact, supports parallel restore).

  • --no-owner --no-acl — strips OWNER TO / GRANT statements (target users have different names; ownership will be set by pg_restore --role).

  • --clean --if-exists is NOT used — the target database is cleaned separately with a DROP SCHEMA command.

  • Without -f /tmp/...pg_dump writes to stdout, which we redirect > to a file on the host.

Note

If any of the databases is missing on your source (for example, ecos_edi does not exist in a community-only deployment) — pg_dump will fail with database "<name>" does not exist. Simply skip the corresponding block: the corresponding target database on launcher2 either does not exist or will remain empty and be populated by the webapp’s Liquibase on first startup.

Commands for each database from the mapping table (execute in blocks, one at a time):

ecos_apps:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_apps \
  > ~/citeck-migration/postgres/ecos_apps.dump

ecos_uiserv:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_uiserv \
  > ~/citeck-migration/postgres/ecos_uiserv.dump

ecos_integrations:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_integrations \
  > ~/citeck-migration/postgres/ecos_integrations.dump

ecos_model:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_model \
  > ~/citeck-migration/postgres/ecos_model.dump

ecos_notifications:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_notifications \
  > ~/citeck-migration/postgres/ecos_notifications.dump

ecos_history:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_history \
  > ~/citeck-migration/postgres/ecos_history.dump

ecos_process:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_process \
  > ~/citeck-migration/postgres/ecos_process.dump

ecos_camunda:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_camunda \
  > ~/citeck-migration/postgres/ecos_camunda.dump

ecos_edi:

docker compose exec -T \
  -e PGPASSWORD=postgresstorngpassword \
  ecos-microservices-postgresql-app \
  pg_dump -U postgres -F c --no-owner --no-acl ecos_edi \
  > ~/citeck-migration/postgres/ecos_edi.dump

After execution, ~/citeck-migration/postgres/ should contain 9 files:

ls ~/citeck-migration/postgres/
# ecos_apps.dump  ecos_camunda.dump  ecos_edi.dump  ecos_history.dump
# ecos_integrations.dump  ecos_model.dump  ecos_notifications.dump
# ecos_process.dump  ecos_uiserv.dump

Note

If the postgres password was changed (from the default postgresstorngpassword in services/environments/ecos-microservices-postgresql-app.env) — replace the PGPASSWORD=... value in all commands.

2.2. MongoDB

Dump of a single database ecos-process. Root login/password — from services/environments/mongodb-app.env. If they were changed — update them in the command. --archive without a value = output to stdout.

docker compose exec -T mongodb-app mongodump \
  --username root_user --password root_user_password --authenticationDatabase admin \
  --db ecos-process --archive --gzip \
  > ~/citeck-migration/mongo/ecos-process.archive

After execution, ~/citeck-migration/mongo/ should contain one file ecos-process.archive.

2.3. Zookeeper

Performed on a stopped container to avoid getting a partially written snapshot. The bind-mount directory belongs to the Bitnami zookeeper uid (1001), not your user — so tar is run inside a temporary container to avoid depending on sudo:

docker compose stop zookeeper-app
docker run --rm \
  -v "$(pwd)/services/ecos-community-demo-data/zookeeper-app/data:/src:ro" \
  -v "$HOME/citeck-migration/zookeeper:/dst" \
  alpine sh -c "
    tar -czf /dst/version-2.tar.gz -C /src version-2 && \
    chown $(id -u):$(id -g) /dst/version-2.tar.gz
  "
docker compose start zookeeper-app

The path services/ecos-community-demo-data/zookeeper-app/data is the host bind-mount path from services/zookeeper-app.yaml. Compose resolves relative paths relative to the service file (i.e., services/...), not the repo root. If your deployment has a different layout — specify your own path.

2.4. Final directory structure

~/citeck-migration/
├── postgres/   # 9 .dump файлов
├── mongo/      # 1 .archive
└── zookeeper/  # version-2.tar.gz

Stage 3. Transferring files

Transfer the ~/citeck-migration/ directory (PostgreSQL dumps + Mongo archive + Zookeeper tarball) to the target server using any convenient method: rsync, scp, S3, portable media — your choice.

Place it on the target server at the same path — ~/citeck-migration/. It is important to preserve the three-subdirectory structure (postgres/, mongo/, zookeeper/) — Stage 4 commands reference paths like ~/citeck-migration/postgres/<name>.dump. If files are copied flat into one folder, the import will break.

Stage 4. Loading onto target

All commands — on the new server. Container names for launcher2 in server mode (namespace is always default):

  • PostgreSQL — citeck_postgres_default

  • MongoDB — citeck_mongo_default

  • Zookeeper — citeck_zookeeper_default

4.1. PostgreSQL

For each database pair from the mapping table — three actions:

  1. Copy the dump into the container.

  2. Clear all user schemas in the target database (Liquibase created service objects on first startup; they must be removed, otherwise the restore will have naming conflicts).

  3. Run pg_restore with --role=<target_user> so that object ownership is assigned correctly.

Extensions (pg_trgm, uuid-ossp, etc.) are restored under the postgres user — --role does not affect CREATE EXTENSION.

Command block for each pair (repeat 9 times):

ecos_apps → citeck_eapps:

docker cp ~/citeck-migration/postgres/ecos_apps.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_eapps <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_eapps;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_eapps \
    --no-owner --no-acl --role=citeck_eapps \
    -j 4 /tmp/ecos_apps.dump

docker exec citeck_postgres_default rm /tmp/ecos_apps.dump

ecos_uiserv → citeck_uiserv:

docker cp ~/citeck-migration/postgres/ecos_uiserv.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_uiserv <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_uiserv;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_uiserv \
    --no-owner --no-acl --role=citeck_uiserv \
    -j 4 /tmp/ecos_uiserv.dump

docker exec citeck_postgres_default rm /tmp/ecos_uiserv.dump

ecos_integrations → citeck_integrations:

docker cp ~/citeck-migration/postgres/ecos_integrations.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_integrations <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_integrations;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_integrations \
    --no-owner --no-acl --role=citeck_integrations \
    -j 4 /tmp/ecos_integrations.dump

docker exec citeck_postgres_default rm /tmp/ecos_integrations.dump

ecos_model → citeck_emodel:

docker cp ~/citeck-migration/postgres/ecos_model.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_emodel <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_emodel;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_emodel \
    --no-owner --no-acl --role=citeck_emodel \
    -j 4 /tmp/ecos_model.dump

docker exec citeck_postgres_default rm /tmp/ecos_model.dump

ecos_notifications → citeck_notifications:

docker cp ~/citeck-migration/postgres/ecos_notifications.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_notifications <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_notifications;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_notifications \
    --no-owner --no-acl --role=citeck_notifications \
    -j 4 /tmp/ecos_notifications.dump

docker exec citeck_postgres_default rm /tmp/ecos_notifications.dump

ecos_history → citeck_history:

docker cp ~/citeck-migration/postgres/ecos_history.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_history <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_history;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_history \
    --no-owner --no-acl --role=citeck_history \
    -j 4 /tmp/ecos_history.dump

docker exec citeck_postgres_default rm /tmp/ecos_history.dump

ecos_process → citeck_eproc:

docker cp ~/citeck-migration/postgres/ecos_process.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_eproc <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_eproc;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_eproc \
    --no-owner --no-acl --role=citeck_eproc \
    -j 4 /tmp/ecos_process.dump

docker exec citeck_postgres_default rm /tmp/ecos_process.dump

ecos_camunda → citeck_camunda:

docker cp ~/citeck-migration/postgres/ecos_camunda.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_camunda <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_camunda;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_camunda \
    --no-owner --no-acl --role=citeck_camunda \
    -j 4 /tmp/ecos_camunda.dump

docker exec citeck_postgres_default rm /tmp/ecos_camunda.dump

ecos_edi → citeck_edi:

docker cp ~/citeck-migration/postgres/ecos_edi.dump citeck_postgres_default:/tmp/

docker exec -i \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  psql -U postgres -d citeck_edi <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
  FOR r IN SELECT schema_name FROM information_schema.schemata
           WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
  LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_edi;
SQL

docker exec \
  -e PGPASSWORD=postgres \
  citeck_postgres_default \
  pg_restore -U postgres \
    -d citeck_edi \
    --no-owner --no-acl --role=citeck_edi \
    -j 4 /tmp/ecos_edi.dump

docker exec citeck_postgres_default rm /tmp/ecos_edi.dump

4.2. MongoDB

docker cp ~/citeck-migration/mongo/ecos-process.archive citeck_mongo_default:/tmp/

docker exec citeck_mongo_default mongorestore \
  --username "$(docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_USERNAME)" \
  --password "$(docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_PASSWORD)" \
  --authenticationDatabase admin \
  --nsFrom 'ecos-process.*' --nsTo 'citeck_eproc.*' \
  --archive=/tmp/ecos-process.archive --gzip --drop

docker exec citeck_mongo_default rm /tmp/ecos-process.archive

Options:

  • --nsFrom 'ecos-process.*' --nsTo 'citeck_eproc.*' — namespace renaming during restore.

  • --drop — drops collections left over from the first startup of the eproc webapp.

  • The root username and password are read from the container ENV via printenv, so as not to depend on what the launcher generated.

4.3. Zookeeper

Stop zookeeper via citeck stop, not docker stop. This is critical: launcher2 has a reconciler that will restart a container stopped «from outside» in ~1 minute, potentially while data is being written. citeck stop marks the application as detached and the reconciler leaves it alone.

Zookeeper storage in launcher2 is a bind-mount directory on the host (not a named docker volume): /opt/citeck/data/runtime/default/volumes/zookeeper2/. Inside it — the data/ subdirectory where zookeeper holds snapshots, and datalog/ where it holds the transaction log. Bitnami on the source stored both snapshots and the log in a single data/version-2/ directory. During import, files must be split: log.* go into datalog/version-2/, and everything else (snapshot.*, acceptedEpoch, currentEpoch, and any other auxiliary files) — into data/version-2/.

ZK_DIR=/opt/citeck/data/runtime/default/volumes/zookeeper2

citeck stop zookeeper

# Очистить старые данные (они созданы launcher'ом при первом старте)
rm -rf $ZK_DIR/data/version-2 $ZK_DIR/datalog/version-2
mkdir -p $ZK_DIR/data/version-2 $ZK_DIR/datalog/version-2

# Распаковать дамп во временный каталог
TMP=$(mktemp -d)
tar -xzf ~/citeck-migration/zookeeper/version-2.tar.gz -C $TMP

# Сначала переносим log'и в datalog/, потом всё остальное в data/
mv $TMP/version-2/log.* $ZK_DIR/datalog/version-2/ 2>/dev/null || true
mv $TMP/version-2/*     $ZK_DIR/data/version-2/
rm -rf $TMP

# Выставить владельца -- uid/gid пользователя zookeeper в official образе
chown -R 1001:1001 $ZK_DIR/data/version-2 $ZK_DIR/datalog/version-2

citeck start zookeeper

Notes:

  • Verify the exact bind-mount path via docker inspect citeck_zookeeper_default --format '{{(index .Mounts 0).Source}}'.

  • chown 1001:1001 — the uid/gid of the zookeeper user in the official image. Without it, startup fails with permission denied.

  • If files are not split — the official zookeeper fails with SnapDirContentCheckException: Snapshot directory has log files.

  • citeck start zookeeper re-attaches the application, after which the reconciler manages it again.

  • Verify startup:

    docker logs --tail 100 citeck_zookeeper_default
    

    The logs should show Snapshot loaded and Snapshotting: without errors.

4.4. Bring webapps back up

The same list of applications that was stopped in step 1.3. Important: unlike citeck stop (which accepts any number of arguments), citeck start accepts only one app at a time, so we run it in a loop — without --detach, so that each startup waits for RUNNING before starting the next:

for app in <app1> <app2> <app3> ...; do
  citeck start "$app"
done

If started with --detach, a race condition occurs: proxy starts before onlyoffice (or another dependency) becomes DNS-resolvable, and fails with host not found in upstream "onlyoffice". Sequential startup (without --detach) uses launcher2’s waitForDeps and brings applications up in the correct order.

The loop takes 10–20 minutes (each Java webapp starts in 1–3 minutes). Progress can be monitored in parallel via citeck status -w in another session.

Each webapp’s Liquibase will see the current changelog on startup (source was updated to 2026.1 before migration) — there will be no schema changes.

Stage 5. Verification

5.1. What should work

  • Log in to the Web UI with admin / <admin password from step 1.1>. Not admin / admin from the source — the password is now generated by the launcher.

  • Record lists in eapps, eproc, uiserv show source data.

  • BPMN processes from eproc start (mongo + postgres are in sync).

  • Keycloak contains only the admin user and the citeck service account.

5.2. Verification commands

citeck status
citeck health
docker exec -e PGPASSWORD=postgres citeck_postgres_default \
  psql -U postgres -d citeck_eapps -c "\dn"
docker exec -e PGPASSWORD=postgres citeck_postgres_default \
  psql -U postgres -d citeck_eapps -c "\dt+ *.*" | head
docker exec citeck_mongo_default mongo \
  -u "$(docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_USERNAME)" \
  -p "$(docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_PASSWORD)" \
  --authenticationDatabase admin \
  citeck_eproc --eval "db.getCollectionNames()"
docker exec citeck_zookeeper_default \
  bash -c 'echo "ls /" | zkCli.sh -server localhost:2181'

citeck status should show all applications in RUNNING status. citeck health — exit code 0.

5.3. What was reset

  • Source Keycloak users and roles.

  • In-flight RabbitMQ messages.

  • nginx/proxy logs.

  • Let’s Encrypt certificate cache — will be re-issued on the first request.

Rollback

Failure on target before data delivery

Reinstall from scratch:

citeck uninstall --delete-data

Then repeat the target installation and preparation (Stage 1).

Failure after import

Start over from scratch:

citeck uninstall --delete-data

Then installation → target preparation → import. Source is already on 2026.1, no changes needed there — dumps from ~/citeck-migration/ are still valid.

Failure to upgrade source to 2026.1 (prerequisite 1)

Restore from the backup made in step 0:

  • If the backup was made via tardocker compose down, extract the tar, docker compose up -d.

  • If it was made via pg_dumpall + mongodump — follow the standard PostgreSQL/MongoDB restore procedure.

Keep dumps from ~/citeck-migration/ for at least 1–2 weeks after migration — in case problems are discovered late.