Docker Build and Push

docker-build-push@1.0.0 Template

Build a container image, push it to any registry, and pass the pinned digest to the next job.

Start from it

flightstack template use docker-build-push

What it does

docker build then docker push every tag to Artifact Registry, GHCR, Docker Hub, ECR or your own registry, logging in from the credentials the agent holds. Emits imageRef (image@sha256 digest) so a deploy job ships exactly the bytes that were built.

Inputs

image string required
Image name without a tag, e.g. us-central1-docker.pkg.dev/my-project/containers/api or ghcr.io/acme/api.
tags array
Tags to apply and push. Defaults to the short commit SHA.
context string default .
Build context directory.
dockerfile string default Dockerfile
Path to the Dockerfile, relative to the repo root.
platform string
Target platform, e.g. linux/amd64. Set it when building on an Apple Silicon agent for an amd64 host like Cloud Run.
buildArgs object
Build arguments (--build-arg). Visible in image history - never a secret.
push boolean default true
Push after building. Off builds only, e.g. to check a Dockerfile on pull requests.
login auto | none default auto
auto logs in using REGISTRY_USERNAME/REGISTRY_PASSWORD when set, GITHUB_TOKEN for ghcr.io, gcloud for *.pkg.dev and gcr.io, and the AWS CLI for ECR. none relies on the agent's existing docker login.

Definition

name: Docker Build and Push
description: >-
  Build a container image and push it to any registry - Artifact Registry,
  GHCR, Docker Hub, ECR or your own - and hand the pinned image digest to the
  next job.

triggers:
  - type: push
    branches: [main]

jobs:
  image:
    name: Build image
    type: plugin/docker-build-push
    with:
      image: us-central1-docker.pkg.dev/my-project/containers/api
      tags:
        - "${{ context.commitShort }}"
        - latest

plugin:
  name: docker-build-push
  displayName: Docker Build and Push
  description: >-
    `docker build` then `docker push` every tag, logging in to the registry
    from the credentials the agent holds. Emits `imageRef` (image@sha256
    digest) so a deploy job ships exactly the bytes that were built.
  category: build

  supportedPlatforms:
    - linux
    - macos

  parameters:
    type: object
    properties:
      image:
        type: string
        description: >-
          Image name without a tag, e.g.
          us-central1-docker.pkg.dev/my-project/containers/api or ghcr.io/acme/api.
      tags:
        type: array
        description: Tags to apply and push. Defaults to the short commit SHA.
      context:
        type: string
        default: "."
        description: Build context directory.
      dockerfile:
        type: string
        default: Dockerfile
        description: Path to the Dockerfile, relative to the repo root.
      platform:
        type: string
        default: ""
        description: >-
          Target platform, e.g. linux/amd64. Set it when building on an Apple
          Silicon agent for an amd64 host like Cloud Run.
      buildArgs:
        type: object
        description: Build arguments (--build-arg). Visible in image history - never a secret.
      push:
        type: boolean
        default: true
        description: Push after building. Off builds only, e.g. to check a Dockerfile on pull requests.
      login:
        type: string
        enum: [auto, none]
        default: auto
        description: >-
          `auto` logs in using REGISTRY_USERNAME/REGISTRY_PASSWORD when set,
          GITHUB_TOKEN for ghcr.io, gcloud for *.pkg.dev and gcr.io, and the AWS
          CLI for ECR. `none` relies on the agent's existing docker login.
    required: [image]

  script: |
    set -euo pipefail

    if ! command -v docker >/dev/null 2>&1; then
      echo "docker is not installed on this agent." >&2
      exit 1
    fi

    image="{{image}}"
    registry="${image%%/*}"
    case "$registry" in
      *.*|*:*|localhost) ;;
      *) registry="docker.io" ;;
    esac

    if [ "{{login}}" = "auto" ] && [ "{{push}}" = "true" ]; then
      if [ -n "${REGISTRY_USERNAME:-}" ] && [ -n "${REGISTRY_PASSWORD:-}" ]; then
        echo "--- docker login $registry (REGISTRY_USERNAME)"
        printf '%s' "$REGISTRY_PASSWORD" | docker login "$registry" --username "$REGISTRY_USERNAME" --password-stdin
      else
        case "$registry" in
          ghcr.io)
            if [ -n "${GITHUB_TOKEN:-}" ]; then
              echo "--- docker login ghcr.io (GITHUB_TOKEN)"
              printf '%s' "$GITHUB_TOKEN" | docker login ghcr.io --username "${GITHUB_ACTOR:-flightstack}" --password-stdin
            fi
            ;;
          *.pkg.dev|gcr.io|*.gcr.io)
            if [ -n "${GOOGLE_APPLICATION_CREDENTIALS:-}" ] && command -v gcloud >/dev/null 2>&1; then
              gcloud auth activate-service-account --key-file "$GOOGLE_APPLICATION_CREDENTIALS" --quiet
            fi
            echo "--- gcloud auth configure-docker $registry"
            gcloud auth configure-docker "$registry" --quiet
            ;;
          *.dkr.ecr.*.amazonaws.com)
            region="$(echo "$registry" | cut -d. -f4)"
            echo "--- docker login ECR ($region)"
            aws ecr get-login-password --region "$region" | docker login "$registry" --username AWS --password-stdin
            ;;
        esac
      fi
    fi

    tags=()
    {{#each tags}}
    tags+=("{{this}}")
    {{/each}}
    if [ "${#tags[@]}" -eq 0 ]; then
      tags=("$(git rev-parse --short HEAD)")
    fi

    args=(build --file "{{dockerfile}}")
    platform="{{platform}}"
    if [ -n "$platform" ]; then args+=(--platform "$platform"); fi
    {{#each buildArgs}}
    args+=(--build-arg "{{@key}}={{this}}")
    {{/each}}
    for t in "${tags[@]}"; do args+=(--tag "$image:$t"); done
    args+=("{{context}}")

    echo "--- docker ${args[*]}"
    docker "${args[@]}"

    first="$image:${tags[0]}"
    fs-output image "$image"
    fs-output tag "${tags[0]}"

    if [ "{{push}}" != "true" ]; then
      echo "push is off - built $first only"
      fs-output imageRef "$first"
      exit 0
    fi

    digest=""
    for t in "${tags[@]}"; do
      echo "--- docker push $image:$t"
      out="$(docker push "$image:$t" 2>&1 | tee /dev/stderr)"
      d="$(printf '%s\n' "$out" | sed -n 's/.*digest: \(sha256:[0-9a-f]*\).*/\1/p' | tail -1)"
      if [ -n "$d" ]; then digest="$d"; fi
    done

    if [ -z "$digest" ]; then
      echo "Pushed, but could not read the digest from docker push output; falling back to the tag." >&2
      fs-output imageRef "$first"
    else
      echo "pushed $image@$digest"
      fs-output digest "$digest"
      fs-output imageRef "$image@$digest"
    fi