Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/docker-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Docker image (PR)

# Default permissions are least-privilege; no job needs elevated access.
# This satisfies zizmor's excessive-permissions check.
permissions:
contents: read

# A newer push to the PR cancels any older build that is still running.
concurrency:
group: docker-image-${{ github.ref }}
cancel-in-progress: true

# Validation-only builds for pull requests: each platform builds natively on
# its own runner, without logging in to GHCR or pushing. Publishing lives in
# docker.yml.
on:
pull_request:
branches: [main]

jobs:
build-amd64:
name: Build image (amd64)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

- name: Build
uses: docker/bake-action@76cc8060bdff6d632a465001e4cf300684c5472c # v5.7.0
with:
files: |
website/docker-bake.hcl
# Use the dedicated PR target, which omits `cache-to` because
# fork PRs lack permission to write to the GHA cache.
targets: synapse-website-pr
set: |
*.platform=linux/amd64

build-arm64:
name: Build image (arm64)
runs-on: ubuntu-24.04-arm
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

- name: Build
uses: docker/bake-action@76cc8060bdff6d632a465001e4cf300684c5472c # v5.7.0
with:
files: |
website/docker-bake.hcl
targets: synapse-website-pr
set: |
*.platform=linux/arm64
162 changes: 138 additions & 24 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Docker image

# Default permissions are least-privilege; the push job elevates to
# Default permissions are least-privilege; the push jobs elevate to
# packages: write below. This satisfies zizmor's excessive-permissions check.
permissions:
contents: read
Expand All @@ -12,12 +12,12 @@ concurrency:
group: docker-image-${{ github.ref }}
cancel-in-progress: true

# Builds and publishes the multi-arch website image on pushes to main and
# on v* tags. Pull-request validation builds live in docker-pr.yml.
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:

env:
Expand All @@ -30,16 +30,22 @@ env:
# readability.
IMAGE_NAME: ${{ github.repository_owner }}/synapse-website

# Each platform is built natively on its own runner (arm64 on GitHub's free
# arm runner instead of QEMU emulation on an amd64 runner) and pushed to
# GHCR by digest; the merge job then combines the two digests into a single
# multi-arch manifest list with the computed tags.
#
# Pattern documented at
# https://docs.docker.com/build/ci/github-actions/multi-platform/
jobs:
build:
name: Build & push image
build-amd64:
name: Build & push image (amd64)
runs-on: ubuntu-latest
# Publish only on direct pushes to main or on v* tags. PRs run the
# build-pr job (push: false, no cache-to) below.
if: github.event_name != 'pull_request'
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.digest.outputs.digest }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expand All @@ -61,34 +67,54 @@ jobs:
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# The computed tags are only applied by the merge job; this step
# exists so its OCI labels get baked into the pushed image.
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,format=short
type=raw,value=latest,enable={{is_default_branch}}
# bake-target makes metadata-action emit a JSON file
# (steps.meta.outputs.bake-file) that overrides the same-named
# target in docker-bake.hcl with the computed tags + labels.
# bake-target makes metadata-action emit a JSON file that supplies
# the labels for the 'synapse-website' target.
bake-target: synapse-website

- name: Build and push
- name: Build and push by digest
id: build
uses: docker/bake-action@76cc8060bdff6d632a465001e4cf300684c5472c # v5.7.0
with:
# The third entry is the metadata-action-generated JSON; it
# supplies tags + labels for the 'synapse-website' target.
# The metadata file supplies the labels; the set overrides then
# replace its tags with the bare repository name, which is what
# push-by-digest requires (tags are attached by the merge job).
files: |
website/docker-bake.hcl
${{ steps.meta.outputs.bake-file }}
targets: synapse-website
push: true
set: |
*.platform=linux/amd64
*.tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
*.output=type=image,push-by-digest=true,name-canonical=true,push=true

build-pr:
name: Build (PR only)
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
- name: Record pushed digest
id: digest
env:
BAKE_METADATA: ${{ steps.build.outputs.metadata }}
run: |
digest="$(jq -r '.["synapse-website"]["containerimage.digest"]' <<<"$BAKE_METADATA")"
if [[ -z "$digest" || "$digest" == "null" ]]; then
echo "bake metadata is missing containerimage.digest" >&2
exit 1
fi
echo "digest=$digest" >> "$GITHUB_OUTPUT"

build-arm64:
name: Build & push image (arm64)
runs-on: ubuntu-24.04-arm
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.digest.outputs.digest }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expand All @@ -98,12 +124,100 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

- name: Build
- name: Log in to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,format=short
type=raw,value=latest,enable={{is_default_branch}}
bake-target: synapse-website

- name: Build and push by digest
id: build
uses: docker/bake-action@76cc8060bdff6d632a465001e4cf300684c5472c # v5.7.0
with:
files: |
website/docker-bake.hcl
# Use the dedicated PR target, which omits `cache-to` because
# fork PRs lack permission to write to the GHA cache.
targets: synapse-website-pr
push: false
${{ steps.meta.outputs.bake-file }}
targets: synapse-website
set: |
*.platform=linux/arm64
*.tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
*.output=type=image,push-by-digest=true,name-canonical=true,push=true

- name: Record pushed digest
id: digest
env:
BAKE_METADATA: ${{ steps.build.outputs.metadata }}
run: |
digest="$(jq -r '.["synapse-website"]["containerimage.digest"]' <<<"$BAKE_METADATA")"
if [[ -z "$digest" || "$digest" == "null" ]]; then
echo "bake metadata is missing containerimage.digest" >&2
exit 1
fi
echo "digest=$digest" >> "$GITHUB_OUTPUT"

merge:
name: Merge manifests & push tags
runs-on: ubuntu-latest
needs: [build-amd64, build-arm64]
permissions:
contents: read
packages: write
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

- name: Log in to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,format=short
type=raw,value=latest,enable={{is_default_branch}}

- name: Create multi-arch manifest list and push
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
AMD64_DIGEST: ${{ needs.build-amd64.outputs.digest }}
ARM64_DIGEST: ${{ needs.build-arm64.outputs.digest }}
run: |
tag_args=()
while IFS= read -r tag; do
tag_args+=("-t" "$tag")
done <<<"$(jq -r '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON")"
docker buildx imagetools create "${tag_args[@]}" \
"$IMAGE@$AMD64_DIGEST" "$IMAGE@$ARM64_DIGEST"

- name: Inspect image
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
# version is the branch name on main pushes and the semver on tag
# pushes (verified in workflow logs), so this always resolves; the
# guard just turns a future trigger change into a clear failure.
docker buildx imagetools inspect "${IMAGE}:${VERSION:?metadata-action produced no version tag}"
9 changes: 8 additions & 1 deletion website/docker-bake.hcl
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# Build targets consumed by docker/bake-action (see .github/workflows/docker.yml).
# Build targets consumed by docker/bake-action (see .github/workflows/
# docker.yml for push/tag publishes and docker-pr.yml for PR builds).
# Defining the build here keeps the Dockerfile, tags, cache config, and
# platform declarations in one file under the website/ source tree.
#
# CI builds each platform natively on its own runner (amd64 and arm64 jobs
# override `platform` via `--set`, avoiding QEMU emulation) and merges the
# two pushed digests into one multi-arch manifest list. A plain local
# `docker buildx bake synapse-website` still builds both platforms on a
# single node.
#
# The PR build target omits `cache-to` because fork pull requests don't have
# permission to write to the GHA cache; trying to export there fails the
# required check.
Expand Down
Loading