deploy: adapt scripts + compose to the monorepo host checkout

/opt/thermograph becomes a monorepo checkout: deploy.sh gains INFRA_DIR (git
ops at the root, compose work cd'd into infra/), lock/tags/render paths move
under infra/deploy/, image-path defaults become emi/thermograph/backend|
frontend across compose, stack, and the tag-prune. docker-compose.yml pins
name: thermograph -- without it compose run from .../infra derives project
"infra" and recreates the whole stack beside the running one;
deploy-dev.sh pins COMPOSE_PROJECT_NAME=thermograph-dev (env wins over the
file key) to keep LAN dev's separate project.
This commit is contained in:
Emi Griffith 2026-07-22 22:11:33 -07:00
parent 7b2db07722
commit 2e753f2c6f
5 changed files with 52 additions and 30 deletions

View file

@ -2,7 +2,7 @@
# LAN-dev deploy: roll the per-service registry-pull stack (see deploy.sh) onto # LAN-dev deploy: roll the per-service registry-pull stack (see deploy.sh) onto
# the ~/thermograph-dev overlay instead of prod/beta's loopback-only stack. # the ~/thermograph-dev overlay instead of prod/beta's loopback-only stack.
# #
# This script assumes $APP_DIR is a checkout of THIS repo (thermograph-infra), # This script assumes $APP_DIR is a checkout of the monorepo (deploy assets under infra/),
# the same way /opt/thermograph is on prod/beta. That is the live state: the LAN # the same way /opt/thermograph is on prod/beta. That is the live state: the LAN
# box's ~/thermograph-dev was reprovisioned as an infra checkout during the # box's ~/thermograph-dev was reprovisioned as an infra checkout during the
# 2026-07-22 cutover (the app monorepo is archived), provision-dev-lan.sh's # 2026-07-22 cutover (the app monorepo is archived), provision-dev-lan.sh's
@ -44,6 +44,12 @@ export APP_DIR BRANCH
# path-list separator it expects (';' only on Windows). # path-list separator it expects (';' only on Windows).
export COMPOSE_FILE="docker-compose.yml:docker-compose.dev.yml" export COMPOSE_FILE="docker-compose.yml:docker-compose.dev.yml"
# Pin the dev project name: docker-compose.yml pins `name: thermograph` for
# prod/beta, but dev's stack has always been the separate "thermograph-dev"
# project (originally derived from this checkout's directory name). The env
# var wins over the compose-file `name:` key, preserving that split.
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-thermograph-dev}"
# deploy.sh needs POSTGRES_PASSWORD to interpolate the compose file (both to init # deploy.sh needs POSTGRES_PASSWORD to interpolate the compose file (both to init
# the db container and to build backend's THERMOGRAPH_DATABASE_URL) -- normally # the db container and to build backend's THERMOGRAPH_DATABASE_URL) -- normally
# supplied by /etc/thermograph.env via render-secrets.sh's SOPS render, but dev has # supplied by /etc/thermograph.env via render-secrets.sh's SOPS render, but dev has

View file

