feat(deploy): app stack + manual Portainer deploy workflow
Add the missing api/web deployment path on top of the existing image build CI. - deploy/jorgecuadros-app.stack.yml: PROD app stack (api + web) pulling the git.mancinas.io registry images. Does not ship mysql/minio (separate stacks); API reaches them via DATABASE_URL / S3_ENDPOINT. API pinned to the jorgecuadros_db node for stable ingest/backup volumes; web is stateless. - deploy/jorgecuadros-app.env.example: documented stack env template. - .gitea/workflows/deploy.yml: manual (workflow_dispatch) deploy to Portainer via cssnr/portainer-stack-deploy-action. Inputs: image tag + scope (app = web+api, full = db+minio+app, applied db->minio->app). Make the web API origin runtime-configurable instead of build-baked: the root layout injects window.__API_ORIGIN__ from the API_ORIGIN env (force-dynamic) and lib/api.ts resolves it at runtime, so one built image serves any deployment. Also: dev.sh to run both dev servers (frees stale ports first) and move local dev to ports web 4500 / api 4501. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# Application stack for the Jorge Cuadros platform: the NestJS API + the Next.js
|
||||
# web front-end. The two images are built + pushed by .gitea/workflows/build.yml:
|
||||
# git.mancinas.io/rmancinas/jorgecuadros-api
|
||||
# git.mancinas.io/rmancinas/jorgecuadros-web
|
||||
#
|
||||
# This stack does NOT ship MySQL or MinIO — those are their own stacks
|
||||
# (deploy/jorgecuadros-db.stack.yml, deploy/jorgecuadros-minio.stack.yml). The
|
||||
# API reaches them over the network via DATABASE_URL / S3_ENDPOINT, which point
|
||||
# at the db + minio stacks' published ingress ports on the swarm host.
|
||||
#
|
||||
# Target: Portainer local endpoint on cubex (3-node Swarm). PROD only.
|
||||
# Deploy with a stack env that supplies every ${VAR:?...} below — see
|
||||
# deploy/jorgecuadros-app.env.example for the full list.
|
||||
#
|
||||
# Statefulness: the API keeps uploaded Access files (ingest) and DB backups on
|
||||
# named volumes, which are node-local. So the API is pinned to the same node as
|
||||
# the db/minio stacks (node label jorgecuadros_db == true) — a reschedule would
|
||||
# otherwise start against empty ingest/backup volumes. The web tier is
|
||||
# stateless and floats freely.
|
||||
#
|
||||
# The web image is NOT URL-baked: the browser's API origin is injected at
|
||||
# runtime from API_ORIGIN (see apps/web/src/app/layout.tsx), so this same image
|
||||
# works for any deployment — set the URL here, not at build time.
|
||||
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
api:
|
||||
image: git.mancinas.io/rmancinas/jorgecuadros-api:${APP_TAG:-latest}
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL:?DATABASE_URL must be set}
|
||||
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET must be set}
|
||||
# CORS: the public origin the browser loads the web app from.
|
||||
WEB_ORIGIN: ${WEB_ORIGIN:?WEB_ORIGIN must be set}
|
||||
PORT: "3001"
|
||||
INGEST_DIR: /data/ingest
|
||||
BACKUP_DIR: /data/backups
|
||||
MIGRATION_ENV: prod
|
||||
# Object storage — internal endpoint the API (server-side) uses to reach
|
||||
# the minio stack. Not browser-facing (downloads proxy through the API).
|
||||
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:
|
||||
- target: 3001
|
||||
published: ${API_PORT:-3001}
|
||||
protocol: tcp
|
||||
mode: ingress
|
||||
volumes:
|
||||
- ingest_data:/data/ingest
|
||||
- backup_data:/data/backups
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.labels.jorgecuadros_db == true
|
||||
restart_policy:
|
||||
condition: any
|
||||
update_config:
|
||||
order: stop-first
|
||||
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}
|
||||
environment:
|
||||
# Public API URL the browser calls (injected at runtime, see layout.tsx).
|
||||
API_ORIGIN: ${API_ORIGIN:?API_ORIGIN must be set}
|
||||
ports:
|
||||
- target: 3000
|
||||
published: ${WEB_PORT:-3000}
|
||||
protocol: tcp
|
||||
mode: ingress
|
||||
depends_on:
|
||||
- api
|
||||
deploy:
|
||||
replicas: 1
|
||||
restart_policy:
|
||||
condition: any
|
||||
update_config:
|
||||
order: start-first
|
||||
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