You are viewing the 3.0 documentation (Beta). Go to the current 2.0.x documentation.

Migrating an installation from 2.0 to 3.0 (preview)

Preview. Zequent 3.0 is not released yet; this runbook describes the preview builds and will change before the release. Until then, run it only against a copy of your installation.

This is the operator's step-by-step guide for moving an existing 2.0 installation — the customer Docker Compose setup or the Zequent Helm chart — to 3.0. What changes and why, which breaking changes affect integrations, and which parts must move together is explained in Upgrading the platform to 3.0; read it first. OpenBao and Keycloak themselves are described in Secrets and sign-in.

Overview

2.03.0
Sign-inthe platform's own password login (password hashes in Postgres), optional single sign-on per organization configured in the platformKeycloak only; single sign-on is brokered by Keycloak
Secretsenvironment variables and Kubernetes Secrets; license activations sealed with EXPORT_PLATFORM_KEKOpenBao: KV for passwords and peppers, Transit keys for signing and sealing
Integration Hub credentialsencrypted in its database with VAULT_ENCRYPTION_KEYOpenBao KV only
InfrastructurePostgres/TimescaleDB, RedisPostgres/TimescaleDB, Redis, Keycloak (with its own database), OpenBao

The upgrade is a maintenance window: all core services and the console stop on 2.0 and start on 3.0 together. Plan for the database migration, which rewrites timestamp columns and can take minutes on a database with a long history.

1. Prerequisites

WhatWhy
The installation runs the latest 2.0.x release3.0's migrations start from the 2.0 database
A copy of the production database to rehearse onthe migration cannot be undone except by restoring a backup (Roll back)
Free disk space on Postgres of at least the size of the detections tablecompressed detection chunks are decompressed for the migration and compressed again later
A DNS name and a TLS certificate for Keycloak (for example auth.<your-domain>)people's browsers are sent to Keycloak to sign in; every token names it as issuer
Docker Compose 2.23 or newer (Compose)the 3.0 compose file uses inline configs for OpenBao and Keycloak
Helm 3 and the 3.0 chart (Kubernetes)openbao.* and keycloak.* values
Every device paired with a claim code, every human approval given by a signed-in user3.0 removes the legacy pairing paths and refuses approvals from credentials — see Recommended upgrade order
Copies of the 2.0 secrets: .env.customer or the Kubernetes Secrets, in particular EXPORT_PLATFORM_KEK, VAULT_ENCRYPTION_KEY, SERVICE_AUTH_PRIVATE_KEY / SERVICE_AUTH_PUBLIC_KEY and LIVE_STREAM_KEYS_PEPPERneeded to move sealed secrets, to keep 2.0 credentials valid and for a rollback

2. Back up

Stop nothing yet; take the backups right before the maintenance window and again after the 2.0 services are stopped (step 6.1), so the second one has no writes missing.

Postgres (with TimescaleDB)

The database zequent_db holds the platform's data and, in its own schema, the Integration Hub's.

# Docker Compose
docker compose exec -T postgres pg_dump -U postgres -Fc zequent_db > zequent_db-$(date +%F).dump

# Kubernetes: in the pod of your Postgres
kubectl -n <namespace> exec <postgres pod> -- pg_dump -U postgres -Fc zequent_db > zequent_db-$(date +%F).dump

pg_dump may warn about circular foreign keys in TimescaleDB's catalog; that is expected. A restore needs the same TimescaleDB version and runs between timescaledb_pre_restore() and timescaledb_post_restore() (see Roll back). Test the restore once into an empty database before the window.

Redis

Redis holds license leases, sessions, the simulator's fleet and, on installations that came from older releases, no-fly zones the console kept only in Redis (connector imports them into Postgres, 3.0 still reads them from there). Take a snapshot:

# Docker Compose (the container has the password in REDISCLI_AUTH)
docker compose exec redis redis-cli BGSAVE
docker compose exec redis redis-cli LASTSAVE      # repeat until the timestamp changes
docker compose cp redis:/data ./redis-$(date +%F)

On Kubernetes, use your Redis installation's snapshot procedure (BGSAVE, then copy the RDB and AOF files from its volume).

OpenBao and Keycloak

Nothing to back up: 2.0 has neither. From the first 3.0 start on, back them up as described in Backup and restore, and keep the OpenBao seal key apart from every backup.

3. Set up Keycloak and OpenBao

Both run next to the 2.0 services without affecting them, so this step can be done before the maintenance window.

Keycloak

Docker ComposeHelm
Switch on--profile keycloakkeycloak.enabled: true
Public addressKEYCLOAK_URL (TLS reverse proxy in front of port 8180)keycloak.ingress.host, TLS Secret keycloak.tls.secretName
Console address (redirect target)ZQNT_CONSOLE_URLconsole.frontend.ingress.host
Databasedatabase keycloak on the platform's Postgres, created by keycloak-db-init with KEYCLOAK_DB_PASSWORDits own keycloak-postgres (keycloak.database.*), or an existing database
SecretsKEYCLOAK_DB_PASSWORD, KEYCLOAK_ADMIN_PASSWORD, KEYCLOAK_ADMIN_CONSOLE_CLIENT_SECRET — each openssl rand -hex 32generated once into <release>-credentials

Keycloak imports the realm zequent (clients zqnt-console and zqnt-admin-console) on its first start. Replace the temporary administrator admin with a permanent one afterwards. Details: How people sign in.

OpenBao

Docker ComposeHelm
Switch on--profile openbaoopenbao.enabled: true (openbao.replicas 1, or 3/5 for high availability)
Seal keygenerated once into the volume openbao_sealgenerated once into <release>-credentials, or openbao.seal.existingSecret
Mounts, Transit keys, policies, loginsopenbao-init, on every startJob openbao-configure, after every install and upgrade
Logins of the servicesone AppRole per workload; role-id and secret-id in the volume openbao_credentialsKubernetes auth, one role per workload, bound to its ServiceAccount

Both create the Transit keys service-token, credential, export-signing and platform-kek under zqnt-transit/, and write each KV secret once, when its path does not exist yet, from the values you already have:

2.0 setting (.env.customer / chart Secret)OpenBao path (mount zqnt/)Field
POSTGRES_PASSWORD / DATABASE_PASSWORDinfrastructure/postgrespassword
REDIS_PASSWORDinfrastructure/redispassword
LIVE_STREAM_AUTH_HOOK_SECRETinfrastructure/mediamtxhook-secret
object storage keys (Helm)infrastructure/object-storageplatform-, dock-, adapter-access-key / -secret-key
DJI MQTT broker credentialsinfrastructure/mqttusername, password, dock-username, dock-password
DJI_APP_KEY, DJI_APP_ID, DJI_APP_LICENSEplatform/edge-adapter-dji/cloud-apiapp-key, app-id, app-license
LIVE_STREAM_KEYS_PEPPERplatform/peppers/stream-keysvalue
ZQNT_PEPPER_WEBHOOK_TOKENS (new)platform/peppers/webhook-tokensvalue
ZQNT_PEPPER_CLAIM_CODES (new)platform/peppers/claim-codesvalue
ZQNT_PEPPER_AGENT_PSEUDONYMS (new)platform/peppers/agent-pseudonymsvalue
KEYCLOAK_ADMIN_CONSOLE_CLIENT_SECRETplatform/admin-console/keycloakclient-secret
GRAFANA_ADMIN_PASSWORD (optional monitoring)infrastructure/grafanaadmin-password

A pepper is the key under which tokens, codes and stream keys are stored as hashes. If your 2.0 installation had LIVE_STREAM_KEYS_PEPPER set, keep exactly that value. Generate the three new peppers once (openssl rand -hex 32) and never change them: the first 3.0 start hashes every existing webhook token and claim code under them. A pepper you do not set is generated by openbao-init / the chart, which is fine for the new ones and wrong for an existing LIVE_STREAM_KEYS_PEPPER.

After the first start, OpenBao is the source of these values: change one with bao kv patch -mount=zqnt <path> <field>=… and restart the workloads that read it.

4. Change the configuration

Docker Compose (.env.customer and docker-compose.customer.yml)

Take the 3.0 docker-compose.customer.yml and .env.customer.example and carry your values over.

ChangeSettingWhat to do
RemovedEXPORT_PLATFORM_KEKNo longer read: license activations and AI model keys are sealed with OpenBao's platform-kek. Keep the value in your backup until step 5 is done
RemovedOIDC_REDIRECT_URISingle sign-on now goes through Keycloak; the redirect URI an organization registers with its identity provider is shown on the console's Single Sign-On screen
No longer read by the core servicesAUTH_PRIVATE_KEY, AUTH_ISSUER, AUTH_PUBLIC_KEY, AUTH_EXPECTED_ISSUER, AUTH_REFRESH_COOKIE_SECURE3.0 issues no sign-in tokens of its own. The template still lists AUTH_PUBLIC_KEY / AUTH_EXPECTED_ISSUER for the simulator's control API
Now requiredLIVE_STREAM_KEYS_PEPPERWas optional. Keep your 2.0 value if you had one
Added, requiredZQNT_PEPPER_WEBHOOK_TOKENS, ZQNT_PEPPER_CLAIM_CODES, ZQNT_PEPPER_AGENT_PSEUDONYMSopenssl rand -hex 32 each, once. The compose file refuses to start without them
Added (Keycloak)KEYCLOAK_URL, ZQNT_CONSOLE_URL, KEYCLOAK_DB_PASSWORD, KEYCLOAK_ADMIN_PASSWORD, KEYCLOAK_ADMIN_CONSOLE_CLIENT_SECRETsee step 3
Added (optional)GRAFANA_ADMIN_PASSWORD, PROMETHEUS_PORT, GRAFANA_PORTonly with --profile monitoring (needs --profile openbao)
Added, set by the compose fileZQNT_PLATFORM_KEYS_URL for adapters and the simulatorthe key set admin-console publishes; nothing to configure
UnchangedPOSTGRES_PASSWORD, DATABASE_*, REDIS_*, SERVICE_AUTH_PRIVATE_KEY, SERVICE_AUTH_PUBLIC_KEY, LICENSING_*, LICENSE_SERVER_URL, AUTH_SYSTEM_ADMIN_EMAIL, ADMIN_CONSOLE_CORS_ORIGINS, NEXT_PUBLIC_*, edge tokenskeep. Keep SERVICE_AUTH_PUBLIC_KEY in any case: edge and client credentials issued on 2.0 are checked against it
Image tagsevery image:the 3.0 tags from the release notes, for all core services and the console at once

The 3.0 core services additionally need these settings (every core service unless noted):

SettingValue
ZQNT_AUTH_KEYCLOAK_ISSUERhttps://<keycloak>/realms/zequent — a core service does not start without it
ZQNT_AUTH_KEYCLOAK_JWKS_URLoptional; Keycloak's keys from inside the network, for example http://keycloak:8080/realms/zequent/protocol/openid-connect/certs
ZQNT_AUTH_KEYCLOAK_ADMIN_URL (admin-console)optional; for example http://keycloak:8080
ZQNT_AUTH_KEYCLOAK_ADMIN_CLIENT_SECRET (admin-console)the zqnt-admin-console client secret, unless it is read from OpenBao
ZQNT_SECRETS_PROVIDERopenbao — required at least on admin-console: without it no license activation can be stored
OPENBAO_ADDRhttp://openbao:8200
OPENBAO_APPROLE_ROLE_ID_FILE, OPENBAO_APPROLE_SECRET_ID_FILEthe files role-id and secret-id from the workload's folder in the volume openbao_credentials

Open before release: the preview docker-compose.customer.yml starts OpenBao and Keycloak but does not pass the settings above to the core services yet. Until it does, add them to the core services yourself, or run the upgrade on the Helm chart.

Helm (zqnt-platform-v2 values)

ChangeValueWhat to do
Removedauth.privateKeyKey (AUTH_PRIVATE_KEY), auth.issuer, auth.refreshCookieSecure3.0 issues no sign-in tokens of its own
Removedauth.exportPlatformKekKey (EXPORT_PLATFORM_KEK)sealing moved to OpenBao Transit platform-kek
RemovedintegrationHub.vault.* (VAULT_ENCRYPTION_KEY)Hub credentials live in OpenBao; see the callout in step 6
RemovedintegrationHub.platform.assetSn, integrationHub.platform.assetNamethe Integration Hub is no asset any more
Removedservices.djiAdapter.licensing.organizationId, services.simulator.organizationIdthe organization comes from the claim code or the edge credential
Keptauth.existingSecret with EXPORT_SIGNING_PRIVATE_KEY / EXPORT_SIGNING_PUBLIC_KEY, auth.publicKeyKey, auth.expectedIssuer, auth.systemAdminEmail, serviceAuth.*, licensing.*, database.*, redis.*with OpenBao, export-signing signs and the public half only verifies files exported before
Added, requiredkeycloak.enabled: true, keycloak.ingress.host, keycloak.tls.secretNamesee step 3
Added, requiredopenbao.enabled: truethe Integration Hub and the sealing of license activations need it
Addedservices.connector.peppers.*, console.backend.peppers.*the new peppers; generated once into <release>-credentials unless you name an existingSecret. console.backend.liveStream.keysPepper stays as it is
Addedservices.connector.startupSeconds (default 1800)time the connector may take for its migrations before liveness applies; raise it for a very large database
Added (optional)availability.*, autoscaling.*, services.<name>.autoscaling, console.backend.replicas, agentService.*, observability.grafana.keycloak.*, networkPolicy.kubeApiServerreplicas and autoscaling, see Sizing and capacity; the AI assistant; Grafana sign-in. Narrow networkPolicy.kubeApiServer.cidr to your API server when OpenBao is on
Added (optional)fileScanner.enabled, objectStorage.filesBucket, objectStorage.quarantineBucketfiles in the AI assistant's chat: the virus scanner and its buckets, see AI assistant: files and profiles. Off by default; uploads are refused without the scanner
Automaticadmin-console reaches the Integration Hub (INTEGRATION_HUB_URL, network rules)set by the chart when integrationHub.enabled; nothing to configure
Image tagsservices.*.tag, console.*.image.tag, integrationHub.image.tagthe 3.0 tags from the release notes

Default passwords are refused

A 3.0 service started outside development mode refuses to start when any of these holds a value from Zequent's local development setup or an unfilled <PLACEHOLDER> of .env.customer.example:

CheckedSince
Postgres password, Redis password, object storage secret key (also when read from OpenBao)new in 3.0
The four peppers, the stream-auth hook secret, the zqnt-admin-console client secret, the OpenBao loginnew in 3.0
Service key pair, export signing pair, platform KEK, license public keyalready in 2.0

The error names the setting, never the value. An installation that still runs on a password copied from a developer setup must change it before the upgrade (for Postgres: ALTER USER … PASSWORD, then the setting).

5. Move users and sealed secrets

The 3.0 connector refuses to migrate the database while anything that 2.0 kept in its own database has not moved:

GuardRefuses whileWhat has to move
Users and single sign-ona user who signs in with a password has no Keycloak account, or an organization's single sign-on provider still has its client secret in the platformevery password user into Keycloak (account linked to the platform user, member of their organization, ideally with their existing password); every organization's single sign-on into Keycloak
Sealed secretsa license activation (or an AI model key) is still sealed with EXPORT_PLATFORM_KEKevery organization's license activation, opened with EXPORT_PLATFORM_KEK and sealed again with OpenBao's Transit key platform-kek

On a 2.0 database both guards apply: the seeded system administrator alone is a password user without a Keycloak account, and every licensed organization has an activation sealed with EXPORT_PLATFORM_KEK.

Open before release: there is no supported way yet to move a 2.0 installation's password users into Keycloak and its license activations into OpenBao Transit. The preview builds that did this on their first start no longer do, and the tool that replaces them is not available yet. On a plain 2.0 database the 3.0 connector therefore stops at this point with … password user(s) have no Keycloak account yet … or … license activation(s) … are still sealed with EXPORT_PLATFORM_KEK, and the database is left half-migrated (restore the backup, see Roll back). This section will describe the supported steps before 3.0 is released. Do not upgrade a production installation until then.

The error messages name a way past each guard that gives the data up instead of moving it. It is not a migration — it costs every password and every activation — and is only meant for test installations; talk to Zequent before using it anywhere else:

  • A user that is to lose its password: UPDATE schema_zequent.users SET password_hash = NULL WHERE id = …. After the upgrade admin-console creates their Keycloak account without a password; an administrator sets one in Keycloak, or the person uses Forgot password? once Keycloak can send email.
  • An organization's single sign-on to configure again: UPDATE schema_zequent.identity_providers SET client_secret = '' WHERE organization_id = …, then set it up again on Single Sign-On after the upgrade (Microsoft Entra ID for an organization).
  • A license activation given up: delete its row from schema_zequent.license_activation_credentials, then activate the organization's license again in the console (Organizations → Activate license).

6. Upgrade the services

6.1 Stop 2.0

Stop the five core services, the console and the Integration Hub. Keep Postgres, Redis, OpenBao and Keycloak running. Take the second backup (step 2).

6.2 Start 3.0

Docker Compose

docker compose --env-file .env.customer -f docker-compose.customer.yml \
  --profile openbao --profile keycloak pull
docker compose --env-file .env.customer -f docker-compose.customer.yml \
  --profile openbao --profile keycloak up -d openbao-seal-key openbao openbao-init keycloak-db-init keycloak
docker compose --env-file .env.customer -f docker-compose.customer.yml \
  --profile openbao --profile keycloak up -d connector-service
docker compose logs -f connector-service       # wait for the migrations to finish
docker compose --env-file .env.customer -f docker-compose.customer.yml \
  --profile openbao --profile keycloak up -d

Helm

One helm upgrade with the 3.0 chart, the changed values and the 3.0 image tags. OpenBao, its configure Job, Keycloak and its realm Job come up with it; the core services restart a few times until OpenBao is configured. Watch the connector:

kubectl -n <namespace> logs -f deploy/connector-service

Order and what to watch

  1. The connector runs the database migrations on its first start (step 7). Let it finish before the other services start. Do not start 2.0 services against the database from here on: after the first migration it is no longer a 2.0 database.
  2. live-data, remote-control, mission-autonomy, admin-console and the console start on the same 3.0 version — never mix 2.0 and 3.0 core services.
  3. The Integration Hub starts last. On its first 3.0 start it moves every credential 2.0 encrypted in its database into OpenBao, then drops the encrypted column. It needs VAULT_ENCRYPTION_KEY (your 2.0 value) for that start only; without it, and with encrypted credentials present, it stops with … credentials are still encrypted in the database: set VAULT_ENCRYPTION_KEY once … and changes nothing. After its log shows moved N credentials from the database to OpenBao, remove VAULT_ENCRYPTION_KEY.

Open before release: the 3.0 Helm chart has no value for VAULT_ENCRYPTION_KEY any more. An installation whose Integration Hub holds credentials from 2.0 has to give the key to the integration-hub-backend Deployment by hand for its first start (and take it away again) until the chart offers this.

7. What the database migrations do

The connector migrates the database from the last 2.0 version to 3.0 in one go. Notable steps:

MigrationWhat it doesReversible
Audit log, node runs, custom roles, AI assistant, approval rules, simulated runs, agent profiles, chat filesnew tables and columns—
Webhook tokenshashes every event trigger's webhook token with the webhook-token pepper and drops the stored tokenno
Claim codeshashes every asset claim code again with the claim-code pepperno
Single sign-on role mappingsturns each provider's claim-to-role mapping into group mappings and drops the old columnno
Timestampsevery remaining timestamp column becomes timestamp with time zone (values read as UTC). Decompresses the compressed detection chunks first; the compression policy compresses them again later. Takes the longest on a large databaseno
Keycloak linkadds each user's Keycloak account (keycloak_subject)—
Users and single sign-on in Keycloak onlyguard (step 5), then drops users.password_hash and the identity provider tablesno
Secrets sealed in OpenBao onlyguard (step 5): refuses while anything is sealed with EXPORT_PLATFORM_KEK—

Each migration runs in a transaction: one that refuses or fails changes nothing itself, but the migrations before it stay applied.

On its first start admin-console also converts the stored operator stream keys to the 3.0 form. Stream keys a 2.0 installation issued with LIVE_STREAM_KEYS_PEPPER set cannot be converted and are revoked (logged as Revoked N stream keys …): cut new keys for the devices that used them.

8. Verify

CheckHow
Services readydocker compose ps / kubectl -n <namespace> get pods: nothing restarting. admin-console: curl -fsS https://<console-api>/q/health/ready
Migrationsthe connector's log reports the migrations applied and no error; it serves requests
OpenBaobao status: Initialized true, Sealed false
Keycloakcurl -s https://<keycloak>/realms/zequent/.well-known/openid-configuration names the issuer https://<keycloak>/realms/zequent
Sign-inopen the console: it sends you to Keycloak. Sign in as the system administrator, then as a user of an organization with single sign-on
Users in step with Keycloakas system administrator, POST /api/admin-console/keycloak/sync: no conflicts
LicenseLicense / Organizations in the console: every organization VALID. admin-console logs no sealed secret(s) could not be moved
Assets and telemetryexisting assets are online and send telemetry; a command reaches a device
Pairingcreate a claim code and pair a test device (Pairing after the upgrade)
Integration Hubits connections work; the credentials appear under Manage → Access & Integrations → Secrets → Integration Hub (the Hub has no Vaults tab in 3.0). Bridges without a poll interval on a source that answers at once now run on demand: give the ones that start Applications a poll interval, see Bridges, triggers and steps
Live videoa stream plays; devices whose stream key was revoked have new keys
Backupstake the first OpenBao snapshot and Keycloak database dump (Backup and restore)

9. Roll back

The migrations drop columns and tables (stored webhook tokens, password hashes, the identity provider tables, the Integration Hub's encrypted credentials) and change column types. The only way back to 2.0 is restoring the backup; everything written on 3.0 since is lost.

  1. Stop the 3.0 core services, the console and the Integration Hub.

  2. Restore Postgres into an empty database with the same TimescaleDB version:

    # Docker Compose; on Kubernetes the same commands in the Postgres pod
    docker compose exec -T postgres psql -U postgres -c 'DROP DATABASE zequent_db' -c 'CREATE DATABASE zequent_db'
    docker compose exec -T postgres psql -U postgres -d zequent_db \
      -c 'CREATE EXTENSION IF NOT EXISTS timescaledb' -c 'SELECT timescaledb_pre_restore()'
    docker compose exec -T postgres pg_restore -U postgres -d zequent_db --no-owner < zequent_db-<date>.dump
    docker compose exec -T postgres psql -U postgres -d zequent_db -c 'SELECT timescaledb_post_restore()'
    
  3. Restore the Redis snapshot (stop Redis, put the saved /data back, start it).

  4. Put back the 2.0 configuration — .env.customer or values, including EXPORT_PLATFORM_KEK and VAULT_ENCRYPTION_KEY — and the 2.0 image tags, and start 2.0.

OpenBao and Keycloak may keep running; 2.0 does not use them. Edge and client credentials issued on 3.0 are not valid on 2.0.

10. Adapters and client applications

Adapters and client applications do not have to move with the platform; see What keeps working.

Open before release: with OpenBao, the 3.0 platform signs its calls into adapters with the Transit key service-token. Adapters on a 2.0 Edge SDK verify those calls only against the static ZQNT_PLATFORM_PUBLIC_KEY and may refuse them. Check your 2.0 adapters against the upgraded platform during the rehearsal; adapters on a 3.0 Edge SDK fetch the key set from ZQNT_PLATFORM_KEYS_URL.

  • Adapters: move them one at a time to the 3.0 Edge SDK — Java · Python · Go. Set ZQNT_PLATFORM_KEYS_URL to https://<console-api>/api/admin-console/auth/service-keys; keep ZQNT_EDGE_TOKEN and ZQNT_PLATFORM_PUBLIC_KEY.
  • Client applications: after the platform, to the 3.0 Client SDKs — Upgrading to 3.0.

Was this page helpful?

© Copyright 2026 Zequent. All rights reserved.