@ -6,27 +6,30 @@
# and you can run it by hand too. # and you can run it by hand too.
# #
# # roll just the backend onto a specific image: # # roll just the backend onto a specific image:
# ssh deploy@vps 'SERVICE=backend BACKEND_IMAGE_TAG=sha-<12hex> /opt/thermograph/deploy/deploy.sh' # ssh deploy@vps 'SERVICE=backend BACKEND_IMAGE_TAG=sha-<12hex> /opt/thermograph/infra/deploy/deploy.sh'
# # roll just the frontend: # # roll just the frontend:
# ssh deploy@vps 'SERVICE=frontend FRONTEND_IMAGE_TAG=sha-<12hex> /opt/thermograph/deploy/deploy.sh' # ssh deploy@vps 'SERVICE=frontend FRONTEND_IMAGE_TAG=sha-<12hex> /opt/thermograph/infra/deploy/deploy.sh'
# # bring the whole stack up (both tags required): # # bring the whole stack up (both tags required):
# ssh deploy@vps 'SERVICE=all BACKEND_IMAGE_TAG=sha-<a> FRONTEND_IMAGE_TAG=sha-<b> /opt/thermograph/deploy/deploy.sh' # ssh deploy@vps 'SERVICE=all BACKEND_IMAGE_TAG=sha-<a> FRONTEND_IMAGE_TAG=sha-<b> /opt/thermograph/infra/deploy/deploy.sh'
# #
# FE/BE CI-CD split: backend and frontend are published from separate repos as # FE/BE CI-CD split: backend and frontend are published from separate repos as
# separate images (emi/thermograph-backend/app, emi/thermograph-frontend/app), # separate images (emi/thermograph/backend, emi/thermograph/frontend),
# so a deploy targets ONE service and leaves the other's running container + # so a deploy targets ONE service and leaves the other's running container +
# tag untouched. Each service's live tag is persisted host-side in # tag untouched. Each service's live tag is persisted host-side in
# deploy/.image-tags.env (untracked -- survives the git reset below) so a # deploy/.image-tags.env (untracked -- survives the git reset below) so a
# single-service roll re-renders compose with BOTH services' real tags and # single-service roll re-renders compose with BOTH services' real tags and
# never accidentally recreates or downgrades the sibling. # never accidentally recreates or downgrades the sibling.
# #
# This checkout is thermograph-infra, not an app repo: BRANCH is this repo's # This checkout is the monorepo (compose + deploy live under infra/), not an app-only checkout: BRANCH is this repo's
# branch (compose files, db init, the secrets vault, this script itself); # branch (compose files, db init, the secrets vault, this script itself);
# the *_IMAGE_TAG values are the separately-published app images to run -- the # the *_IMAGE_TAG values are the separately-published app images to run -- the
# two axes are independent and rarely change together. # two axes are independent and rarely change together.
set -euo pipefail set -euo pipefail
APP_DIR="${APP_DIR:-/opt/thermograph}" APP_DIR="${APP_DIR:-/opt/thermograph}"
# Monorepo layout: git operations act on the checkout root ($APP_DIR); all
# compose files and deploy assets live under infra/.
INFRA_DIR="$APP_DIR/infra"
BRANCH="${BRANCH:-main}" BRANCH="${BRANCH:-main}"
# Which service this deploy rolls: backend | frontend | all. Defaults to `all` # Which service this deploy rolls: backend | frontend | all. Defaults to `all`
# (a full-stack bring-up) so a by-hand run with both tags still works; the # (a full-stack bring-up) so a by-hand run with both tags still works; the
@ -49,7 +52,7 @@ esac
# inherited across the self re-exec below, so the lock spans the whole run; # inherited across the self re-exec below, so the lock spans the whole run;
# -w 600 bounds the wait (a deploy holding the lock >10 min is already # -w 600 bounds the wait (a deploy holding the lock >10 min is already
# broken), after which this exits non-zero and the CI job fails loudly. # broken), after which this exits non-zero and the CI job fails loudly.
DEPLOY_LOCK="$APP_DIR/deploy/.deploy.lock" DEPLOY_LOCK="$INFRA_DIR/deploy/.deploy.lock"
if [ -z "${DEPLOY_SH_FLOCKED:-}" ]; then if [ -z "${DEPLOY_SH_FLOCKED:-}" ]; then
export DEPLOY_SH_FLOCKED=1 export DEPLOY_SH_FLOCKED=1
exec flock -w 600 "$DEPLOY_LOCK" "$0" "$@" exec flock -w 600 "$DEPLOY_LOCK" "$0" "$@"
@ -66,10 +69,10 @@ fi
# after which deploy.sh re-execs), so a missing helper simply falls back to the # after which deploy.sh re-execs), so a missing helper simply falls back to the
# existing /etc/thermograph.env. Then source it so a by-hand run interpolates the # existing /etc/thermograph.env. Then source it so a by-hand run interpolates the
# same as the systemd unit does. See deploy/render-secrets.sh + deploy/secrets/. # same as the systemd unit does. See deploy/render-secrets.sh + deploy/secrets/.
if [ -f "$APP_DIR/deploy/render-secrets.sh" ]; then if [ -f "$INFRA_DIR/deploy/render-secrets.sh" ]; then
# shellcheck source=deploy/render-secrets.sh # shellcheck source=deploy/render-secrets.sh
. "$APP_DIR/deploy/render-secrets.sh" . "$INFRA_DIR/deploy/render-secrets.sh"
render_thermograph_secrets "$APP_DIR" render_thermograph_secrets "$INFRA_DIR"
fi fi
set -a; . /etc/thermograph.env 2>/dev/null || true; set +a set -a; . /etc/thermograph.env 2>/dev/null || true; set +a
@ -120,9 +123,15 @@ fi
# freshly-pulled one, and the SERVICE/tag contract passes through unchanged -- # freshly-pulled one, and the SERVICE/tag contract passes through unchanged --
# the app repos' workflows never need to know which mode a host runs. # the app repos' workflows never need to know which mode a host runs.
if [ "$(cat /etc/thermograph/deploy-mode 2>/dev/null || true)" = "stack" ]; then if [ "$(cat /etc/thermograph/deploy-mode 2>/dev/null || true)" = "stack" ]; then
exec bash "$APP_DIR/deploy/stack/deploy-stack.sh" exec bash "$INFRA_DIR/deploy/stack/deploy-stack.sh"
fi fi
# Monorepo: run every docker compose command from infra/ (the git operations
# above ran at the checkout root). The compose project name is pinned in the
# file itself (`name: thermograph`), so moving the working directory does NOT
# rename the project and recreate the stack under a second name.
cd "$INFRA_DIR"
# Registry-pull cutover: pull the image each app repo's build-push.yml already # Registry-pull cutover: pull the image each app repo's build-push.yml already
# built and pushed, instead of building in place. This checkout is # built and pushed, instead of building in place. This checkout is
# thermograph-infra, not an app repo, so there's no "current commit" to derive # thermograph-infra, not an app repo, so there's no "current commit" to derive
@ -137,7 +146,7 @@ export REGISTRY_HOST BACKEND_IMAGE_PATH FRONTEND_IMAGE_PATH
# bare `local` that would recreate/downgrade it). The incoming env for the # bare `local` that would recreate/downgrade it). The incoming env for the
# service being deployed then overrides its line below. This file is untracked # service being deployed then overrides its line below. This file is untracked
# (see .gitignore), so `git reset --hard` above leaves it in place. # (see .gitignore), so `git reset --hard` above leaves it in place.
TAGS_FILE="$APP_DIR/deploy/.image-tags.env" TAGS_FILE="$INFRA_DIR/deploy/.image-tags.env"
# Capture the tags the caller explicitly passed BEFORE sourcing -- they must # Capture the tags the caller explicitly passed BEFORE sourcing -- they must
# WIN. The file only supplies the *sibling's* last-known tag; sourcing it # WIN. The file only supplies the *sibling's* last-known tag; sourcing it
# unconditionally would clobber an incoming tag (e.g. a frontend deploy whose # unconditionally would clobber an incoming tag (e.g. a frontend deploy whose
@ -277,8 +286,8 @@ fi
# roll never garbage-collects the image a rollback would need; docker also # roll never garbage-collects the image a rollback would need; docker also
# refuses to remove an image any container still uses. # refuses to remove an image any container still uses.
echo "==> Pruning old app-image tags" echo "==> Pruning old app-image tags"
_be_repo="${REGISTRY_HOST}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}" _be_repo="${REGISTRY_HOST}/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}"
_fe_repo="${REGISTRY_HOST}/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}" _fe_repo="${REGISTRY_HOST}/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}"
docker images --format '{{.Repository}}:{{.Tag}}' \ docker images --format '{{.Repository}}:{{.Tag}}' \
| grep -E "^(${_be_repo}|${_fe_repo}):" \ | grep -E "^(${_be_repo}|${_fe_repo}):" \
| grep -v -e "^${_be_repo}:${BACKEND_IMAGE_TAG}$" -e "^${_fe_repo}:${FRONTEND_IMAGE_TAG}$" \ | grep -v -e "^${_be_repo}:${BACKEND_IMAGE_TAG}$" -e "^${_fe_repo}:${FRONTEND_IMAGE_TAG}$" \

