All checks were successful
secrets-guard / encrypted (push) Successful in 5s
secrets-guard / encrypted (pull_request) Successful in 6s
PR build (required check) / changes (pull_request) Successful in 9s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-backend (pull_request) Successful in 54s
PR build (required check) / gate (pull_request) Successful in 3s
126 lines
5.9 KiB
YAML
126 lines
5.9 KiB
YAML
name: Build + push backend image (Forgejo registry)
|
|
|
|
# Builds the BACKEND domain's image (backend/Dockerfile, context backend/) and
|
|
# pushes it to Forgejo's built-in container registry, tagged by git SHA (every
|
|
# push) and semver (version tags) -- build-once, deploy-everywhere.
|
|
#
|
|
# Monorepo port (reunification): the split repos each derived IMAGE_PATH from
|
|
# github.repository, which now collides for every domain -- so each domain's
|
|
# build-push names its image explicitly: this one is emi/thermograph/backend
|
|
# (frontend-build-push.yml publishes emi/thermograph/frontend). infra's
|
|
# compose/stack files reference the two paths independently
|
|
# (BACKEND_IMAGE_PATH / FRONTEND_IMAGE_PATH -- their defaults were renamed to
|
|
# match in the same cutover).
|
|
#
|
|
# Path-filtered: only a push that touches backend/** builds this image. A
|
|
# frontend-only push builds nothing here; deploy.sh's persisted
|
|
# .image-tags.env keeps the backend's live tag for the sibling's deploy.
|
|
# EXCEPTION: version-tag pushes (v*.*.*) carry no paths context, so a semver
|
|
# tag builds BOTH domain images -- intended, a release names the whole set.
|
|
#
|
|
# Runs on the `docker` label -- the LAN runner's job containers get the host's
|
|
# docker.sock automounted in (forgejo-runner config: container.docker_host:
|
|
# automount; see infra/deploy/forgejo/register-lan-runner.sh),
|
|
# Docker-outside-of-Docker rather than DinD -- no privileged mode needed.
|
|
# The job image itself (node:20-bookworm) has no docker CLI, hence the
|
|
# "Install Docker CLI" step.
|
|
#
|
|
# The registry is deliberately mesh-only: git.thermograph.org's public DNS
|
|
# resolves to beta's public IP, which Caddy's ACL rejects for /v2/ paths, so
|
|
# any runner host that pushes/pulls needs a /etc/hosts entry pointing
|
|
# git.thermograph.org at beta's WireGuard IP (10.10.0.2). The docker
|
|
# build/push commands run against the automounted HOST daemon, so it's the
|
|
# runner HOST's own resolver that has to route over the mesh, not anything
|
|
# settable from inside the job container.
|
|
#
|
|
# REGISTRY is the vars.REGISTRY_HOST Actions variable (git.thermograph.org),
|
|
# NOT github.server_url -- that context resolves to the bare mesh IP:port
|
|
# (http://10.10.0.2:3080, no TLS), which docker CLI refuses ("server gave HTTP
|
|
# response to HTTPS client"). git.thermograph.org gets real TLS via Caddy and
|
|
# routes over the mesh once /etc/hosts is set, so it's the only correct value.
|
|
# deploy.sh resolves the SHA tag at deploy time and `docker compose pull`s it.
|
|
|
|
on:
|
|
push:
|
|
branches: [dev, main, release]
|
|
paths: ['backend/**']
|
|
tags: ['v*.*.*']
|
|
workflow_dispatch: {}
|
|
|
|
# The runner has capacity: 1 (one physical LAN box) -- a burst of pushes to the
|
|
# same ref would otherwise queue whole builds back to back even though only the
|
|
# last one still matters. Keying on domain + github.ref means each domain's
|
|
# dev/main/release/tag builds get their own group, so this only cancels a
|
|
# superseded build of the SAME domain on the SAME ref -- and a frontend build
|
|
# never cancels a backend one.
|
|
concurrency:
|
|
group: backend-build-push-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
build-push:
|
|
runs-on: docker
|
|
env:
|
|
REGISTRY: https://${{ vars.REGISTRY_HOST }}
|
|
IMAGE_PATH: ${{ github.repository }}/backend
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
# fetch-depth 0: the tag is keyed to the LAST COMMIT THAT TOUCHED THIS
|
|
# DOMAIN, not the branch tip -- in a path-filtered monorepo the tip is
|
|
# often an unrelated domain's commit, and a depth-1 clone can't see
|
|
# past it to find the real key.
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install Docker CLI
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y -qq docker.io
|
|
|
|
- name: Compute image ref + tags
|
|
id: image
|
|
run: |
|
|
host="${REGISTRY#https://}"; host="${host#http://}"
|
|
path="$(printf '%s' "$IMAGE_PATH" | tr '[:upper:]' '[:lower:]')"
|
|
ref="$host/$path"
|
|
# Key the tag to the last commit that touched backend/ -- the SAME key
|
|
# every backend deploy workflow computes -- so build and deploy always
|
|
# agree even when the branch tip is another domain's commit. (A tag
|
|
# keyed to the push tip breaks the moment an infra-only commit lands:
|
|
# deploys go looking for an image no build ever produced.)
|
|
domain_sha="$(git log -1 --format=%H -- backend/)"
|
|
sha_tag="$ref:sha-${domain_sha:0:12}"
|
|
echo "host=$host" >> "$GITHUB_OUTPUT"
|
|
echo "sha_tag=$sha_tag" >> "$GITHUB_OUTPUT"
|
|
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
|
|
echo "semver_tag=$ref:${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Log in to the Forgejo registry
|
|
run: |
|
|
# NOT secrets.GITHUB_TOKEN -- Forgejo's per-job auto-injected token
|
|
# can never push to the container registry (a known Forgejo
|
|
# limitation independent of any permission setting). Needs a
|
|
# manually provisioned PAT with write:package scope, stored as the
|
|
# REGISTRY_TOKEN secret.
|
|
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ steps.image.outputs.host }}" \
|
|
--username emi --password-stdin
|
|
|
|
- name: Build
|
|
run: |
|
|
# One build, both tags -- the semver tag (tag pushes only) rides
|
|
# along with the SHA tag instead of a second tag+push round trip.
|
|
# Context is the DOMAIN dir, so backend/Dockerfile sees the same
|
|
# tree it saw as a repo root in the split era.
|
|
tag_args=(-t "${{ steps.image.outputs.sha_tag }}")
|
|
if [[ -n "${{ steps.image.outputs.semver_tag }}" ]]; then
|
|
tag_args+=(-t "${{ steps.image.outputs.semver_tag }}")
|
|
fi
|
|
docker build "${tag_args[@]}" backend/
|
|
|
|
- name: Push (SHA tag, every push)
|
|
run: docker push "${{ steps.image.outputs.sha_tag }}"
|
|
|
|
- name: Push (semver tag, tag pushes only)
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
run: docker push "${{ steps.image.outputs.semver_tag }}"
|