Azure 60: Multi-Component Services — `services[]` + `auxiliaryImages[]`
easy⏱ 5 mincourseazure
services[] for the deployable components
Each entry in services[] is { name, projectName, universalChartType, helmOverrides? }. projectName selects which sub-module the Gradle build emits (Spring Boot multi-project pattern: routes/, consumer/, availability/). One Gradle build → multiple jars → multiple images → multiple helm releases.
services:
- name: offer-api
projectName: routes
universalChartType: api
- name: offer-api-cron
projectName: cron
universalChartType: cron
- name: odyssey-availability-sub
projectName: availability
universalChartType: headless
auxiliaryImages[] for non-deployable artifacts
Auxiliary images are built alongside services but DON'T get their own helm release — they're consumed by main services as init containers, sidecars, or one-shot Jobs. Liquibase migration images, fixture loaders, and seed-data containers all fit. They share the version (build number) so the init container in your deployment always matches the app code that needs it.
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"
All share $(GIT_SEM_VER) and $(SHORT_BUILD_NUMBER)
GitVersion runs once at the start of the pipeline. Every component image — service + auxiliary — is tagged with the same 1.5.0 + 20260514.4. Helm --set deployment.initContainers.liquibase.image.tag=$(SHORT_BUILD_NUMBER) keeps the auxiliary pinned exactly to the build that produced the main app. No drift, no 'wrong migration ran' incidents.
Build the resolver
Write planMultiComponent({ environmentName, services, auxiliaryImages, version, buildNumber }) returning { ciMatrix, cdMatrix }. ciMatrix is one entry per component (services + auxiliaryImages) with { name, projectName?, dockerFile, tag }. cdMatrix is one entry per service (NOT auxiliaries) with { name, chart, helmOverrides } where helmOverrides includes auxiliary tags as --set deployment.initContainers.<aux>.image.tag=<buildNumber> when an auxiliary's name starts with <service>/. Log the plan for offer-api.
Name auxiliaries <service>/<aux> so they bind
The slash convention (offer-api/liquibase, odyssey-api/seeder) makes the binding explicit: an auxiliary named <svc>/<aux> is wired into <svc>'s init containers automatically. This avoids a separate bindings: parameter; the convention IS the binding.
Quiz: deploy one component, hold the others
Q: You changed only offer-api-cron. Can the pipeline build/deploy just that? A: In principle yes, but Lythia chooses no. All components ship together because they share dependencies (e.g. the migration). Building unconditional fan-outs is faster than tracking change-isolation graphs. Bandwidth is cheap; debugging staircase versions is expensive.