Skip to content

actions

actions #16

name: build-test-sign-image
on:
push:
branches: [main, master]
release:
types: [published]
workflow_dispatch:
# Needed for pushing to GHCR + keyless cosign signing (OIDC)
permissions:
contents: read
packages: write
id-token: write
env:
REGISTRY: ghcr.io
jobs:
build_test_and_release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# Docker repository names must be lowercase
- name: Set image name (lowercase)
id: image
run: echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Set up QEMU (optional)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
logout: false
# Log in to Azure CR with token that can pull the base image (ARACHNE_DOCKER_REGISTRY_TOKEN).
- name: Log in to Azure CR (pull base image)
uses: docker/login-action@v3
with:
registry: executionengine.azurecr.io
username: github-actions-push
password: ${{ secrets.ARACHNE_DOCKER_REGISTRY_TOKEN }}
logout: false
# Try to pull base image; only build if pull fails (e.g. first run or base not yet published).
- name: Try pull base image
id: pull_base
run: |
BASE_IMAGE="executionengine.azurecr.io/${{ steps.image.outputs.name }}-base:latest"
if docker pull "$BASE_IMAGE"; then
docker tag "$BASE_IMAGE" examplestudy-base:latest
echo "need_build=false" >> $GITHUB_OUTPUT
else
echo "need_build=true" >> $GITHUB_OUTPUT
fi
# Build base image only when it could not be pulled.
- name: Build base image (CI / local load)
if: steps.pull_base.outputs.need_build == 'true'
run: |
docker buildx build \
--load \
--file ./Dockerfile.base \
--tag examplestudy-base:latest \
--cache-from type=gha \
--cache-to type=gha,mode=max \
--platform linux/amd64 \
.
# Re-login to Azure CR with token that can push (for release step).
- name: Log in to Azure Container Registry (push)
uses: docker/login-action@v3
with:
registry: executionengine.azurecr.io
username: github-actions-push
password: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}
logout: false
# When we built the base locally, push it so future runs can pull it.
- name: Push base image to Azure CR
if: steps.pull_base.outputs.need_build == 'true'
run: |
BASE_REPO="executionengine.azurecr.io/${{ steps.image.outputs.name }}-base:latest"
docker tag examplestudy-base:latest "$BASE_REPO"
docker push "$BASE_REPO"
# Produces tags + labels (commit SHA, semver if you use tags, etc.)
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
tags: |
type=sha,format=long
type=ref,event=branch
type=ref,event=tag
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
# 1b) Build study image (LOAD) so we can run tests inside it.
# Attestations (sbom/provenance) require pushing, so we do that only after tests pass.
- name: Build study image (CI / local load)
id: build_ci
run: |
set -o pipefail
docker buildx build \
--progress=plain \
--load \
--file ./Dockerfile \
--build-arg BASE_IMAGE=examplestudy-base:latest \
--tag ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}:ci-${{ github.sha }} \
--label "org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}" \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--cache-from type=gha \
--cache-to type=gha,mode=max \
--platform linux/amd64 \
. 2>&1 | tee build.log
- name: Upload build log
uses: actions/upload-artifact@v4
if: always()
with:
name: docker-build-log
path: build.log
retention-days: 30
if-no-files-found: ignore
# Run your build/test script INSIDE the built image.
# Assumes your image contains your package source (COPY . ...) and that this script exists.
- name: Run build/test script inside image
run: |
set -euo pipefail
docker run --rm \
${{ env.REGISTRY }}/${{ steps.image.outputs.name }}:ci-${{ github.sha }} \
Rscript -f tests/build_test.R
# Optional (but common): scan the image before release
- name: Trivy scan (fail on HIGH/CRITICAL)
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}:ci-${{ github.sha }}
format: table
ignore-unfixed: true
vuln-type: os,library
severity: HIGH,CRITICAL
exit-code: "1"
# 2) Release build (PUSH) to GHCR and Azure CR with SBOM + provenance attestations.
- name: Build & push (release + attestations)
id: build_release
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
build-args: BASE_IMAGE=examplestudy-base:latest
platforms: linux/amd64
push: true
tags: |
${{ steps.meta.outputs.tags }}
executionengine.azurecr.io/${{ steps.image.outputs.name }}:sha-${{ github.sha }}
executionengine.azurecr.io/${{ steps.image.outputs.name }}:${{ github.ref_name }}
labels: ${{ steps.meta.outputs.labels }}
# Supply-chain metadata
provenance: true
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
# Keyless signing using GitHub OIDC (no long-lived keys).
- name: Install cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: v2.4.1
- name: Sign image digest (keyless)
env:
COSIGN_EXPERIMENTAL: "false"
run: |
set -euo pipefail
IMAGE="${{ env.REGISTRY }}/${{ steps.image.outputs.name }}"
DIGEST="${{ steps.build_release.outputs.digest }}"
cosign sign --yes "${IMAGE}@${DIGEST}"
# Optional: sign the SBOM/provenance attestations too (recommended if you plan to verify them client-side)
- name: Sign attestations (keyless)
run: |
set -euo pipefail
IMAGE="${{ env.REGISTRY }}/${{ steps.image.outputs.name }}"
DIGEST="${{ steps.build_release.outputs.digest }}"
# This signs the attached attestations (provenance/SBOM) for that digest.
cosign sign-attestation --yes "${IMAGE}@${DIGEST}"