Files
2026-07-21 17:34:59 +02:00

231 lines
7.8 KiB
YAML

name: Build and Push
on:
push:
branches: [main]
workflow_dispatch:
inputs:
service:
description: "Service to build (all, backend, consumer, worker, tracking, realtime)"
required: false
default: "all"
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}/warmbly
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
# JSON lists consumed as matrix inputs. `go` services cross-compile both
# arches on one runner; `native` services (Rust, Elixir) have no
# cross-compiler, so each arch builds on a native runner and the digests
# are merged into one manifest.
go: ${{ steps.matrix.outputs.go }}
native: ${{ steps.matrix.outputs.native }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
if: github.event_name == 'push'
id: filter
with:
filters: |
backend:
- 'go.mod'
- 'go.sum'
- 'internal/**'
- 'cmd/backend/**'
- 'deploy/docker/backend.Dockerfile'
consumer:
- 'go.mod'
- 'go.sum'
- 'internal/**'
- 'cmd/consumer/**'
- 'deploy/docker/consumer.Dockerfile'
worker:
- 'go.mod'
- 'go.sum'
- 'internal/**'
- 'cmd/worker/**'
- 'deploy/docker/worker.Dockerfile'
tracking:
- 'tracking/**'
realtime:
- 'realtime/**'
- 'deploy/docker/realtime.Dockerfile'
- name: Assemble build matrices
id: matrix
env:
EVENT: ${{ github.event_name }}
SELECTED: ${{ github.event.inputs.service }}
BACKEND: ${{ steps.filter.outputs.backend }}
CONSUMER: ${{ steps.filter.outputs.consumer }}
WORKER: ${{ steps.filter.outputs.worker }}
TRACKING: ${{ steps.filter.outputs.tracking }}
REALTIME: ${{ steps.filter.outputs.realtime }}
run: |
go=""
native=""
if [ "$EVENT" = "workflow_dispatch" ]; then
sel="${SELECTED:-all}"
for s in backend consumer worker; do
if [ "$sel" = "all" ] || [ "$sel" = "$s" ]; then go="$go\"$s\","; fi
done
for s in tracking realtime; do
if [ "$sel" = "all" ] || [ "$sel" = "$s" ]; then native="$native\"$s\","; fi
done
else
if [ "$BACKEND" = "true" ]; then go="$go\"backend\","; fi
if [ "$CONSUMER" = "true" ]; then go="$go\"consumer\","; fi
if [ "$WORKER" = "true" ]; then go="$go\"worker\","; fi
if [ "$TRACKING" = "true" ]; then native="$native\"tracking\","; fi
if [ "$REALTIME" = "true" ]; then native="$native\"realtime\","; fi
fi
echo "go=[${go%,}]" >> "$GITHUB_OUTPUT"
echo "native=[${native%,}]" >> "$GITHUB_OUTPUT"
# Go services: the Dockerfiles build on $BUILDPLATFORM and cross-compile to
# each target arch, so one amd64 runner produces both platforms without QEMU.
build-go:
name: Build ${{ matrix.service }}
needs: changes
if: needs.changes.outputs.go != '[]'
strategy:
fail-fast: false
matrix:
service: ${{ fromJSON(needs.changes.outputs.go) }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- 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 }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: deploy/docker/${{ matrix.service }}.Dockerfile
push: true
tags: |
${{ env.IMAGE_PREFIX }}/${{ matrix.service }}:${{ github.sha }}
${{ env.IMAGE_PREFIX }}/${{ matrix.service }}:dev
platforms: linux/amd64,linux/arm64
cache-from: type=gha,scope=${{ matrix.service }}
cache-to: type=gha,mode=max,scope=${{ matrix.service }}
# Rust (tracking) and Elixir (realtime) have no cross-compiler; an emulated
# arm64 build under QEMU runs for an hour or more. Build each arch on a
# native runner and merge the digests into one manifest (the
# Docker-documented multi-runner pattern).
build-native:
name: Build ${{ matrix.service }} (${{ matrix.platform }})
needs: changes
if: needs.changes.outputs.native != '[]'
strategy:
fail-fast: false
matrix:
service: ${{ fromJSON(needs.changes.outputs.native) }}
platform: [linux/amd64, linux/arm64]
runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Prepare platform pair
id: prep
run: echo "pair=${PLATFORM//\//-}" >> "$GITHUB_OUTPUT"
env:
PLATFORM: ${{ matrix.platform }}
- 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 }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: ${{ matrix.service == 'tracking' && './tracking' || '.' }}
file: ${{ matrix.service == 'tracking' && './tracking/Dockerfile' || format('deploy/docker/{0}.Dockerfile', matrix.service) }}
platforms: ${{ matrix.platform }}
cache-from: type=gha,scope=${{ matrix.service }}-${{ steps.prep.outputs.pair }}
cache-to: type=gha,mode=max,scope=${{ matrix.service }}-${{ steps.prep.outputs.pair }}
outputs: type=image,name=${{ env.IMAGE_PREFIX }}/${{ matrix.service }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.service }}-${{ steps.prep.outputs.pair }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge-native:
name: Merge ${{ matrix.service }} manifest
needs: [changes, build-native]
if: needs.changes.outputs.native != '[]'
strategy:
fail-fast: false
matrix:
service: ${{ fromJSON(needs.changes.outputs.native) }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-${{ matrix.service }}-*
merge-multiple: true
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
-t ${{ env.IMAGE_PREFIX }}/${{ matrix.service }}:${{ github.sha }} \
-t ${{ env.IMAGE_PREFIX }}/${{ matrix.service }}:dev \
$(printf '${{ env.IMAGE_PREFIX }}/${{ matrix.service }}@sha256:%s ' *)