Registry-pull cutover for prod/beta deploys (repo-split Stage 6) (#21)

This commit is contained in:
emi 2026-07-21 21:36:03 +00:00
parent 90f313cf56
commit 20054ab198
9 changed files with 62 additions and 4 deletions

View file

@ -41,8 +41,20 @@ echo "==> Fetching $BRANCH"
git fetch --prune origin "$BRANCH" git fetch --prune origin "$BRANCH"
git reset --hard "origin/$BRANCH" git reset --hard "origin/$BRANCH"
echo "==> Building images" # Registry-pull cutover (repo-split Stage 6): pull the tag build-push.yml
docker compose build # already pushed for this exact commit instead of building in place. Mirrors
# build-push.yml's own tag computation (sha-<12 hex>) so this always resolves
# to "whatever HEAD of this branch built to" -- no separate promotion step.
REGISTRY_HOST="${REGISTRY_HOST:-git.thermograph.org}"
IMAGE_PATH="${IMAGE_PATH:-emi/thermograph/app}"
export IMAGE_TAG="sha-$(git rev-parse --short=12 HEAD)"
export REGISTRY_HOST IMAGE_PATH
echo "==> Logging in to the registry ($REGISTRY_HOST)"
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" --username emi --password-stdin
echo "==> Pulling images ($IMAGE_TAG)"
docker compose pull backend frontend
# Schema migrations run inside the backend container's entrypoint (alembic # Schema migrations run inside the backend container's entrypoint (alembic
# upgrade head) before uvicorn starts, so there's no separate migrate step or # upgrade head) before uvicorn starts, so there's no separate migrate step or

View file

@ -9,6 +9,14 @@
# host loopback (127.0.0.1:8137) for Caddy to proxy to. Keep 8137. # host loopback (127.0.0.1:8137) for Caddy to proxy to. Keep 8137.
PORT=8137 PORT=8137
# --- Registry pull (deploy.sh / Terraform) ---------------------------------------
# deploy.sh logs in to git.thermograph.org's registry and `docker compose pull`s
# the image build-push.yml already pushed for the deployed commit (repo-split
# Stage 6), instead of building in place. A personal access token with at least
# read:package scope -- the same REGISTRY_TOKEN secret build-push.yml uses (that
# one needs write:package too, to push; this only needs to pull).
REGISTRY_TOKEN=
# --- PostgreSQL (docker-compose stack) ------------------------------------------ # --- PostgreSQL (docker-compose stack) ------------------------------------------
# The app and Postgres run as a docker-compose stack (see docker-compose.yml). # The app and Postgres run as a docker-compose stack (see docker-compose.yml).
# POSTGRES_PASSWORD is the database password: compose uses it to initialize the # POSTGRES_PASSWORD is the database password: compose uses it to initialize the

View file

@ -80,6 +80,13 @@ services:
backend: backend:
build: . build: .
# Named so `docker compose pull` can fetch a prebuilt tag instead of
# `--build`ing in place (repo-split Stage 6's registry-pull cutover --
# deploy.sh/Terraform set IMAGE_TAG to the SHA build-push.yml just pushed
# for the branch being deployed). Harmless default for local/CI
# `--build` usage, which never needs to resolve this against the
# registry -- `build:` + `image:` together just tag the local build.
image: ${REGISTRY_HOST:-git.thermograph.org}/${IMAGE_PATH:-emi/thermograph/app}:${IMAGE_TAG:-local}
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
@ -130,6 +137,9 @@ services:
frontend: frontend:
build: . build: .
# Same image as backend (see backend's `image:` comment above) --
# THERMOGRAPH_SERVICE_ROLE below is what picks the process.
image: ${REGISTRY_HOST:-git.thermograph.org}/${IMAGE_PATH:-emi/thermograph/app}:${IMAGE_TAG:-local}
# Its own register() fetches the IndexNow key from backend at boot -- must # Its own register() fetches the IndexNow key from backend at boot -- must
# wait for a real, healthy backend, not just a started container. # wait for a real, healthy backend, not just a started container.
depends_on: depends_on:

View file

@ -90,4 +90,5 @@ module "host" {
discord_app_id = var.discord_app_id discord_app_id = var.discord_app_id
discord_bot_token = var.discord_bot_token discord_bot_token = var.discord_bot_token
discord_client_secret = var.discord_client_secret discord_client_secret = var.discord_client_secret
registry_token = var.registry_token
} }

