name: Docker
on:
push:
branches: [main]
tags:
- "v*"
pull_request:
branches: [main]
paths:
- ".github/workflows/docker.yml"
- "Dockerfile"
- "rust-toolchain.toml"
- "scripts/check_static_image.py"
- "scripts/smoke_container_https.py"
- ".dockerignore"
- "Cargo.toml"
- "Cargo.lock"
- "src/**"
- "crates/**"
schedule:
- cron: "0 4 * * 1"
workflow_dispatch:
inputs:
release-prep:
description: Set by Auto Release when this build publishes the runtime image for a committed version bump
required: false
default: false
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
packages: read
env:
REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
DOCKERHUB_NAMESPACE: ${{ vars.RUST_TEMPLATE_DOCKERHUB_NAMESPACE || 'threatflux' }}
PUBLISH_DOCKERHUB: ${{ vars.RUST_TEMPLATE_PUBLISH_DOCKERHUB || 'false' }}
DOCKER_BUILDKIT: 1
BINARY_NAME: ${{ vars.RUST_TEMPLATE_BINARY_NAME || 'github-actions-maintainer' }}
BINARY_PACKAGE: ${{ vars.RUST_TEMPLATE_BINARY_PACKAGE || '' }}
SBOM_MANIFEST_PATH: ${{ vars.RUST_TEMPLATE_SBOM_MANIFEST_PATH || 'Cargo.toml' }}
jobs:
build:
name: Build Docker Image
runs-on: ${{ vars.RUST_TEMPLATE_RUNNER_UBUNTU || 'ubuntu-latest' }}
permissions:
contents: read
packages: write
security-events: write
outputs:
digest: ${{ steps.build.outputs.digest }}
image_name: ${{ steps.lowercase.outputs.image_name }}
dockerhub_enabled: ${{ steps.images.outputs.dockerhub_enabled }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Lowercase image name
id: lowercase
run: echo "image_name=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: Lowercase repo name
id: dockerhub_repo
run: |
REPO_NAME="${GITHUB_REPOSITORY#*/}"
echo "name=${REPO_NAME,,}" >> "$GITHUB_OUTPUT"
- name: Detect Rust toolchain version
id: rust_toolchain
run: |
TOOLCHAIN_VERSION="$(awk -F '"' '/^channel[[:space:]]*=/ { print $2; exit }' rust-toolchain.toml)"
if [[ -z "${TOOLCHAIN_VERSION}" ]]; then
echo "Unable to detect Rust toolchain version from rust-toolchain.toml" >&2
exit 1
fi
echo "version=${TOOLCHAIN_VERSION}" >> "$GITHUB_OUTPUT"
- name: Check Docker Hub credentials
id: dockerhub
if: github.event_name != 'pull_request' && env.PUBLISH_DOCKERHUB == 'true'
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
if [[ -n "${DOCKERHUB_USERNAME}" && -n "${DOCKERHUB_TOKEN}" ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::RUST_TEMPLATE_PUBLISH_DOCKERHUB is true, but DOCKERHUB_USERNAME or DOCKERHUB_TOKEN is not set; publishing to ${REGISTRY} only"
echo "enabled=false" >> "$GITHUB_OUTPUT"
fi
- name: Resolve image targets
id: images
env:
IMAGE_NAME: ${{ steps.lowercase.outputs.image_name }}
DOCKERHUB_REPO: ${{ steps.dockerhub_repo.outputs.name }}
DOCKERHUB_ENABLED: ${{ steps.dockerhub.outputs.enabled == 'true' }}
run: |
{
echo "images<<EOF"
echo "${REGISTRY}/${IMAGE_NAME}"
if [[ "${DOCKERHUB_ENABLED}" == "true" ]]; then
echo "${DOCKERHUB_REGISTRY}/${DOCKERHUB_NAMESPACE}/${DOCKERHUB_REPO}"
fi
echo "EOF"
echo "dockerhub_enabled=${DOCKERHUB_ENABLED}"
} >> "$GITHUB_OUTPUT"
- name: Select build platforms
id: platforms
run: |
echo "value=linux/amd64,linux/arm64" >> "$GITHUB_OUTPUT"
echo "needs_qemu=true" >> "$GITHUB_OUTPUT"
- name: Set up QEMU
if: steps.platforms.outputs.needs_qemu == 'true'
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 with:
image: tonistiigi/binfmt:qemu-v10.2.3@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 with:
version: v0.39.0
driver-opts: image=moby/buildkit:v0.35.0@sha256:aa632b921ef07411d2cb77292cd34213d4f7b384001d6fceef1e1f25bf9752bb
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Docker Hub
if: steps.images.outputs.dockerhub_enabled == 'true'
uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract runtime metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 with:
images: ${{ steps.images.outputs.images }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}
- name: Extract base image metadata
id: base_meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 with:
images: ${{ steps.images.outputs.images }}
tags: |
type=raw,value=base-rust-${{ steps.rust_toolchain.outputs.version }}
type=raw,value=base-rust-latest,enable={{is_default_branch}}
- name: Build base image
if: github.event_name != 'pull_request'
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc with:
context: .
target: rust-base
platforms: ${{ steps.platforms.outputs.value }}
push: true
tags: ${{ steps.base_meta.outputs.tags }}
labels: ${{ steps.base_meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
BUILD_DATE=${{ github.event.repository.updated_at }}
VCS_REF=${{ github.sha }}
BINARY_NAME=${{ env.BINARY_NAME }}
BINARY_PACKAGE=${{ env.BINARY_PACKAGE }}
SBOM_MANIFEST_PATH=${{ env.SBOM_MANIFEST_PATH }}
- name: Build and export (PRs)
if: github.event_name == 'pull_request'
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc with:
context: .
platforms: linux/amd64
tags: ${{ steps.lowercase.outputs.image_name }}:scan
labels: ${{ steps.meta.outputs.labels }}
outputs: type=docker,dest=/tmp/image.tar
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
BUILD_DATE=${{ github.event.repository.updated_at }}
VCS_REF=${{ github.sha }}
BINARY_NAME=${{ env.BINARY_NAME }}
BINARY_PACKAGE=${{ env.BINARY_PACKAGE }}
SBOM_MANIFEST_PATH=${{ env.SBOM_MANIFEST_PATH }}
- name: Build and push runtime image (non-PR)
if: github.event_name != 'pull_request'
id: build
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc with:
context: .
platforms: ${{ steps.platforms.outputs.value }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
BUILD_DATE=${{ github.event.repository.updated_at }}
VCS_REF=${{ github.sha }}
BINARY_NAME=${{ env.BINARY_NAME }}
BINARY_PACKAGE=${{ env.BINARY_PACKAGE }}
SBOM_MANIFEST_PATH=${{ env.SBOM_MANIFEST_PATH }}
- name: Upload image artifact
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 with:
name: docker-image
path: /tmp/image.tar
if-no-files-found: error
retention-days: 1
scan:
name: Security Scan
needs: build
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ${{ vars.RUST_TEMPLATE_RUNNER_UBUNTU || 'ubuntu-latest' }}
- arch: arm64
runner: ubuntu-24.04-arm
permissions:
contents: read
packages: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Download image (PRs)
if: github.event_name == 'pull_request' && matrix.arch == 'amd64'
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 with:
name: docker-image
path: /tmp
- name: Load image (PRs)
if: github.event_name == 'pull_request' && matrix.arch == 'amd64'
run: docker load -i /tmp/image.tar
- name: Build native ARM64 PR image
if: github.event_name == 'pull_request' && matrix.arch == 'arm64'
env:
IMAGE_NAME: ${{ needs.build.outputs.image_name }}
VERSION: ${{ github.ref_name }}
BUILD_DATE: ${{ github.event.repository.updated_at }}
VCS_REF: ${{ github.sha }}
run: |
docker build --tag "${IMAGE_NAME}:scan" \
--build-arg "VERSION=${VERSION}" \
--build-arg "BUILD_DATE=${BUILD_DATE}" \
--build-arg "VCS_REF=${VCS_REF}" \
--build-arg "BINARY_NAME=${BINARY_NAME}" \
--build-arg "BINARY_PACKAGE=${BINARY_PACKAGE}" \
--build-arg "SBOM_MANIFEST_PATH=${SBOM_MANIFEST_PATH}" .
- name: Get short SHA
id: sha
run: echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Log in to registry
if: github.event_name != 'pull_request'
uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull image
if: github.event_name != 'pull_request'
run: docker pull ${{ env.REGISTRY }}/${{ needs.build.outputs.image_name }}:${{ steps.sha.outputs.short }}
- name: Validate static runtime and HTTPS API behavior
env:
IMAGE: ${{ github.event_name == 'pull_request' && format('{0}:scan', needs.build.outputs.image_name) || format('{0}/{1}:{2}', env.REGISTRY, needs.build.outputs.image_name, steps.sha.outputs.short) }}
run: |
python3 scripts/check_static_image.py "${IMAGE}"
python3 scripts/smoke_container_https.py "${IMAGE}"
- name: Run Trivy scanner
id: trivy
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 with:
version: v0.76.2
image-ref: >-
${{ github.event_name == 'pull_request' && format('{0}:scan', needs.build.outputs.image_name) ||
format('{0}/{1}:{2}', env.REGISTRY, needs.build.outputs.image_name, steps.sha.outputs.short) }}
format: sarif
output: trivy-results.sarif
severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL
vuln-type: os,library
exit-code: '1'
limit-severities-for-sarif: true
- name: Validate native Trivy report
run: |
python3 - <<'PY'
import json
from pathlib import Path
payload = json.loads(Path("trivy-results.sarif").read_text())
assert payload.get("version") == "2.1.0", "invalid SARIF version"
assert payload.get("runs"), "missing native scanner runs"
PY
- name: Upload AMD64 results with existing analysis identity
if: always() && matrix.arch == 'amd64' && hashFiles('trivy-results.sarif') != ''
uses: github/codeql-action/upload-sarif@33d7b85da1a04171e211befe2c739303b9a4c066 env:
CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/docker.yml:scan
with:
sarif_file: trivy-results.sarif
category: .github/workflows/docker.yml:scan
matrix: '{}'
- name: Upload independent ARM64 results
if: always() && matrix.arch == 'arm64' && hashFiles('trivy-results.sarif') != ''
uses: github/codeql-action/upload-sarif@33d7b85da1a04171e211befe2c739303b9a4c066 with:
sarif_file: trivy-results.sarif
category: docker-arm64
sign:
name: Sign Container
needs: [build, scan]
runs-on: ${{ vars.RUST_TEMPLATE_RUNNER_UBUNTU || 'ubuntu-latest' }}
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Install cosign
uses: sigstore/cosign-installer@756fe2d284c7292c4e9b62c8a28f1e5629c59532 with:
cosign-release: v3.1.4
- name: Log in to registry
uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Sign container image
run: cosign sign --yes ${{ env.REGISTRY }}/${{ needs.build.outputs.image_name }}@${{ needs.build.outputs.digest }}
env:
COSIGN_EXPERIMENTAL: 1
- name: Log in to Docker Hub
if: needs.build.outputs.dockerhub_enabled == 'true'
uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Sign Docker Hub image
if: needs.build.outputs.dockerhub_enabled == 'true'
env:
DIGEST: ${{ needs.build.outputs.digest }}
run: |
set -euo pipefail
repo_name="${GITHUB_REPOSITORY#*/}"
cosign sign --yes "${DOCKERHUB_REGISTRY}/${DOCKERHUB_NAMESPACE}/${repo_name,,}@${DIGEST}"
release-handoff:
name: Hand Off to Auto Release
needs: [build, scan, sign, sbom]
if: inputs.release-prep && github.ref == 'refs/heads/main'
runs-on: ${{ vars.RUST_TEMPLATE_RUNNER_UBUNTU || 'ubuntu-latest' }}
permissions:
actions: write
steps:
- name: Dispatch the release tag phase
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "published runtime image ${{ needs.build.outputs.digest }} for ${GITHUB_SHA}"
gh workflow run auto-release.yml --repo "$GITHUB_REPOSITORY" --ref main -f phase=tag -f version_bump=auto
sbom:
name: Generate SBOM
needs: build
runs-on: ${{ vars.RUST_TEMPLATE_RUNNER_UBUNTU || 'ubuntu-latest' }}
if: github.event_name != 'pull_request'
steps:
- name: Get short SHA
id: sha
run: echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Log in to registry
uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate SBOM
uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c with:
syft-version: v1.57.1
image: ${{ env.REGISTRY }}/${{ needs.build.outputs.image_name }}:${{ steps.sha.outputs.short }}
artifact-name: sbom.spdx.json
format: spdx-json