--- name: azure-pipeline-docker description: Extends azure-devops-pipeline for Docker image builds and pushes. Handles buildx with layer caching, Trivy scanning, ECR and ACR login, and a git-SHA/tag tagging strategy. Always load azure-devops-pipeline first. --- ## What I add Type-specific steps for Docker image pipelines. Merge these into the skeleton from `azure-devops-pipeline`. ## Additional required inputs — ask the user 1. **Registry type** — `ECR` | `ACR` 2. **Registry URL** — e.g. `123456789.dkr.ecr.us-east-1.amazonaws.com` or `myregistry.azurecr.io` 3. **Image repository name** — e.g. `myapp/api` 4. **Dockerfile path** — default `./Dockerfile` 5. **AWS region** — required if ECR 6. **AWS service connection name** — required if ECR 7. **ACR service connection name** — required if ACR ## Lint stage steps ```yaml - script: | docker run --rm -i hadolint/hadolint < displayName: "Lint — hadolint Dockerfile" ``` ## Security scan stage steps The security scan builds the image locally and runs Trivy against it **before** pushing. This ensures vulnerabilities are caught pre-push. ```yaml - script: | docker build \ -t scan-target:$(Build.SourceVersion) \ -f \ . docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy:latest image \ --exit-code 1 \ --severity HIGH,CRITICAL \ --format json \ --output trivy-results.json \ scan-target:$(Build.SourceVersion) displayName: "Security scan — Trivy" - task: PublishBuildArtifacts@1 inputs: pathToPublish: trivy-results.json artifactName: security-scan condition: always() displayName: "Publish Trivy results" ``` Note: `condition: always()` on the publish step ensures results are available even when Trivy exits 1. The `--exit-code 1` on the scan step itself still fails the pipeline on HIGH/CRITICAL findings. ## Build stage steps ### Step order — always emit in this order 1. Registry login 2. docker buildx build + push ### Registry login — ECR ```yaml - script: | aws ecr get-login-password --region | \ docker login --username AWS --password-stdin displayName: "Login — ECR" env: AWS_DEFAULT_REGION: # Wire the OIDC service connection at the job level, not inside the script step. # In the job or deployment job that contains this step, set: # # job: Build # pool: EKS-Pool # container: {} # omit if not containerised # services: # ... # # For OIDC federation, the AWSCLI task approach is preferred. # Alternatively, wrap with AWSShellScript@1: # # - task: AWSShellScript@1 # inputs: # awsCredentials: # regionName: # scriptType: inline # inlineScript: | # aws ecr get-login-password --region | \ # docker login --username AWS --password-stdin # displayName: "Login — ECR (via service connection)" ``` AWS credentials come from the OIDC service connection configured on the job — do not add any `AWS_ACCESS_KEY_ID` or `AWS_SECRET_ACCESS_KEY` env vars. ### Registry login — ACR ```yaml - task: Docker@2 inputs: command: login containerRegistry: displayName: "Login — ACR" ``` ### Build and push — nonprod ```yaml - script: | docker buildx create --use --name pipeline-builder 2>/dev/null || \ docker buildx use pipeline-builder docker buildx build \ --cache-from type=registry,ref=/:cache \ --cache-to type=registry,ref=/:cache,mode=max \ --tag /:$(Build.SourceVersion) \ --tag /:latest \ --file \ --push \ . displayName: "Build and push — nonprod" ``` ### Build and push — prod ```yaml - script: | docker buildx create --use --name pipeline-builder 2>/dev/null || \ docker buildx use pipeline-builder docker buildx build \ --cache-from type=registry,ref=/:cache \ --cache-to type=registry,ref=/:cache,mode=max \ --tag /:$(Build.SourceBranchName) \ --tag /:$(Build.SourceVersion) \ --file \ --push \ . displayName: "Build and push — prod" ``` ## Tagging strategy | Tier | Tags applied | |---------|---------------------------------------------------| | Nonprod | ``, `latest` | | Prod | ``, `` | Never tag prod images as `latest`. ## Hard rules for Docker - Always use `docker buildx` — never plain `docker build` - Trivy scan must run before push — the scan in SecurityScan stage uses a locally built image, not a registry pull - `--exit-code 1` on Trivy is non-negotiable — HIGH and CRITICAL findings must fail the pipeline - Never tag prod images as `latest` — prod tags use `$(Build.SourceBranchName)` and `$(Build.SourceVersion)` only - Build args containing secrets must come from ADO variables injected via `env:` — never hardcoded in YAML - Registry layer cache lives in the registry itself (not ADO pipeline cache) for reproducibility across EKS-Pool agents - ECR login uses OIDC credentials only — never hardcode `AWS_ACCESS_KEY_ID` or `AWS_SECRET_ACCESS_KEY` - The `docker buildx create --use ... || docker buildx use ...` pattern is required to handle re-use across runs without error