Skip to content
126 changes: 126 additions & 0 deletions .github/workflows/publish_docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Builds and publishes Docker image to GitHub Container Registry (ghcr.io)
#
# This workflow is gated on successful tests:
# - For pushes to main: waits for "Build and Test" workflow to complete successfully
# - For version tags (v*): runs directly (assumes tag is cut from tested code)
# - For pull requests: builds only (no push) to validate Dockerfile before merge
# - For manual dispatch: runs directly (operator takes responsibility)
#
# Published images are available at: ghcr.io/<owner>/aibs-informatics-aws-lambda

name: Build and Publish Docker Image

on:
# Trigger after the test workflow completes on main branch.
# Note: workflow_run triggers on completion (success OR failure), so we check
# the conclusion in the job's `if` condition below.
workflow_run:
workflows: ["Build and Test"]
types: [completed]
branches: [main]

# Version tags trigger directly (no test gate) - assumes tags are cut from
# tested commits on main.
push:
tags:
- 'v*'

# Build (but don't push) on pull requests to validate the Dockerfile.
pull_request:
branches: [main]

# Allow manual trigger for debugging or re-running failed builds.
workflow_dispatch:

# Prevent out-of-order publishing when multiple workflow_run events complete.
# If a newer run starts while an older one is in progress, cancel the older one.
concurrency:
group: docker-publish-${{ github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
Comment thread
rpmcginty marked this conversation as resolved.
Comment on lines +37 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know about this mechanism -- cool


env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push:
name: Build and Push Docker Image
runs-on: ubuntu-latest
timeout-minutes: 30

# Gate logic:
# - workflow_run events: only proceed if the triggering workflow succeeded
# - tag pushes / manual dispatch: always proceed (no workflow_run event)
if: >
github.event_name != 'workflow_run' ||
github.event.workflow_run.conclusion == 'success'

permissions:
contents: read # Required to checkout the repository
packages: write # Required to push to GitHub Container Registry

steps:
# For workflow_run events, checkout the SHA that was actually tested,
# not the current HEAD (which may have moved). For other events, use github.sha.
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}

# Configure SSH agent and git credentials for accessing private
# AllenInstitute repositories during the Docker build.
# This step is optional - the composite action handles missing secrets gracefully.
- name: Set up AllenInstitute Repo Authorization
uses: ./.github/actions/configure-org-repo-authorization
with:
token: ${{ secrets.AI_PACKAGES_TOKEN }}
ssh_private_key: ${{ secrets.AIBSGITHUB_PRIVATE_KEY }}
Comment on lines +73 to +77
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't seem to need this when I wrote a similar workflow.


# Buildx enables advanced features like caching and multi-platform builds.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

# Skip login for PRs since we won't push the image.
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Generate Docker tags based on git context:
# - "latest" for default branch (main)
# - branch name (e.g., "main")
# - PR number (e.g., "pr-123") - used for build validation only
# - semver tags (e.g., "1.2.3", "1.2")
# - short SHA (e.g., "sha-abc1234")
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
Comment on lines +103 to +109
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this workflow is triggered via workflow_run, runs can complete out of order (an older main commit’s tests may finish after a newer one). With latest enabled on the default branch, a late publish from an older SHA can overwrite the latest tag with an older image. To avoid this, add a guard that only publishes latest (or publishes at all) when the workflow_run.head_sha matches the current HEAD of main (e.g., compare against refs/heads/main via git ls-remote/GitHub API), or otherwise skip/cancel stale runs.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add concurrency with cancel-in-progress set to true


# Build the image; only push for non-PR events.
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Forward SSH agent to builder for private repo access during build
# (used by `RUN --mount=type=ssh` in Dockerfile). Only set if SSH agent is available.
ssh: ${{ env.SSH_AUTH_SOCK && format('default={0}', env.SSH_AUTH_SOCK) || '' }}
Comment thread
rpmcginty marked this conversation as resolved.
# Use GitHub Actions cache for Docker layers to speed up rebuilds
cache-from: type=gha
cache-to: type=gha,mode=max
Loading