Build container image #145
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: Build container image | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| paths-ignore: | |
| - 'README.md' | |
| - 'docs/**' | |
| schedule: | |
| - cron: '00 05 * * 0' # 5:00am UTC Sunday; about 1:00am Eastern during daylight time | |
| push: | |
| branches: | |
| - main | |
| paths-ignore: | |
| - '**/README.md' | |
| - 'docs/**' | |
| workflow_dispatch: | |
| env: | |
| IMAGE_NAME: "${{ github.event.repository.name }}" # output image name, usually same as repo name | |
| IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" # do not edit | |
| DEFAULT_TAG: "latest" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| build_push: | |
| name: Build and push image | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 90 | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Prepare environment | |
| run: | | |
| # Lowercase the image uri | |
| echo "IMAGE_REGISTRY=${IMAGE_REGISTRY,,}" >> ${GITHUB_ENV} | |
| echo "IMAGE_NAME=${IMAGE_NAME,,}" >> ${GITHUB_ENV} | |
| # These stage versions are pinned by https://github.com/renovatebot/renovate | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # This is optional, but if you see that your builds are way too big for the runners, you can enable this by uncommenting the following lines: | |
| - name: Maximize build space | |
| uses: ublue-os/remove-unwanted-software@695eb75bc387dbcd9685a8e72d23439d8686cba6 | |
| # Ubuntu 24.04's Podman does not preserve Chunkah's layer annotations | |
| # when pushing. Use the same newer toolchain as Aurora until the hosted | |
| # runner includes a sufficiently new Podman release. The step skips | |
| # itself once the runner's podman major version is >= 5, so it retires | |
| # automatically when GitHub updates the hosted image. | |
| - name: Update Podman | |
| run: | | |
| set -eux | |
| need_major=5 | |
| have_major=$(podman --version | grep -oE '[0-9]+' | head -n 1) | |
| if [ "${have_major}" -ge "${need_major}" ]; then | |
| echo "Runner podman is already >= ${need_major}; skipping cross-release install" | |
| exit 0 | |
| fi | |
| echo "deb http://azure.archive.ubuntu.com/ubuntu resolute universe main" | sudo tee /etc/apt/sources.list.d/resolute.list | |
| sudo apt-get update | |
| sudo apt-get install -y --allow-downgrades \ | |
| crun/resolute \ | |
| buildah/resolute \ | |
| podman/resolute \ | |
| skopeo/resolute | |
| podman --version | |
| # GitHub-hosted runners keep most of their free space on /mnt (~66G), | |
| # while / has far less headroom. Building the image and then re-layering | |
| # it with Chunkah keeps two copies of the root filesystem in podman's | |
| # storage at once, which can exhaust /. Relocate podman's rootless | |
| # storage onto /mnt so the rechunk has room. The image-copy temp dir is | |
| # deliberately left on / (freed by relocating storage) so the two large | |
| # consumers land on separate disks instead of competing for one. | |
| - name: Move container storage to the large runner disk | |
| run: | | |
| set -eux | |
| sudo mkdir -p /mnt/containers | |
| sudo chown "$(id -u):$(id -g)" /mnt/containers | |
| mkdir -p "${HOME}/.local/share" | |
| rm -rf "${HOME}/.local/share/containers" | |
| ln -s /mnt/containers "${HOME}/.local/share/containers" | |
| df -h / /mnt | |
| - name: Get current date | |
| id: date | |
| run: | | |
| echo "date=$(date -u +%Y\-%m\-%d\T%H\:%M\:%S\Z)" >> $GITHUB_OUTPUT | |
| - name: Image Metadata | |
| uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 | |
| id: metadata | |
| with: | |
| # This generates all the tags for your image, you can add custom tags here too! | |
| # Default tags are "$DEFAULT_TAG" and "$DEFAULT_TAG.$date". | |
| tags: | | |
| type=raw,value=${{ env.DEFAULT_TAG }} | |
| type=raw,value=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}} | |
| type=raw,value={{date 'YYYYMMDD'}} | |
| type=sha,enable=${{ github.event_name == 'pull_request' }} | |
| type=ref,event=pr | |
| labels: | | |
| org.opencontainers.image.created=${{ steps.date.outputs.date }} | |
| org.opencontainers.image.description=Aurora DX image with ZFS support | |
| 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'}} | |
| containers.bootc=1 | |
| sep-tags: " " | |
| sep-annotations: " " | |
| - name: Build Image | |
| id: build_image | |
| uses: redhat-actions/buildah-build@7a95fa7ee0f02d552a32753e7414641a04307056 # v2 | |
| with: | |
| containerfiles: | | |
| ./Containerfile | |
| # Postfix image name with -custom to make it a little more descriptive | |
| # Syntax: https://docs.github.com/en/actions/learn-github-actions/expressions#format | |
| image: ${{ env.IMAGE_NAME }} | |
| tags: ${{ steps.metadata.outputs.tags }} | |
| labels: ${{ steps.metadata.outputs.labels }} | |
| oci: false | |
| # Post-process the completed image so the ZFS and replacement-kernel | |
| # content is chunked along with the inherited Aurora filesystem. | |
| - name: Rechunk Image with Chunkah | |
| env: | |
| CHUNKAH_IMAGE: quay.io/coreos/chunkah:v0.6.0@sha256:ff8b8b466a942ec6000445d4001fc661e2fc5a952ad9ee29b4de9ab09d1d1708 | |
| run: | | |
| set -euo pipefail | |
| source_image="localhost/${IMAGE_NAME}:${DEFAULT_TAG}" | |
| chunked_image="localhost/${IMAGE_NAME}:chunkah" | |
| export CHUNKAH_CONFIG_STR | |
| CHUNKAH_CONFIG_STR="$(podman inspect "${source_image}")" | |
| podman run --rm \ | |
| --mount=type=image,src="${source_image}",target=/chunkah \ | |
| -e CHUNKAH_CONFIG_STR \ | |
| "${CHUNKAH_IMAGE}" build \ | |
| --verbose \ | |
| --compressed \ | |
| --max-layers 128 \ | |
| --prune /sysroot/ \ | |
| --label ostree.commit- \ | |
| --label ostree.final-diffid- \ | |
| --tag "${chunked_image}" | podman load | |
| for tag in ${{ steps.metadata.outputs.tags }}; do | |
| podman tag "${chunked_image}" "${IMAGE_NAME}:${tag}" | |
| done | |
| # These `if` statements are so that pull requests for your custom images do not make it publish any packages under your name without you knowing | |
| # They also check if the runner is on the default branch so that things like the merge queue (if you enable it), are going to work | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 | |
| if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Push To GHCR | |
| uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2 | |
| if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
| id: push | |
| env: | |
| REGISTRY_USER: ${{ github.actor }} | |
| REGISTRY_PASSWORD: ${{ github.token }} | |
| with: | |
| registry: ${{ env.IMAGE_REGISTRY }} | |
| image: ${{ env.IMAGE_NAME }} | |
| tags: ${{ steps.metadata.outputs.tags }} | |
| username: ${{ env.REGISTRY_USER }} | |
| password: ${{ env.REGISTRY_PASSWORD }} | |
| # All tags in this job point at one manifest pushed from one local | |
| # image. Enforce that as an invariant instead of trusting it: if any | |
| # published tag resolves to a different digest than the one about to | |
| # be signed, fail before signing rather than publish a half-signed | |
| # tag set. | |
| - name: Verify pushed tags share one digest | |
| if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
| run: | | |
| set -euo pipefail | |
| expected="${{ steps.push.outputs.digest }}" | |
| if [ -z "${expected}" ]; then | |
| echo "push step did not expose a digest" >&2 | |
| exit 1 | |
| fi | |
| for tag in ${{ steps.metadata.outputs.tags }}; do | |
| actual=$(skopeo inspect --format '{{.Digest}}' "docker://${IMAGE_REGISTRY}/${IMAGE_NAME}:${tag}") | |
| if [ "${actual}" != "${expected}" ]; then | |
| echo "Tag ${tag} resolves to ${actual}, expected ${expected}" >&2 | |
| exit 1 | |
| fi | |
| echo "Tag ${tag} -> ${actual} OK" | |
| done | |
| # This section is optional and only needs to be enabled if you plan on distributing | |
| # your project for others to consume. You will need to create a public and private key | |
| # using Cosign and save the private key as a repository secret in Github for this workflow | |
| # to consume. For more details, review the image signing section of the README. | |
| - name: Install Cosign | |
| uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 | |
| if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
| with: | |
| cosign-release: 'v2.6.1' | |
| - name: Sign container image | |
| if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
| run: | | |
| cosign sign -y --key env://COSIGN_PRIVATE_KEY \ | |
| "${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.push.outputs.digest }}" | |
| env: | |
| COSIGN_EXPERIMENTAL: false | |
| COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} |