View file

@ -48,10 +48,10 @@ export STACK_NAME
# Render (SOPS) + source /etc/thermograph.env exactly like deploy.sh, then # Render (SOPS) + source /etc/thermograph.env exactly like deploy.sh, then
# install the uid-10001-readable copy the tasks' env-entrypoint shim sources. # install the uid-10001-readable copy the tasks' env-entrypoint shim sources.
# 10001 = the app images' `thermograph` user; the file is 0400 to that uid. # 10001 = the app images' `thermograph` user; the file is 0400 to that uid.
if [ -f "$APP_DIR/deploy/render-secrets.sh" ]; then if [ -f "$APP_DIR/infra/deploy/render-secrets.sh" ]; then
# shellcheck source=deploy/render-secrets.sh # shellcheck source=deploy/render-secrets.sh
. "$APP_DIR/deploy/render-secrets.sh" . "$APP_DIR/infra/deploy/render-secrets.sh"
render_thermograph_secrets "$APP_DIR" render_thermograph_secrets "$APP_DIR/infra"
fi fi
set -a; . /etc/thermograph.env 2>/dev/null || true; set +a set -a; . /etc/thermograph.env 2>/dev/null || true; set +a
sudo install -o 10001 -g 0 -m 0400 /etc/thermograph.env /etc/thermograph/stack.env \ sudo install -o 10001 -g 0 -m 0400 /etc/thermograph.env /etc/thermograph/stack.env \
@ -62,7 +62,7 @@ sudo install -o 10001 -g 0 -m 0400 /etc/thermograph.env /etc/thermograph/stack.e
# supplies the sibling. Stack mode keeps its own file so test/real never mix. # supplies the sibling. Stack mode keeps its own file so test/real never mix.
REGISTRY_HOST="${REGISTRY_HOST:-git.thermograph.org}" REGISTRY_HOST="${REGISTRY_HOST:-git.thermograph.org}"
export REGISTRY_HOST export REGISTRY_HOST
TAGS_FILE="$APP_DIR/deploy/.stack-image-tags.env" TAGS_FILE="$APP_DIR/infra/deploy/.stack-image-tags.env"
_incoming_backend="${BACKEND_IMAGE_TAG:-}" _incoming_backend="${BACKEND_IMAGE_TAG:-}"
_incoming_frontend="${FRONTEND_IMAGE_TAG:-}" _incoming_frontend="${FRONTEND_IMAGE_TAG:-}"
if [ -f "$TAGS_FILE" ]; then set -a; . "$TAGS_FILE"; set +a; fi if [ -f "$TAGS_FILE" ]; then set -a; . "$TAGS_FILE"; set +a; fi
@ -77,8 +77,8 @@ case "$SERVICE" in
esac esac
export BACKEND_IMAGE_TAG="${BACKEND_IMAGE_TAG:-local}" export BACKEND_IMAGE_TAG="${BACKEND_IMAGE_TAG:-local}"
export FRONTEND_IMAGE_TAG="${FRONTEND_IMAGE_TAG:-local}" export FRONTEND_IMAGE_TAG="${FRONTEND_IMAGE_TAG:-local}"
BACKEND_IMAGE="$REGISTRY_HOST/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:$BACKEND_IMAGE_TAG" BACKEND_IMAGE="$REGISTRY_HOST/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}:$BACKEND_IMAGE_TAG"
FRONTEND_IMAGE="$REGISTRY_HOST/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}:$FRONTEND_IMAGE_TAG" FRONTEND_IMAGE="$REGISTRY_HOST/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}:$FRONTEND_IMAGE_TAG"
# --- timescale image pin --------------------------------------------------------- # --- timescale image pin ---------------------------------------------------------
# Hazard #7: the db image under an existing volume must never drift. Resolve # Hazard #7: the db image under an existing volume must never drift. Resolve
@ -138,7 +138,7 @@ FIRST_DEPLOY_MIGRATE=0
docker service inspect "${STACK_NAME}_db" >/dev/null 2>&1 || FIRST_DEPLOY_MIGRATE=1 docker service inspect "${STACK_NAME}_db" >/dev/null 2>&1 || FIRST_DEPLOY_MIGRATE=1
if [ "$SERVICE" = "all" ] || ! docker service inspect "${STACK_NAME}_web" >/dev/null 2>&1; then if [ "$SERVICE" = "all" ] || ! docker service inspect "${STACK_NAME}_web" >/dev/null 2>&1; then
echo "==> docker stack deploy ($STACK_NAME)" echo "==> docker stack deploy ($STACK_NAME)"
docker stack deploy --with-registry-auth -c "$APP_DIR/deploy/stack/thermograph-stack.yml" "$STACK_NAME" docker stack deploy --with-registry-auth -c "$APP_DIR/infra/deploy/stack/thermograph-stack.yml" "$STACK_NAME"
# First-ever deploy ran no migrate above (db didn't exist): wait for db, # First-ever deploy ran no migrate above (db didn't exist): wait for db,
# migrate, then force web/worker to restart cleanly against the schema. # migrate, then force web/worker to restart cleanly against the schema.
if [ "${FIRST_DEPLOY_MIGRATE:-0}" = "1" ]; then if [ "${FIRST_DEPLOY_MIGRATE:-0}" = "1" ]; then
@ -179,7 +179,7 @@ if ! docker ps --format '{{.Names}}' | grep -qx "$LB_NAME"; then
docker run -d --name "$LB_NAME" --restart unless-stopped \ docker run -d --name "$LB_NAME" --restart unless-stopped \
--network "$NET" \ --network "$NET" \
-p "127.0.0.1:${LB_HTTP_PORT}:8137" -p "127.0.0.1:${LB_FE_PORT}:8080" \ -p "127.0.0.1:${LB_HTTP_PORT}:8137" -p "127.0.0.1:${LB_FE_PORT}:8080" \
-v "$APP_DIR/deploy/stack/lb/Caddyfile:/etc/caddy/Caddyfile:ro" \ -v "$APP_DIR/infra/deploy/stack/lb/Caddyfile:/etc/caddy/Caddyfile:ro" \
caddy:2-alpine >/dev/null caddy:2-alpine >/dev/null
fi fi
@ -205,8 +205,8 @@ FRONTEND_IMAGE_TAG=$FRONTEND_IMAGE_TAG
EOF EOF
# GC superseded app-image tags (keep the running pair), same as deploy.sh. # GC superseded app-image tags (keep the running pair), same as deploy.sh.
_be_repo="$REGISTRY_HOST/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}" _be_repo="$REGISTRY_HOST/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}"
_fe_repo="$REGISTRY_HOST/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}" _fe_repo="$REGISTRY_HOST/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}"
docker images --format '{{.Repository}}:{{.Tag}}' \ docker images --format '{{.Repository}}:{{.Tag}}' \
| grep -E "^(${_be_repo}|${_fe_repo}):" \ | grep -E "^(${_be_repo}|${_fe_repo}):" \
| grep -v -e "^${_be_repo}:${BACKEND_IMAGE_TAG}$" -e "^${_fe_repo}:${FRONTEND_IMAGE_TAG}$" \ | grep -v -e "^${_be_repo}:${BACKEND_IMAGE_TAG}$" -e "^${_fe_repo}:${FRONTEND_IMAGE_TAG}$" \

