Add a `deploy` job to build-images.yml that runs on the self-hosted `uom_deploy` runner and, via `needs: build`, fires only after a clean image build+push. It pulls the fresh :latest images and recreates the stack (pull → down → up -d) from /home/perry/website. Guarded on refs/heads/main so a workflow_dispatch off another branch can't deploy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
4.8 KiB
YAML
121 lines
4.8 KiB
YAML
# Build the app + bot container images, publish them to Gitea's container
|
|
# registry, then roll the production stack onto the fresh images — all on every
|
|
# merge to main. Production only ever pulls prebuilt images; it never builds.
|
|
#
|
|
# Two jobs run in sequence:
|
|
# build — builds & pushes website-app / website-bot images (on ubuntu-latest)
|
|
# deploy — `needs: build`, so it starts only after a clean build+push, and
|
|
# pulls + recreates the stack on the production host (on uom_deploy)
|
|
#
|
|
# Prerequisites (one-time):
|
|
# • An always-on Gitea runner with label `ubuntu-latest` whose jobs have the
|
|
# host Docker socket mounted (/var/run/docker.sock), so `docker build` talks
|
|
# to the host daemon. This also gives free layer caching between runs.
|
|
# • A second self-hosted runner labelled `uom_deploy` ON the production host,
|
|
# with access to the Docker daemon and to /home/perry/website (the directory
|
|
# holding the production docker-compose.yml + .env). This is what actually
|
|
# rolls the stack; it must be able to `docker compose pull` from the registry
|
|
# (log in once on the host, or ensure the images are public-read).
|
|
# • Two repo secrets (Settings → Actions → Secrets):
|
|
# REGISTRY_USER — the Gitea username that owns the token below
|
|
# REGISTRY_TOKEN — a Gitea access token with `write:package` (+ read:package)
|
|
# See the PR description / README for step-by-step token creation.
|
|
#
|
|
# Produces, in gitea.whitlocktech.com/<owner>/ :
|
|
# website-app:latest + website-app:sha-<7>
|
|
# website-bot:latest + website-bot:sha-<7>
|
|
# then deploys the `:latest` images (docker-compose.yml defaults IMAGE_TAG=latest).
|
|
|
|
name: Build container images
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch: {}
|
|
|
|
concurrency:
|
|
group: images-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
REGISTRY: gitea.whitlocktech.com
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out the merged commit
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Derive image refs (registry owner must be lowercase for Docker)
|
|
run: |
|
|
set -euo pipefail
|
|
OWNER="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')"
|
|
SHORT_SHA="${GITHUB_SHA:0:7}"
|
|
echo "APP_IMAGE=${REGISTRY}/${OWNER}/website-app" >> "$GITHUB_ENV"
|
|
echo "BOT_IMAGE=${REGISTRY}/${OWNER}/website-bot" >> "$GITHUB_ENV"
|
|
echo "TAG=sha-${SHORT_SHA}" >> "$GITHUB_ENV"
|
|
|
|
- name: Verify the Docker daemon is reachable
|
|
# Fails fast with a clear message if the host socket isn't mounted into
|
|
# the job container (the one hard runner prerequisite).
|
|
run: |
|
|
set -euo pipefail
|
|
if ! docker info >/dev/null 2>&1; then
|
|
echo "::error::Docker daemon not reachable. Mount /var/run/docker.sock into the runner's job containers."
|
|
exit 1
|
|
fi
|
|
echo "Docker daemon OK"
|
|
|
|
- name: Log in to the Gitea container registry
|
|
run: |
|
|
set -euo pipefail
|
|
echo "${{ secrets.REGISTRY_TOKEN }}" \
|
|
| docker login "${REGISTRY}" -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
|
|
|
- name: Build & push the app image (server + client)
|
|
run: |
|
|
set -euo pipefail
|
|
docker build -f Dockerfile \
|
|
-t "${APP_IMAGE}:latest" \
|
|
-t "${APP_IMAGE}:${TAG}" \
|
|
.
|
|
docker push "${APP_IMAGE}:latest"
|
|
docker push "${APP_IMAGE}:${TAG}"
|
|
|
|
- name: Build & push the bot image
|
|
run: |
|
|
set -euo pipefail
|
|
docker build -f bot/Dockerfile \
|
|
-t "${BOT_IMAGE}:latest" \
|
|
-t "${BOT_IMAGE}:${TAG}" \
|
|
.
|
|
docker push "${BOT_IMAGE}:latest"
|
|
docker push "${BOT_IMAGE}:${TAG}"
|
|
|
|
- name: Log out (clear cached credentials from the runner)
|
|
if: always()
|
|
run: docker logout "${REGISTRY}" || true
|
|
|
|
deploy:
|
|
# Roll production onto the images `build` just pushed. `needs: build` makes
|
|
# this wait for a clean build+push — if the build fails, deploy never fires,
|
|
# so the running stack is left untouched rather than torn down for nothing.
|
|
needs: build
|
|
runs-on: uom_deploy
|
|
# Guard against a workflow_dispatch fired from a non-main branch: only ever
|
|
# deploy the main line to production.
|
|
if: github.ref == 'refs/heads/main'
|
|
steps:
|
|
- name: Pull the fresh images and recreate the stack
|
|
# `pull` grabs the new :latest images the build job published; `down`
|
|
# then `up -d` recreates the containers on them. Compose only recreates
|
|
# services whose image digest changed, so the DB stays put.
|
|
run: |
|
|
set -euo pipefail
|
|
cd /home/perry/website
|
|
docker compose pull
|
|
docker compose down
|
|
docker compose up -d
|
|
docker compose ps
|