autojanet/skills/azure-pipeline-docker/SKILL.md
Zoë cc74ad0bd0
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
fix: use library/ Harbor project, add skills, fix pipeline secrets
- .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/
2026-05-30 15:43:14 -07:00

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

  1. Registry typeECR | 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

- 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

  1. Registry login
  2. 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 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