Azure 49: PostgreSQL Flexible Server per-service + Liquibase auxiliary images
easy⏱ 5 mincourseazure
auxiliaryImages in delivery.yml
Defined in the service's pipeline parameters: auxiliaryImages: [{ name: offer-api/liquibase, dockerFile: ./liquibase/Dockerfile, buildArgs: '--build-arg URL=jdbc:postgresql://prod-nooffer-postgres-flex-server0.postgres.database.azure.com:5432/postgres' }]. The buildImages stage builds them alongside service images so the migration container is tagged with the same SemVer as the app.
auxiliaryImages:
- name: offer-api/liquibase
dockerFile: ./liquibase/Dockerfile
buildArgs: "--build-arg URL=jdbc:postgresql://prod-nooffer-postgres-flex-server0.postgres.database.azure.com:5432/postgres"
Init container pattern
The universal api chart can mount the Liquibase image as a deployment.initContainers.liquibase block. The init container exits 0 on success, the main container starts only afterwards. Helm --set deployment.initContainers.liquibase.image.tag=$(SHORT_BUILD_NUMBER) keeps the migration image pinned to the same build that produced the app code.
Per-service PG server vs shared
Why a server per service instead of one shared cluster? PG Flex's General Purpose tier maxes at ~16 vCores per server. Lythia's hot services (cart, incentives, offer) easily peak past that during sales events. Per-service servers also let each team scale independently and pick their own backup retention. The cost is operational overhead — backups, version upgrades, parameter tuning — handled centrally via Terraform-common.
Coordinate the migration
Write planMigration({ service, env, version, schemaChanges }) returning { imageTag, buildCmd, applyCmd, helmGate }. imageTag = <service>/liquibase:<version>. buildCmd = docker build … --build-arg URL=…. applyCmd = docker run … liquibase update. helmGate is a boolean: only run helm upgrade if schemaChanges is empty OR applyCmd succeeded (simulate with a simulateApply flag). Log the plan for two services.
Use Liquibase tags to enable safe rollback
Before each release, tag the Liquibase changelog (liquibase tag <semver>). If the helm rollout fails and you need to roll back the app, you can also liquibase rollback <previous-semver> to revert the schema without manual SQL. The pre-tag is automatic in Lythia's Dockerfile build step.
Quiz: why not use a Kubernetes Job?
A pre-deploy K8s Job runs once per pipeline invocation, but in a multi-pod rollout (especially with canary) you'd need to time it carefully. Init containers run per pod start with Liquibase's built-in lockout preventing duplicates — simpler model, naturally idempotent.