Add own image build-push + per-service beta deploy (FE/BE CI-CD split)
Frontend now publishes its OWN registry image (emi/thermograph-frontend/app) via build-push.yml and deploys ONLY the frontend service to beta on push to main, passing SERVICE=frontend + FRONTEND_IMAGE_TAG to infra's deploy.sh. Independent of the backend's pipeline. Claude-Session: https://claude.ai/code/session_01RdARHDJaYC1wSQRTYM6t3Z
This commit is contained in:
parent
ccc74f6f08
commit
7ece52db1b
2 changed files with 160 additions and 0 deletions
104
.forgejo/workflows/build-push.yml
Normal file
104
.forgejo/workflows/build-push.yml
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
name: Build + push image (Forgejo registry)
|
||||||
|
|
||||||
|
# Builds THIS repo's image (frontend/Dockerfile) and pushes it to Forgejo's
|
||||||
|
# built-in container registry, tagged by git SHA (every push) and semver
|
||||||
|
# (version tags) -- build-once, deploy-everywhere.
|
||||||
|
#
|
||||||
|
# Repo-split "finish separating" (was Stage 6/7): the frontend and backend no
|
||||||
|
# longer share the single `emi/thermograph/app` image the old monorepo
|
||||||
|
# build-push.yml produced. Each app repo now publishes its OWN image from its
|
||||||
|
# OWN Dockerfile -- this one is emi/thermograph-frontend/app -- and
|
||||||
|
# thermograph-infra's compose references the two paths independently
|
||||||
|
# (BACKEND_IMAGE_PATH / FRONTEND_IMAGE_PATH). IMAGE_PATH is derived from
|
||||||
|
# github.repository, so this file is byte-identical in the frontend repo; the
|
||||||
|
# repo it lives in is what makes the image distinct.
|
||||||
|
#
|
||||||
|
# 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 thermograph-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]
|
||||||
|
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 github.ref means dev/main/release/each tag
|
||||||
|
# get their own group, so this only cancels a superseded build of the SAME ref.
|
||||||
|
concurrency:
|
||||||
|
group: build-push-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-push:
|
||||||
|
runs-on: docker
|
||||||
|
env:
|
||||||
|
REGISTRY: https://${{ vars.REGISTRY_HOST }}
|
||||||
|
IMAGE_PATH: ${{ github.repository }}/app
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- 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"
|
||||||
|
sha_tag="$ref:sha-${GITHUB_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.
|
||||||
|
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[@]}" -f Dockerfile .
|
||||||
|
|
||||||
|
- 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 }}"
|
||||||
56
.forgejo/workflows/deploy.yml
Normal file
56
.forgejo/workflows/deploy.yml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
name: Deploy frontend to beta VPS
|
||||||
|
|
||||||
|
# On a push to `main`, SSH to beta and roll ONLY the frontend service onto the
|
||||||
|
# image build-push.yml just published for this commit. Backend is deployed
|
||||||
|
# independently by its own repo's deploy.yml -- that is the whole point of the
|
||||||
|
# FE/BE CI-CD split: a frontend change ships without touching backend and vice
|
||||||
|
# versa. thermograph-infra/deploy/deploy.sh persists each service's live tag
|
||||||
|
# host-side, so a single-service roll never disturbs the other.
|
||||||
|
#
|
||||||
|
# The SSH_HOST/USER/KEY/PORT secrets point at beta. Prod is NOT deployed by a
|
||||||
|
# workflow -- it's deployed with `terraform apply` on the `release` branch, so
|
||||||
|
# there is deliberately no release-triggered workflow. appleboy/ssh-action is
|
||||||
|
# referenced by full GitHub URL because it isn't mirrored in Forgejo's default
|
||||||
|
# action registry.
|
||||||
|
#
|
||||||
|
# The tag MUST match build-push.yml's `sha-${GITHUB_SHA:0:12}` (12 hex), not
|
||||||
|
# the full 40-char ${{ github.sha }} -- default Actions expressions have no
|
||||||
|
# substring function, so the compute step below truncates it. deploy.sh also
|
||||||
|
# retries the pull for ~5 min because Forgejo `needs:` can't gate across the
|
||||||
|
# separate build-push.yml, so this push's build may still be in flight.
|
||||||
|
#
|
||||||
|
# Frontend's own register() fetches the IndexNow key from backend at boot, so
|
||||||
|
# deploy.sh waits on a healthy backend before declaring the frontend roll OK
|
||||||
|
# (its compose depends_on already encodes that ordering).
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: beta-deploy-frontend
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- name: Compute image tag
|
||||||
|
id: tag
|
||||||
|
run: echo "tag=sha-${GITHUB_SHA:0:12}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Deploy frontend over SSH
|
||||||
|
uses: https://github.com/appleboy/ssh-action@v1.2.0
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.SSH_HOST }}
|
||||||
|
username: ${{ secrets.SSH_USER }}
|
||||||
|
key: ${{ secrets.SSH_KEY }}
|
||||||
|
port: ${{ secrets.SSH_PORT }}
|
||||||
|
# Forward the target service + the image tag to run. deploy.sh reads
|
||||||
|
# FRONTEND_IMAGE_TAG and rolls just `frontend`.
|
||||||
|
envs: SERVICE,FRONTEND_IMAGE_TAG
|
||||||
|
script: /opt/thermograph/deploy/deploy.sh
|
||||||
|
env:
|
||||||
|
SERVICE: frontend
|
||||||
|
FRONTEND_IMAGE_TAG: ${{ steps.tag.outputs.tag }}
|
||||||
Loading…
Reference in a new issue