Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- .woodpecker.yaml: image paths -> library/autojanet-{agent,dispatcher}
- .woodpecker.yaml: secret names RS_HARBOR_USER / RS_HARBOR_PASS (global)
- container/Dockerfile: restore COPY skills/, skills/ populated from opencode config
- skills/: 84 opencode skills bundled into image
- k8s/manifests: update image refs to library/
5.6 KiB
5.6 KiB
| name | description |
|---|---|
| azure-pipeline-docker | 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
- Registry type —
ECR|ACR - Registry URL — e.g.
123456789.dkr.ecr.us-east-1.amazonaws.comormyregistry.azurecr.io - Image repository name — e.g.
myapp/api - Dockerfile path — default
./Dockerfile - AWS region — required if ECR
- AWS service connection name — required if ECR
- ACR service connection name — required if ACR
Lint stage steps
- script: |
docker run --rm -i hadolint/hadolint < <dockerfile-path>
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.
- script: |
docker build \
-t scan-target:$(Build.SourceVersion) \
-f <dockerfile-path> \
.
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
- Registry login
- docker buildx build + push
Registry login — ECR
- script: |
aws ecr get-login-password --region <aws-region> | \
docker login --username AWS --password-stdin <registry-url>
displayName: "Login — ECR"
env:
AWS_DEFAULT_REGION: <aws-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: <aws-service-connection-name>
# regionName: <aws-region>
# scriptType: inline
# inlineScript: |
# aws ecr get-login-password --region <aws-region> | \
# docker login --username AWS --password-stdin <registry-url>
# 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
- task: Docker@2
inputs:
command: login
containerRegistry: <acr-service-connection-name>
displayName: "Login — ACR"
Build and push — nonprod
- script: |
docker buildx create --use --name pipeline-builder 2>/dev/null || \
docker buildx use pipeline-builder
docker buildx build \
--cache-from type=registry,ref=<registry-url>/<image-repo>:cache \
--cache-to type=registry,ref=<registry-url>/<image-repo>:cache,mode=max \
--tag <registry-url>/<image-repo>:$(Build.SourceVersion) \
--tag <registry-url>/<image-repo>:latest \
--file <dockerfile-path> \
--push \
.
displayName: "Build and push — nonprod"
Build and push — prod
- script: |
docker buildx create --use --name pipeline-builder 2>/dev/null || \
docker buildx use pipeline-builder
docker buildx build \
--cache-from type=registry,ref=<registry-url>/<image-repo>:cache \
--cache-to type=registry,ref=<registry-url>/<image-repo>:cache,mode=max \
--tag <registry-url>/<image-repo>:$(Build.SourceBranchName) \
--tag <registry-url>/<image-repo>:$(Build.SourceVersion) \
--file <dockerfile-path> \
--push \
.
displayName: "Build and push — prod"
Tagging strategy
| Tier | Tags applied |
|---|---|
| Nonprod | <git-sha>, latest |
| Prod | <git-tag / branch-name>, <git-sha> |
Never tag prod images as latest.
Hard rules for Docker
- Always use
docker buildx— never plaindocker build - Trivy scan must run before push — the scan in SecurityScan stage uses a locally built image, not a registry pull
--exit-code 1on 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_IDorAWS_SECRET_ACCESS_KEY - The
docker buildx create --use ... || docker buildx use ...pattern is required to handle re-use across runs without error