ci(deploy): auto-deploy prod stack after image build on merge to main #63

Merged
whitlocktech merged 1 commits from ci/auto-deploy-on-merge into main 2026-07-17 10:29:06 +00:00

View File

@@ -1,11 +1,21 @@
# Build and publish the app + bot container images to Gitea's container registry
# on every merge to main. Production then pulls prebuilt images instead of
# building on the host.
# 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)
@@ -14,6 +24,7 @@
# 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
@@ -85,3 +96,25 @@ jobs:
- 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