Skip to content
This repository was archived by the owner on Feb 21, 2026. It is now read-only.

Build container image #10

Build container image

Build container image #10

Workflow file for this run

name: Build container image
on:
pull_request:
branches:
- main
schedule:
- cron: "0 1 * * SUN"
push:
branches:
- main
paths-ignore:
- "**/README.md"
workflow_dispatch:
env:
IMAGE_DESC: "OCI image for Homebrew"
IMAGE_KEYWORDS: "homebrew"
IMAGE_LOGO_URL: "https://avatars.githubusercontent.com/u/136393846?s=200&v=4"
IMAGE_NAME: "${{ github.event.repository.name }}"
IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}"
DEFAULT_TAG: "latest"
jobs:
build_push:
name: Build and push image
permissions:
contents: read
packages: write
id-token: write
timeout-minutes: 60
strategy:
fail-fast: true
matrix:
platform: ["amd64", "arm64"]
runs-on: ${{ matrix.platform == 'amd64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }}
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Build image
run: podman build -t "${IMAGE_NAME}:${DEFAULT_TAG}" -f ./Containerfile .
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LOGIN_USER: ${{ github.actor }}
run: |
echo "${GITHUB_TOKEN}" | podman login -u "${LOGIN_USER}" --password-stdin "ghcr.io"
echo "${GITHUB_TOKEN}" | docker login -u "${LOGIN_USER}" --password-stdin "ghcr.io"
- name: Push to GHCR
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
id: push
env:
IMAGE_REGISTRY: ${{ env.IMAGE_REGISTRY }}
IMAGE_NAME: ${{ env.IMAGE_NAME }}
IMAGE_DIGEST: ${{ steps.load.outputs.digest }}
PLATFORM: ${{ matrix.platform }}
MAX_RETRIES: 3
run: |
set -x
for i in $(seq "${MAX_RETRIES}"); do
podman push --digestfile=/tmp/digestfile "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" "${IMAGE_REGISTRY}/${IMAGE_NAME}:${DEFAULT_TAG}-${PLATFORM}" && break || sleep $((5 * i));
done
echo "remote_image_digest=$(< /tmp/digestfile)" | tee "${GITHUB_OUTPUT}"
- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
- name: Sign Image
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
env:
REMOTE_IMAGE_DIGEST: ${{ steps.push.outputs.remote_image_digest }}
COSIGN_EXPERIMENTAL: false
COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}
run: cosign sign -y --key env://COSIGN_PRIVATE_KEY "${IMAGE_REGISTRY}/${IMAGE_NAME}@${REMOTE_IMAGE_DIGEST}"
- name: Create Job Outputs
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
env:
IMAGE_NAME: ${{ env.IMAGE_NAME }}
PLATFORM: ${{ matrix.platform }}
DIGEST: ${{ steps.push.outputs.remote_image_digest }}
run: |
set -x
mkdir -p /tmp/outputs/digests
echo "${DIGEST}" | tee "/tmp/outputs/digests/${IMAGE_NAME}-${PLATFORM}.txt"
- name: Upload Output Artifacts
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: ${{ env.IMAGE_NAME }}-${{ matrix.platform }}
retention-days: 1
if-no-files-found: error
path: |
/tmp/outputs/digests/*.txt
manifest:
name: Create Manifest
runs-on: ubuntu-latest
if: always()
needs:
- build_push
container:
image: docker.io/library/alpine:latest
options: --privileged --security-opt seccomp=unconfined
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Install dependencies
run: apk add bash coreutils curl docker findutils fuse-overlayfs git grep jq podman sed
# Necessary until bootc supports Cosign v3
- name: Install old cosign
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
# Thank you Bri! :3 @b-
curl --retry 3 -H "Authorization: Bearer ${GITHUB_TOKEN}" -fsSLo "/usr/bin/cosign" "https://github.com/sigstore/cosign/releases/download/v2.6.1/cosign-linux-amd64"
chmod +x /usr/bin/cosign
- name: Exit on failure
shell: bash
env:
JOBS: ${{ toJson(needs) }}
run: |
for i in $(jq -r 'to_entries[] | .value.result' <<< "${JOBS}"); do
if [ "$i" != "success" ] && [ "$i" != "skipped" ]; then
echo "Status check not okay!"
exit 1
fi
done
# This generates a timestamp like what is defined on the ArtifactHub documentation
# E.G: 2022-02-08T15:38:15Z'
# https://artifacthub.io/docs/topics/repositories/container-images/
# https://linux.die.net/man/1/date
- name: Get current date
id: date
run: echo "date=$(date -u +%Y\-%m\-%d\T%H\:%M\:%S\Z)" | tee "${GITHUB_OUTPUT}"
- name: Image Metadata
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5
id: metadata
with:
tags: |
type=raw,value=${{ env.DEFAULT_TAG }}
type=raw,value=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}}
type=raw,value={{date 'YYYYMMDD'}}
type=ref,event=pr
type=sha,enable=${{ github.event_name == 'pull_request' }}
labels: |
containers.bootc=1
io.artifacthub.package.deprecated=false
io.artifacthub.package.keywords=${{ env.IMAGE_KEYWORDS }}
io.artifacthub.package.license=Apache-2.0
io.artifacthub.package.logo-url=${{ env.IMAGE_LOGO_URL }}
io.artifacthub.package.prerelease=false
io.artifacthub.package.readme-url=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/${{ github.sha }}/README.md
org.opencontainers.image.created=${{ steps.date.outputs.date }}
org.opencontainers.image.description=${{ env.IMAGE_DESC }}
org.opencontainers.image.documentation=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/${{ github.sha }}/README.md
org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/blob/${{ github.sha }}/Containerfile
org.opencontainers.image.title=${{ env.IMAGE_NAME }}
org.opencontainers.image.url=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/tree/${{ github.sha }}
org.opencontainers.image.vendor=${{ github.repository_owner }}
org.opencontainers.image.version=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}}
- name: Fetch Build Outputs
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
pattern: ${{ env.IMAGE_NAME }}-*
merge-multiple: true
path: /tmp/artifacts
- name: Load Outputs
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
shell: bash
id: load-outputs
run: |
set -x
OUTCSV="$(mktemp)"
for digest_file in /tmp/artifacts/*.txt; do
# Extract the platform from the file name
PLATFORM="$(basename "${digest_file}" ".txt" | sed 's/.*-//g')"
IMAGE_NAME="$(basename "${digest_file}" ".txt" | grep -Po ".*-" | sed 's/-$//')"
DIGEST="$(< "${digest_file}")"
echo "${PLATFORM},${IMAGE_NAME},${DIGEST}" | tee -a "${OUTCSV}"
done
echo "OUTPUT_CSV=$(base32 -w 0 < "${OUTCSV}")" | tee -a "${GITHUB_OUTPUT}"
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
env:
REGISTRY: ghcr.io
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | podman login -u "${{ github.actor }}" --password-stdin "${REGISTRY}"
echo "${{ secrets.GITHUB_TOKEN }}" | docker login -u "${{ github.actor }}" --password-stdin "${REGISTRY}"
- name: Publish manifest with unified architecture and sign
shell: bash
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
env:
COSIGN_EXPERIMENTAL: false
COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}
LABELS: ${{ steps.metadata.outputs.labels }}
OUTPUT_CSV: ${{ steps.load-outputs.outputs.OUTPUT_CSV }}
TAGS: ${{ steps.metadata.outputs.tags }}
run: |
set -x
OUTPUT_CSV_FILE="$(mktemp)"
echo "${OUTPUT_CSV}" | base32 -d | tee "${OUTPUT_CSV_FILE}"
# This is insanely dumb, please PR if you have a better solution ~ @tulilirockz :3c
IMAGES=$(cat "${OUTPUT_CSV_FILE}" | while IFS=$'\n' read -r ENTRY ; do echo "${ENTRY}" | cut -d, -f2 ; done | sort | uniq | tr "\n" " " | sed 's/.$//')
for TARGET_IMAGE in ${IMAGES} ; do
TARGET_MANIFEST="${IMAGE_REGISTRY}/${TARGET_IMAGE}"
podman manifest create "${TARGET_MANIFEST}"
PLATFORMS=$(grep -F -e "${IMAGE}" "${OUTPUT_CSV_FILE}" | while IFS=$'\n' read -r ENTRY ; do echo "${ENTRY}" | cut -d, -f1 ; done | sort | uniq | tr "\n" " " | sed 's/.$//')
for TARGET_PLATFORM in ${PLATFORMS} ; do
TARGET_DIGEST="$(grep -F -e ",${TARGET_IMAGE}," "${OUTPUT_CSV_FILE}" | grep -F -e "${TARGET_PLATFORM}" | cut -d, -f3 | tr -d "\n")"
podman manifest add "${TARGET_MANIFEST}" "${TARGET_MANIFEST}@${TARGET_DIGEST}" --arch "${TARGET_PLATFORM}"
done
while IFS= read -r LABEL; do
podman manifest annotate --index --annotation "${LABEL}" "${TARGET_MANIFEST}"
done <<< "${LABELS}"
while IFS= read -r TAG; do
podman manifest push --all=false --digestfile=/tmp/digestfile "${TARGET_MANIFEST}" "${TARGET_MANIFEST}:${TAG}"
done <<< "${TAGS}"
cosign sign -y --key env://COSIGN_PRIVATE_KEY "${TARGET_MANIFEST}@$(< /tmp/digestfile)"
done