Skip to content

Commit 498b2b3

Browse files
bokchannajuna-brianr0ssing
authored
ci(synkronus): Support FOSS multi-arch build in CI (#586)
* ci(synkronus-docker): simplify multi-arch build and tagging - replace shell-heavy logic with metadata-action and Buildah actions\n- make tag generation explicit for release, push, and PR events\n- tighten permissions and wire attestation to pushed image digest * chore(renovate): align GitHub Actions pin update rules - update Renovate config for action pin maintenance\n- keep workflow dependency governance explicit and auditable * docs(ci): refresh Synkronus Docker workflow documentation - document event-to-tag mapping from metadata-action\n- describe manifest verification and attestation behavior\n- clarify required workflow permissions --------- Co-authored-by: Najuna Brian <[email protected]> Co-authored-by: Emil Rossing <[email protected]>
1 parent 0855cfe commit 498b2b3

File tree

3 files changed

+89
-64
lines changed

3 files changed

+89
-64
lines changed

.github/CICD.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,31 @@ Images are published to **GitHub Container Registry (GHCR)**:
3434

3535
#### Tagging Strategy
3636

37-
| Branch/Event | Tags Generated | Description |
38-
|--------------|----------------|-------------|
39-
| `main` | `latest`, `main-{sha}` | Latest stable release |
40-
| `dev` | `dev`, `dev-{sha}` | Development pre-release |
41-
| Feature branches | `{branch-name}`, `{branch-name}-{sha}` | Feature-specific builds |
42-
| Pull Requests | `pr-{number}` | PR validation builds (not pushed) |
43-
| Manual with version | `v{version}`, `v{major}.{minor}`, `latest` | Versioned release |
37+
Image tags are computed by `docker/metadata-action`. The highest-priority tag per event is also used as the primary tag in manifest verification.
38+
39+
| Event | Tags produced | Published? |
40+
|-------|--------------|------------|
41+
| Release | `v{X.Y.Z}`, `v{X.Y}` | Yes |
42+
| Push → `main` | `latest`, `sha-{short}` | Yes |
43+
| Push → `dev` | `dev`, `latest`, `sha-{short}` | Yes |
44+
| Push → other branch | `{branch-name}`, `sha-{short}` | Yes |
45+
| `workflow_dispatch` | `{branch-name}`, `sha-{short}` | Yes |
46+
| Pull request | `pr-{number}` | No (build only) |
4447

4548
#### Build Features
4649

47-
- **Multi-platform**: Builds for `linux/amd64` and `linux/arm64`
48-
- **Build Cache**: Uses GitHub Actions cache for faster builds
49-
- **Attestation**: Generates build provenance for security
50-
- **Metadata**: Includes OCI-compliant labels and annotations
50+
- **Multi-platform**: Builds for `linux/amd64` and `linux/arm64` using Buildah
51+
- **Attestation**: Generates SLSA build provenance and pushes it to GHCR
52+
- **Metadata**: Includes OCI-compliant labels (title, description, vendor, source, revision)
53+
- **Verification**: After push, the manifest list and per-arch layers are pulled to confirm correctness
5154

5255
#### Permissions Required
5356

5457
The workflow requires these permissions:
5558
- `contents: read` - To checkout the repository
5659
- `packages: write` - To publish to GHCR
60+
- `id-token: write` - For OIDC-based build provenance attestation
61+
- `attestations: write` - To push attestation records to GHCR
5762

5863
#### Secrets Used
5964

.github/workflows/synkronus-docker.yml

Lines changed: 69 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -24,38 +24,37 @@ on:
2424

2525
env:
2626
REGISTRY: ghcr.io
27+
IMAGE_NAME: ${{ github.repository_owner }}/synkronus
2728

2829
jobs:
2930
build-and-push:
3031
runs-on: ubuntu-latest
3132
name: Build and push combined Synkronus image
3233
permissions:
33-
contents: write
34+
contents: read
3435
packages: write
3536
id-token: write
3637
attestations: write
37-
38+
3839
steps:
3940
- name: Checkout repository
4041
uses: actions/checkout@v4
4142
with:
4243
fetch-depth: 0 # Full history so git describe can reach the last tag
43-
44+
4445
- name: Set up QEMU
45-
uses: docker/setup-qemu-action@v3
46-
with:
47-
platforms: all
48-
49-
- name: Set up Docker Buildx
50-
uses: docker/setup-buildx-action@v3
51-
52-
- name: Log in to GitHub Container Registry
53-
uses: docker/login-action@v3
46+
run: |
47+
sudo apt-get update
48+
sudo apt-get install -y qemu-user-static binfmt-support
49+
sudo update-binfmts --enable qemu-aarch64
50+
51+
- name: Log in to Github Container Registry
52+
uses: redhat-actions/podman-login@v1
5453
with:
55-
registry: ${{ env.REGISTRY }}
5654
username: ${{ github.actor }}
5755
password: ${{ secrets.GITHUB_TOKEN }}
58-
56+
registry: ${{ env.REGISTRY }}
57+
5958
- name: Determine Synkronus version from git
6059
id: version
6160
run: |
@@ -71,51 +70,69 @@ jobs:
7170
fi
7271
echo "version=${VERSION}" >> $GITHUB_OUTPUT
7372
echo "Building Synkronus with version: ${VERSION}"
74-
75-
- name: Extract metadata (tags, labels)
76-
id: meta
77-
uses: docker/metadata-action@v5
73+
74+
- name: Compute image tags and labels
75+
id: tags
76+
uses: docker/metadata-action@v6
7877
with:
79-
images: ${{ env.REGISTRY }}/opendataensemble/synkronus
78+
images: |
79+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
8080
tags: |
81-
# For main branch: latest + version tag (manual dispatch) or release tag
82-
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
83-
# For dev branch: tag as both dev and latest (for demo server auto-updates)
84-
type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }}
85-
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/dev' }}
86-
# When triggered from a GitHub Release event, use the release tag name as the semver source
87-
type=semver,pattern=v{{version}},enable=${{ github.event_name == 'release' }},value=${{ github.event.release.tag_name }}
88-
type=semver,pattern=v{{major}}.{{minor}},enable=${{ github.event_name == 'release' }},value=${{ github.event.release.tag_name }}
89-
# For other branches: branch name (pre-release)
90-
type=ref,event=branch,enable=${{ github.event_name != 'release' && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }}
91-
# For PRs: pr-number
92-
type=ref,event=pr
93-
# SHA for traceability (only for non-release events)
94-
type=sha,prefix=sha-,enable=${{ github.event_name != 'release' && github.event_name != 'pull_request' }}
95-
labels: |
96-
org.opencontainers.image.title=Synkronus
97-
org.opencontainers.image.description=Synchronization API and Web Portal for offline-first applications
98-
org.opencontainers.image.vendor=Open Data Ensemble
99-
100-
- name: Build and push Docker image
101-
id: build
102-
uses: docker/build-push-action@v5
81+
# Release event: v<version> + v<major>.<minor>
82+
type=semver,pattern=v{{version}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }},priority=1000
83+
type=semver,pattern=v{{major}}.{{minor}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }},priority=900
84+
# Pull requests: pr-<number>
85+
type=ref,event=pr,priority=1000
86+
# Non-release/non-PR refs: main/dev/other branches + sha
87+
type=raw,value=latest,enable=${{ github.event_name != 'release' && github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }},priority=1000
88+
type=raw,value=dev,enable=${{ github.event_name != 'release' && github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' }},priority=1000
89+
type=raw,value=latest,enable=${{ github.event_name != 'release' && github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' }},priority=900
90+
type=ref,event=branch,enable=${{ github.event_name != 'release' && github.event_name != 'pull_request' && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }},priority=1000
91+
type=sha,enable=${{ github.event_name != 'release' && github.event_name != 'pull_request' }},priority=100
92+
93+
- name: Build multi-arch image with Buildah
94+
id: build-image
95+
uses: redhat-actions/buildah-build@v2
10396
with:
104-
context: .
105-
file: ./Dockerfile
106-
platforms: linux/amd64
107-
push: ${{ github.event_name != 'pull_request' }}
108-
tags: ${{ steps.meta.outputs.tags }}
109-
labels: ${{ steps.meta.outputs.labels }}
97+
image: ${{ env.IMAGE_NAME }}
98+
tags: ${{ steps.tags.outputs.tag-names }}
99+
labels: ${{ steps.tags.outputs.labels }}
100+
archs: amd64,arm64
101+
containerfiles: |
102+
./Dockerfile
110103
build-args: |
111104
SYNKRONUS_VERSION=${{ steps.version.outputs.version }}
112-
cache-from: type=gha
113-
cache-to: type=gha,mode=max
114-
105+
106+
- name: Push image manifest to registry
107+
if: github.event_name != 'pull_request'
108+
id: push-image
109+
uses: redhat-actions/push-to-registry@v2
110+
with:
111+
image: ${{ steps.build-image.outputs.image }}
112+
tags: ${{ steps.build-image.outputs.tags }}
113+
registry: ${{ env.REGISTRY }}
114+
115+
- name: Verify image
116+
if: github.event_name != 'pull_request'
117+
shell: bash
118+
run: |
119+
set -euo pipefail
120+
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
121+
PRIMARY_TAG="${{ steps.tags.outputs.version }}"
122+
123+
# Confirm the manifest list includes both target platforms.
124+
skopeo inspect --raw "docker://${IMAGE}:${PRIMARY_TAG}" | \
125+
jq -e '[.manifests[].platform | "\(.os)/\(.architecture)"] | index("linux/amd64") and index("linux/arm64")' >/dev/null
126+
echo "Verified manifest platforms for ${IMAGE}:${PRIMARY_TAG}"
127+
128+
# Pull each platform explicitly to confirm the runtime layers are accessible.
129+
podman pull --arch amd64 "${IMAGE}:${PRIMARY_TAG}"
130+
podman pull --arch arm64 "${IMAGE}:${PRIMARY_TAG}"
131+
115132
- name: Generate artifact attestation
116133
if: github.event_name != 'pull_request'
117134
uses: actions/attest-build-provenance@v1
118135
with:
119-
subject-name: ${{ env.REGISTRY }}/opendataensemble/synkronus
120-
subject-digest: ${{ steps.build.outputs.digest }}
136+
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
137+
subject-digest: ${{ steps.push-image.outputs.digest }}
121138
push-to-registry: true

renovate.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
"extends": [
44
"config:recommended"
55
],
6-
"labels": ["dependencies"]
6+
"labels": ["dependencies"],
7+
"github-actions": {
8+
"pinDigests": true
9+
}
710
}

0 commit comments

Comments
 (0)