View file

@ -64,7 +64,7 @@ services:
condition: on-failure condition: on-failure
web: web:
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:${BACKEND_IMAGE_TAG:?required} image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}:${BACKEND_IMAGE_TAG:?required}
entrypoint: ["/host/env-entrypoint.sh"] entrypoint: ["/host/env-entrypoint.sh"]
environment: environment:
THERMOGRAPH_DATABASE_URL: postgresql+asyncpg://thermograph:${POSTGRES_PASSWORD}@db:5432/thermograph THERMOGRAPH_DATABASE_URL: postgresql+asyncpg://thermograph:${POSTGRES_PASSWORD}@db:5432/thermograph
@ -105,7 +105,7 @@ services:
failure_action: rollback failure_action: rollback
worker: worker:
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:${BACKEND_IMAGE_TAG:?required} image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}:${BACKEND_IMAGE_TAG:?required}
entrypoint: ["/host/env-entrypoint.sh"] entrypoint: ["/host/env-entrypoint.sh"]
environment: environment:
THERMOGRAPH_DATABASE_URL: postgresql+asyncpg://thermograph:${POSTGRES_PASSWORD}@db:5432/thermograph THERMOGRAPH_DATABASE_URL: postgresql+asyncpg://thermograph:${POSTGRES_PASSWORD}@db:5432/thermograph
@ -139,7 +139,7 @@ services:
condition: on-failure condition: on-failure
frontend: frontend:
image: ${REGISTRY_HOST:-git.thermograph.org}/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}:${FRONTEND_IMAGE_TAG:?required} image: ${REGISTRY_HOST:-git.thermograph.org}/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}:${FRONTEND_IMAGE_TAG:?required}
entrypoint: ["/host/env-entrypoint.sh"] entrypoint: ["/host/env-entrypoint.sh"]
environment: environment:
THERMOGRAPH_BASE: / THERMOGRAPH_BASE: /

