feat(deploy): prisma migration history, /version, galactus standalone deploy
Closes the gap between "what tag did I deploy" and "what is actually running", and gives the schema a history that can be reasoned about across releases. Migrations - Baseline the existing schema as 0000_init (migrate diff --from-empty). The schema had only ever been applied with `prisma db push`, so no history existed and schema state was disconnected from app version. Existing databases must be baselined once with `migrate resolve --applied 0000_init`; the workflows print this remedy on P3005. - Run `prisma migrate deploy` as a deploy STEP, not the container CMD — as a CMD, N replicas would race each other applying the same migration. Version reporting - GET /version on the API reports the APP_VERSION / GIT_SHA / BUILD_DATE that build.yml already baked into both images but nothing ever read. - The web footer shows the web build and flags an api/web mismatch. The two cannot drift at build time (one matrix run) but can at deploy time. - Both deploy workflows now fail if the running API does not report the tag that was dispatched — a stack naming a tag is not proof of what is running. - scripts/set-version.mjs stamps every package.json, which had all sat at 0.1.0 while real releases shipped as v1.x. Pre-migrate backup - deploy/scripts/pre-migrate-backup.mjs dumps the database from INSIDE the still-running old API container over Portainer's Docker API, so the file lands in the volume the Operaciones restore screen reads. A dump taken on the CI runner would be unreachable by the only restore path we have. Verifies the artefact with `gzip -t` before letting the migration proceed. galactus - deploy/galactus/*.compose.yml: standalone-Docker ports of the Swarm stacks. Plain compose silently ignores `deploy:`, so restart_policy becomes `restart: unless-stopped` — without it nothing returns after a host reboot. - .gitea/workflows/deploy-galactus.yml drives endpoint 3 with its own secrets. Fixes - deploy.yml passed `endpoint_id` and `pull_image` to cssnr/portainer-stack-deploy-action, which has no such inputs (they are `endpoint` and `pull`). The endpoint was silently never set. docs/DEPLOY_AND_MIGRATIONS.md documents expand/contract as the rule for schema changes: Prisma has no down-migrations, so a code rollback never rolls the schema back, and restoring the replication master from a dump diverges every replica. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# NestJS API + Next.js web on galactus (standalone Docker, Portainer endpoint 3).
|
||||
#
|
||||
# Standalone port of deploy/jorgecuadros-app.stack.yml — see the header of
|
||||
# deploy/galactus/jorgecuadros-db.compose.yml for the Swarm keys plain compose
|
||||
# silently ignores. The one that matters most here: without
|
||||
# `restart: unless-stopped` neither service returns after a host reboot.
|
||||
#
|
||||
# Cross-stack traffic still goes over the HOST, not service DNS. db and minio
|
||||
# are separate Portainer stacks, so they are on separate compose networks and
|
||||
# their service names do not resolve from here. DATABASE_URL / S3_ENDPOINT must
|
||||
# name galactus's own address and the published port — exactly as on cubex
|
||||
# today. Do not "simplify" them to `mysql:3306`.
|
||||
#
|
||||
# The web image is NOT URL-baked: the browser's API origin is injected at
|
||||
# runtime from API_ORIGIN (apps/web/src/app/layout.tsx), so the same image works
|
||||
# for any deployment. APP_VERSION / GIT_SHA / BUILD_DATE come baked in from
|
||||
# build.yml and are surfaced at GET /version (api) and in the web footer.
|
||||
#
|
||||
# Keep in sync with deploy/jorgecuadros-app.stack.yml when either changes.
|
||||
|
||||
services:
|
||||
api:
|
||||
image: git.mancinas.io/rmancinas/jorgecuadros-api:${APP_TAG:-latest}
|
||||
restart: unless-stopped
|
||||
# Stable handle for deploy/scripts/pre-migrate-backup.sh, which finds this
|
||||
# container by label to run mysqldump into the backup volume. A label
|
||||
# survives stack renames; the compose service name does not.
|
||||
labels:
|
||||
io.jorgecuadros.role: "api"
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL:?DATABASE_URL must be set}
|
||||
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET must be set}
|
||||
WEB_ORIGIN: ${WEB_ORIGIN:?WEB_ORIGIN must be set}
|
||||
PORT: "3001"
|
||||
INGEST_DIR: /data/ingest
|
||||
BACKUP_DIR: /data/backups
|
||||
MIGRATION_ENV: prod
|
||||
S3_ENDPOINT: ${S3_ENDPOINT:?S3_ENDPOINT must be set}
|
||||
S3_BUCKET: ${S3_BUCKET:-jorgecuadros-documents}
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER must be set}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD must be set}
|
||||
ports:
|
||||
- "${API_PORT:-3001}:3001"
|
||||
volumes:
|
||||
# Uploaded Access files and DB backups. Named, so they survive every
|
||||
# redeploy — and so the pre-migrate dump the deploy takes is the same
|
||||
# file the "Operaciones" restore screen lists.
|
||||
- ingest_data:/data/ingest
|
||||
- backup_data:/data/backups
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://localhost:3001/health || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
web:
|
||||
image: git.mancinas.io/rmancinas/jorgecuadros-web:${APP_TAG:-latest}
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
io.jorgecuadros.role: "web"
|
||||
environment:
|
||||
# Public API URL the browser calls (injected at runtime, see layout.tsx).
|
||||
API_ORIGIN: ${API_ORIGIN:?API_ORIGIN must be set}
|
||||
ports:
|
||||
- "${WEB_PORT:-3000}:3000"
|
||||
depends_on:
|
||||
# Unlike Swarm — which ignores depends_on entirely — plain compose honours
|
||||
# this, so web waits for the API to pass its healthcheck.
|
||||
api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/ >/dev/null 2>&1 || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
ingest_data:
|
||||
backup_data:
|
||||
Reference in New Issue
Block a user