View file

@ -56,6 +56,7 @@ locals {
discord_app_id = var.discord_app_id discord_app_id = var.discord_app_id
discord_bot_token = var.discord_bot_token discord_bot_token = var.discord_bot_token
discord_client_secret = var.discord_client_secret discord_client_secret = var.discord_client_secret
registry_token = var.registry_token
}) })
# Caddyfile is only meaningful on a host with a public domain. # Caddyfile is only meaningful on a host with a public domain.
@ -261,10 +262,14 @@ resource "null_resource" "host" {
inline = [ inline = [
<<-EOT <<-EOT
set -eu set -eu
echo "[${var.name}] deploy: install env + docker compose up" echo "[${var.name}] deploy: install env + docker compose pull + up"
sudo install -m 0640 -o root -g root /tmp/thermograph.env /etc/thermograph.env sudo install -m 0640 -o root -g root /tmp/thermograph.env /etc/thermograph.env
rm -f /tmp/thermograph.env rm -f /tmp/thermograph.env
sudo bash -c 'set -a; . /etc/thermograph.env; set +a; cd ${var.app_dir} && docker compose ${local.compose_flags} up -d --build' sudo bash -c 'set -a; . /etc/thermograph.env; set +a; cd ${var.app_dir} && \
export REGISTRY_HOST="git.thermograph.org" IMAGE_PATH="emi/thermograph/app" IMAGE_TAG="sha-$(git rev-parse --short=12 HEAD)" && \
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" --username emi --password-stdin && \
docker compose ${local.compose_flags} pull backend frontend && \
docker compose ${local.compose_flags} up -d'
EOT EOT
] ]
} }

View file

@ -5,6 +5,11 @@
# Port uvicorn binds inside the container (compose publishes it on the host). # Port uvicorn binds inside the container (compose publishes it on the host).
PORT=${app_port} PORT=${app_port}
# --- Registry pull (repo-split Stage 6) -------------------------------------------
# `docker login` + `docker compose pull` credential -- read:package scope is
# enough (deploy.sh only pulls, never pushes).
REGISTRY_TOKEN=${registry_token}
# --- PostgreSQL (docker-compose stack) ------------------------------------------ # --- PostgreSQL (docker-compose stack) ------------------------------------------
# Initializes the db container AND builds the app's THERMOGRAPH_DATABASE_URL. # Initializes the db container AND builds the app's THERMOGRAPH_DATABASE_URL.
POSTGRES_PASSWORD=${postgres_password} POSTGRES_PASSWORD=${postgres_password}

View file

@ -228,3 +228,8 @@ variable "discord_client_secret" {
type = string type = string
sensitive = true sensitive = true
} }
variable "registry_token" {
type = string
sensitive = true
}

View file

@ -130,3 +130,9 @@ vapid_contact = "mailto:you@example.com"
# discord_app_id = "" # discord_app_id = ""
# discord_bot_token = "" # discord_bot_token = ""
# discord_client_secret = "" # discord_client_secret = ""
# ---- Required: registry pull credential (repo-split Stage 6) --------------------
# A Forgejo personal access token, read:package scope, for deploy.sh's
# `docker login` + `docker compose pull` on every apply. No default -- apply
# fails loud without it rather than deploying a host that can't redeploy itself.
# registry_token = ""

View file

@ -238,6 +238,12 @@ variable "discord_bot_token" {
default = "" default = ""
} }
variable "registry_token" {
description = "Forgejo registry pull credential (read:package scope) -- deploy.sh's docker login + docker compose pull (repo-split Stage 6). Required; no default -- deploys fail loud without it rather than silently falling back to a build."
type = string
sensitive = true
}
variable "discord_client_secret" { variable "discord_client_secret" {
description = "THERMOGRAPH_DISCORD_CLIENT_SECRET — OAuth2 account linking (a credential)." description = "THERMOGRAPH_DISCORD_CLIENT_SECRET — OAuth2 account linking (a credential)."
type = string type = string