View file

@ -25,6 +25,13 @@
# so `docker compose up` can interpolate it. It is used BOTH to initialize the db # so `docker compose up` can interpolate it. It is used BOTH to initialize the db
# container and to build backend's THERMOGRAPH_DATABASE_URL below. # container and to build backend's THERMOGRAPH_DATABASE_URL below.
# Pin the compose project name: the monorepo layout runs compose from
# /opt/thermograph/infra, and without an explicit name the project would be
# derived from that directory ("infra") -- silently a NEW project, recreating
# the whole stack beside the running one. LAN dev overrides this via
# COMPOSE_PROJECT_NAME=thermograph-dev (the env var always wins over this key).
name: thermograph
services: services:
db: db:
# TimescaleDB on PostgreSQL 18 (the stock image already sets # TimescaleDB on PostgreSQL 18 (the stock image already sets
@ -90,10 +97,10 @@ services:
# No `build:` here -- infra holds no Dockerfile; each service's Dockerfile # No `build:` here -- infra holds no Dockerfile; each service's Dockerfile
# lives in its own app repo. deploy.sh sets BACKEND_IMAGE_TAG to the SHA # lives in its own app repo. deploy.sh sets BACKEND_IMAGE_TAG to the SHA
# build-push.yml pushed for the backend commit being deployed; local dev # build-push.yml pushed for the backend commit being deployed; local dev
# builds `emi/thermograph-backend/app:local` from the backend repo (see # builds `emi/thermograph/backend:local` from the backend repo (see
# infra Makefile) and this pulls/uses it. BACKEND_IMAGE_PATH/TAG are # infra Makefile) and this pulls/uses it. BACKEND_IMAGE_PATH/TAG are
# independent of the frontend's, so the two services deploy separately. # independent of the frontend's, so the two services deploy separately.
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:${BACKEND_IMAGE_TAG:-local} image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}:${BACKEND_IMAGE_TAG:-local}
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
@ -155,7 +162,7 @@ services:
# from its own Dockerfile (which starts `uvicorn app:app` directly -- no # from its own Dockerfile (which starts `uvicorn app:app` directly -- no
# THERMOGRAPH_SERVICE_ROLE process-picking anymore). Independent # THERMOGRAPH_SERVICE_ROLE process-picking anymore). Independent
# FRONTEND_IMAGE_TAG so a frontend deploy never disturbs the backend's tag. # FRONTEND_IMAGE_TAG so a frontend deploy never disturbs the backend's tag.
image: ${REGISTRY_HOST:-git.thermograph.org}/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}:${FRONTEND_IMAGE_TAG:-local} image: ${REGISTRY_HOST:-git.thermograph.org}/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}:${FRONTEND_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: