Pin a coherent release
Use the Gateway-capable chart implementation introduced in0.2.0 (or a later release that includes it) and matching Den API, Den Web, and Gateway images from this release. It implements the authenticated deployment capability contract version: 1. Do not infer image support from a numerically newer chart, an older stable tag, or the checkout’s placeholder appVersion: "0.1.0".
Pin the shared image.tag and inspect all component overrides: denApi.image.tag, denWeb.image.tag, and the effective gateway.image.tag/inference.image.tag. A nonempty component tag wins over the shared tag; the shared tag wins over Chart.appVersion. An empty canonical component tag clears its legacy override, but still requires a compatible shared tag. The retention job uses the effective Gateway image too.
Keep the installed release name, *-inference Service/Deployment/container names and selectors, and ghcr.io/different-ai/openwork-inference repository. Product naming changed to OpenWork Gateway; installed resource identifiers did not. den-gateway is the separate Web proxy, not a replacement image.
Why the default hook is insufficient
The chart defaults to apre-install,pre-upgrade migration Job running:
DATABASE_URL through the existing secret.keys.databaseUrl mapping and DEN_DB_ENCRYPTION_KEY through secret.keys.denDbEncryptionKey, regardless of config.databaseMode. Even with a PlanetScale HTTP runtime using DATABASE_HOST, DATABASE_USERNAME, and DATABASE_PASSWORD, the precreated Secret must also provide the mapped TCP URL for the same database. Include the database name, TCP port, and verified TLS options in that URL; there are no new migration values or chart inputs for bootstrap’s alternative DATABASE_NAME/DATABASE_PORT configuration. Runtime credentials alone do not satisfy migration connectivity, TLS trust, or DDL privileges.
A pre-upgrade hook runs before the updated Deployments are applied, while the old pods can still be reading and writing. It does not drain model requests, OAuth callbacks, refresh workers, background writers, or retention jobs. Setting migrations.hook: false is not a fix: a normal Job does not establish the required migration-before-rollout barrier either. Helm timeout, retry, and automatic rollback options do not undo MySQL DDL.
There is also a production bootstrap baseline hazard. For an existing schema without __drizzle_migrations, the current bootstrap records all committed migrations as a baseline without checking that the existing schema already contains their changes. It can therefore mark 0097 and its successors applied without executing them. A successful hook or a populated ledger is not proof that the schema is correct. Do not use that path for an unverified legacy database.
For this existing-database cutover, disable the automatic Job and use an externally controlled, reviewed migration procedure:
helm upgrade --atomic recovery.
Prepare before the maintenance window
- Inventory all readers/writers of the shared database, installed images, current values, database version/mode, migration ledger, and actual schema. Include Den API, Gateway/legacy inference, OAuth callbacks and refresh workers, administrative scripts, and retention callers. Confirm no old replica or controller can restart during cutover.
- Back up the database and retain the existing encryption key securely. Verify a restore into an isolated environment. Preserve row counts, schema/ledger evidence, and encrypted credential/usage history without exposing secret columns.
- Rehearse the migration against an isolated restored copy using the exact release SQL and approved runner. Migration source or journal registration is not evidence that production data passes preflight.
- Review the full
0096prerequisite schema and registered migrations0097,0098, and0099, plus any later migrations shipped with the selected release. A schema-push database may be baselined only after full schema verification, never by guessing from table presence. - Prepare one complete, reviewed values set with
migrations.enabled: false, precreated Secret references, explicit destinations, matching images, and an explicit choice about component retention and capability enablement. When removing saved canonical entries, use--reset-valueswith that full configuration, not a final empty override file or--reset-then-reuse-values. Verify the final render without displaying secrets.
0097, the runner must use one dedicated MySQL connection, execute statements sequentially at --> statement-breakpoint boundaries, and stop on the first error. It requires MySQL 8.0.16+ (including 8.4), enforced CHECK support, strict SQL mode (STRICT_TRANS_TABLES or STRICT_ALL_TABLES), CREATE TEMPORARY TABLES, and ordinary migration DDL/DML rights. MariaDB and TiDB are explicitly refused. A serverless/HTTP transport that cannot preserve connection-local temporary tables is not sufficient; choose a reviewed migration connection rather than bypassing preflight. Runtime planetscale mode alone does not validate this.
The embedded preflight checks the complete prior schema, indexes, IDs, ownership, credential subjects, and grant audiences before persistent DDL. It rejects duplicate or dual-audience grants rather than silently merging or deleting them. See the release’s 0097 migration contract for the exact sentinels and preservation rules; use the copy from your pinned release, not an unreviewed moving branch.
Quiesced cutover
- Establish the maintenance barrier. Block new requests at ingress and other callers, drain active streams and OAuth work, stop all relevant application readers/writers, and suspend retention/background jobs. A database migration lock alone does not exclude application writers.
- Confirm the backup and migration starting state. A legacy schema must reach verified
0096before applying0097; an already-migrated or partially migrated schema needs explicit inspection rather than replay. A genuinely empty database can be initialized from the matching current-schema snapshot, but must not be confused with a populated database missing its ledger. - Run the approved migration procedure. Execute
0097with its embedded preflight and connection requirements, then registered0098_gateway_provider_model_universeand0099_gateway_credential_set_creatorin order, and any further release prerequisites. Keep old writers stopped:0098also backfills model policy that old code does not maintain. - Verify actual schema, preserved data, and receipts. Record each migration only after all its SQL succeeds. Check canonical Gateway tables and selection columns,
gateway_providers.model_ids, andgateway_credential_sets.created_by_org_membership_id, not justselect 1or ledger timestamps. - With traffic still closed, deploy the matching Den API, Den Web, and Gateway images. Keep
migrations.enabled: falsefor this externally migrated rollout. Review sparse values and required Secret references as described in the opt-in guide. - Verify liveness/readiness, then the authenticated
GET /v1/orgtop-leveldeploymentCapabilitiescontract across API replicas. For opt-in, expect{ "version": 1, "aiGateway": true }. Confirm the intended organization’s separate dashboard grant and fresh admin access. Readiness and the capability response are not schema proofs. - Exercise a permitted provider flow with non-sensitive test data, verify public desktop routing and internal routing, and check usage attribution. Reopen traffic only after these checks pass; resume approved retention and background work last.
Expected preserved state
- Existing providers, credential identities, encrypted credential values, grants, and usage history are preserved by the migration’s explicit backfills/renames. Keep
DEN_DB_ENCRYPTION_KEYunchanged. - Existing provider grants receive the migration’s default group/set mapping; historical usage selection fields remain unknown/null rather than being attributed to invented selections.
- Unused legacy OAuth states are marked used because they lack the new set/client binding. Members must start those sign-in flows again; existing credentials are not deleted or revoked by that action.
- Gateway’s
gateway_keysstarts empty. Members receive independentow_gw_keys through the normal connect/provisioning flow; existing Modelsow_inf_keys are not copied into it. - Models keys, subscription billing, limits, usage buckets, and ledger remain separate. Keep
OPENWORK_INFERENCE_BASE_URL,STRIPE_INFERENCE_PRICE_ID, andconfig.inference.*legacy contracts intact.
Disable without a schema downgrade
Choose the operation that matches your intent. These switches preserve persisted Gateway data; they are not credential-revocation controls or a schema rollback.
For the keep-component Helm case, the relevant final values are:
gateway root is also supported for legacy reused values and means no canonical overrides. However, once gateway.enabled or other canonical entries have been saved, an empty root or nested map can be refilled by Helm’s saved values or earlier input files. Use --reset-values with a complete, reviewed values file that omits gateway.enabled and unwanted canonical entries, explicitly includes any desired empty maps, and preserves the rest of the deployment. A final empty override file or --reset-then-reuse-values is not a substitute. Render the complete desired values and confirm the *-inference component remains and both services receive GATEWAY_ENABLED=false.
Do not use gateway.enabled: false together with inference.enabled: true to try to keep the component: canonical presence wins, so it is removed. Do not inject GATEWAY_ENABLED through denApi.env, gateway.env, or inference.env; the chart rejects that override. Retaining other canonical keys while omitting only enabled is supported.
There are additional environment differences in this legacy service-only mode. Verify the rendered pod environment, not just retained values:
- Keep
config.public.gatewayPublicBaseUrland the legacy Models public origin inconfig.internal.inferenceProxyBaseUrl. The chart retains those destinations when management is disabled, and runtime selection prefers a valid legacy Models public origin, then a valid Gateway public origin, independently of the management flag. Existing disabled-mode per-appGATEWAY_PUBLIC_BASE_URLoverrides are still honored without duplicate env entries, but remove them before re-enabling and use shared public configuration. If neither public destination is usable, historical Models fallback may still be private/loopback; malformed optional disabled settings do not become new startup prerequisites. Existing member Gateway payloads retain their configured public URL. Previously stored provider URLs are not automatically repaired by toggling the flag. - The enabled-only required database/encryption
secretKeyRefentries also disappear. Legacy pods use the shared Secret throughenvFrom; ensure it contains the actual runtime names (DATABASE_URL, or the PlanetScale variables, andDEN_DB_ENCRYPTION_KEY). Remapped data keys alone no longer provide those runtime variables in service-only mode. Resolve this through the precreated Secret, not literal env values. - Outside retention, canonical admin/webhook token overrides are no longer emitted. Recheck the effective aliases from the shared Secret and any legacy env settings. Retention, if retained, still supplies both admin-token aliases from its dedicated reference.
helm rollback to an older pre-migration image against the renamed schema. Recover by fixing forward with compatible images, or use a separately approved, quiesced database-and-image restore from the verified backup. A restore can lose post-backup writes and must account for credentials, usage, and billing reconciliation. Keep traffic stopped until schema and all readers/writers agree again.
Release references
- Gateway chart values and compatibility
- 0097 migration contract
- 0098 model-policy backfill
- 0099 credential-set creator