# 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-runner) # # 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-runner` 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// : # 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-runner # 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