Skip to content

🐋 Production Docker Build • v1.0.0 #2

🐋 Production Docker Build • v1.0.0

🐋 Production Docker Build • v1.0.0 #2

Workflow file for this run

# .github/workflows/docker-prod.yml
name: 🐋 Docker Production Image
run-name: 🐋 Production Docker Build • ${{ inputs.tag || github.ref_name }}
# Production images are release artifacts. They are built only from existing,
# non-empty annotated SemVer tags. Stable tags update the matching major,
# minor, and `latest` aliases; prerelease tags publish only their exact version.
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: Existing annotated release tag, such as v1.0.0 or v1.0.0-rc.1
required: true
type: string
permissions:
contents: read
packages: write
jobs:
docker-prod:
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.tag || github.ref }}
- name: 🐍 Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.14"
- name: 🔖 Resolve and validate Path Header Scanner build version
id: version
shell: bash
env:
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
run: |
python -m pip install --disable-pip-version-check packaging
TAG_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+([.-](alpha|beta|rc|dev|post)\.[0-9]+)?(\+[0-9A-Za-z.]+)?$'
STABLE_TAG_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+$'
TAG="$RELEASE_TAG"
PUBLISH_ALIASES=false
PUBLISH_LATEST=false
if [[ ! "$TAG" =~ $TAG_REGEX ]]; then
echo "Invalid Path Header Scanner release tag: $TAG" >&2
exit 1
fi
if ! git show-ref --verify --quiet "refs/tags/$TAG"; then
echo "Release tag does not exist in the repository: $TAG" >&2
exit 1
fi
if [[ "$(git cat-file -t "refs/tags/$TAG")" != "tag" ]]; then
echo "Production images require an annotated Git tag: $TAG" >&2
exit 1
fi
TAG_MESSAGE="$(git tag -l --format='%(contents)' "$TAG")"
if [[ -z "${TAG_MESSAGE//[[:space:]]/}" ]]; then
echo "Annotated release tag has an empty message: $TAG" >&2
exit 1
fi
VERSION="$(python -m app.core.build.version)"
EXPECTED_VERSION="$(python -c 'from packaging.version import Version; import sys; print(Version(sys.argv[1].removeprefix("v")).public)' "$TAG")"
if [[ -z "$VERSION" || "$VERSION" != "$EXPECTED_VERSION" ]]; then
echo "Resolved build version '$VERSION' does not match release tag '$TAG' ('$EXPECTED_VERSION')." >&2
exit 1
fi
if [[ "$TAG" =~ $STABLE_TAG_REGEX ]]; then
PUBLISH_ALIASES=true
PUBLISH_LATEST=true
fi
IMAGE_TAG="${TAG//+/-}"
VERSION_CORE="${TAG#v}"
VERSION_CORE="${VERSION_CORE%%+*}"
IFS='.' read -r MAJOR MINOR _ <<< "$VERSION_CORE"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
echo "publish_aliases=$PUBLISH_ALIASES" >> "$GITHUB_OUTPUT"
echo "publish_latest=$PUBLISH_LATEST" >> "$GITHUB_OUTPUT"
- name: 🐳 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🔐 Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 🔤 Normalize image name
id: vars
shell: bash
run: |
echo "image_name=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: 🏷️ Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ steps.vars.outputs.image_name }}
tags: |
type=raw,value=${{ steps.version.outputs.image_tag }}
type=raw,value=v${{ steps.version.outputs.major }}.${{ steps.version.outputs.minor }},enable=${{ steps.version.outputs.publish_aliases }}
type=raw,value=v${{ steps.version.outputs.major }},enable=${{ steps.version.outputs.publish_aliases }}
type=raw,value=latest,enable=${{ steps.version.outputs.publish_latest }}
- name: 🏗️ Build and push production image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
target: production
build-args: |
PHS_BUILD_VERSION=${{ steps.version.outputs.version }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max