12. CI/CD: build, scan, deploy
CI is a delivery adapter, not a workflow orchestrator. It proves the repository is healthy, builds the same workload images Compose uses, scans them, and updates ACA definitions by digest. Training, batch scoring, LLM evaluation, and promotion remain workload or release operations, not GitHub Actions steps.
The blocking test job
The .github/workflows/ci.yml test job runs on pull requests and pushes to main. Its validations are intentionally local and reproducible:
cd projects/ml-platform
pip install -r src/train_job/requirements.txt ruff pytest
ruff check src/ demo/ tools/ tests/
python tools/check_env_contract.py
bash -n deploy/*.sh demo/minio-init.sh
docker compose -f demo/docker-compose.yml config --quiet
pytest tests/ -q --tb=shortThe test command is blocking. A failure in retries, model adaptation, environment fallback, or contract drift prevents the dependent build-and-push job from running. The local suite has a dependency-gated model-loading test because the repository’s lightweight development environment may not have the MLflow stack installed; CI installs the workload requirements before running it.
Build by digest
On main, the build-and-push job runs after test. A matrix builds the MLflow, train, batch, serving, and dashboard images from the project context. The train image also contains the shared LLM registration/evaluation entrypoints, so no second LLM image is needed.
Each image is tagged with the commit SHA, scanned by Trivy for CRITICAL vulnerabilities, pushed to ACR, and converted to a registry/repository@sha256:... reference. ACA definitions consume that immutable reference. A latest tag may help a developer inspect a registry, but it is not a deployment identity.
OIDC and the release boundary
The workflow requests id-token: write and uses azure/login with the repository’s Azure client, tenant, and subscription values. That is OIDC federation: GitHub obtains a short-lived Azure token instead of storing a service-principal password in the repository.
The id-ci identity has the narrow ACA definition-update role described in chapter 09. It can update images and definitions but does not receive the train, batch, serving, or database data-plane permissions. Required repository variables identify ACR, the resource group, and the ACA resources.
For the optional LLM evaluator, CI delivers the train image digest. Provisioning the manual LLM Jobs and supplying the evaluation dataset remain Terraform/deployment configuration, while triggering an evaluation is an operator or release action.
What CI does not guarantee
A green build proves syntax, focused behavioral tests, environment coverage, shell syntax, Compose parsing, image buildability, and vulnerability policy. It does not prove that a live Azure database grants are correct, that a managed identity can obtain every token, or that an external LLM endpoint is available.
Those claims belong to the cloud adapter’s smoke tests and operations runbook. The smoke tests poll real ACA executions and assert results rows, model identity, readiness, and prediction behavior after deployment. Keeping those checks outside CI prevents a pull request from becoming a scheduler or a cloud integration test with hidden state.
Next: 13 — Azure operations turns those deployment outputs into a practical operating loop.