Deploy (prod y dev)
El gateway corre en dos ambientes con mecanismos distintos: prod en un droplet
dedicado vía GitHub Actions, y dev en Dokploy. Ambos usan la imagen ya construida en
ghcr (el build con el paquete privado de ajolote lo hace el CI, con github_token de GitHub
Actions — nunca Dokploy).
Prod — droplet (GitHub Actions)
.github/workflows/deploy.yml: en push a main, tras el CI, buildea y pushea
ghcr.io/cuatro-quinas/proteus-gateway:{latest,<sha>}, entra a la tailnet, y por SSH al droplet
(tag:proteus) corre docker-compose.droplet.yml:
docker pullde la imagen.- Migra primero:
docker compose run --rm api pnpm migration:up(con las réplicas viejas aún sirviendo). docker compose up -d.
El .env de prod (secretos) se configura a mano una vez en el droplet, junto con los certs
de nginx. Ver el encabezado de docker-compose.droplet.yml.
El orden es migrar → up. Así el código nuevo nunca corre contra un schema sin migrar, y si la
migración falla, aborta el deploy dejando la versión vieja sirviendo. Requiere migraciones
backward-compatible (aditivas).
Dev — Dokploy
Vive en el droplet dokploy-dev (100.64.188.115, en la tailnet). Dokploy no buildea:
pullea la imagen :dev que el CI construye.
Flujo
push a dev
→ CI (.github/workflows/deploy-dev.yml): build & push ghcr :dev (token vía GitHub Actions)
→ CI: connect Tailscale → POST /api/application.deploy (x-api-key)
→ Dokploy: pull :dev (pull_policy: always) + redeploy (api + worker)
Piezas en el repo
dokploy/docker-compose.dokploy.yml—api+workerdesde la misma imagen (ghcr.io/cuatro-quinas/proteus-gateway:${GATEWAY_IMAGE_TAG:-latest}), endokploy-network, conpull_policy: always..github/workflows/deploy-dev.yml— build:dev+ Tailscale + trigger por API.dokploy/.env.dokploy.example— set de variables.
Config en Dokploy
- App tipo Application (source docker-compose) apuntando a
dokploy/docker-compose.dokploy.yml, branchdev. - Registry credential de ghcr (Settings → Registry:
ghcr.io+ usuario + PATread:packages) para pullear la imagen privada. - Environment (todo por nombre de contenedor sobre
dokploy-network, puertos internos):GATEWAY_IMAGE_TAG=devDATABASE_URL=postgres://…@databases-postgresqldb-…:5432/proteus_gateway_devDATABASE_SSL=false— el Postgres interno de Dokploy no tiene SSL.REDIS_HOST=databases-redis-…,RABBITMQ_URL=amqp://…@databases-and-services-rabbitmq-…:5672AUTH_SERVICE_URL=http://ajolote-backend-…:3010,PROTEUS_BASE_URL=http://proteus-backend-…:3005PROTEUS_API_KEY= elGATEWAY_INBOUND_API_KEYdel backend.NEW_RELIC_ENABLED=false
- Pre-deploy:
pnpm migration:up. - El servicio de RabbitMQ debe estar atachado a
dokploy-network(se le agrega la red en su compose).
Secrets de GitHub (para deploy-dev.yml)
DOKPLOY_API_TOKEN— token de Dokploy (Settings → API).DOKPLOY_APPLICATION_ID— id de la app (de la URL del panel.../services/application/<id>).TS_OAUTH_CLIENT_ID/TS_OAUTH_SECRET— para entrar a la tailnet (compartidos con prod).
Exposición: sólo tailnet
El api se publica atado a la IP de Tailscale, no a 0.0.0.0:
ports:
- "100.64.188.115:3001:3000"
Así no escucha en la IP pública del droplet. Quién puede llegar lo controla la ACL de
Tailscale — el grant group:devs → tag:dokploy-dev debe incluir el puerto 3001. Los devs le
pegan a http://100.64.188.115:3001/fhir (y /health/live para probar). Encima sigue la auth
del gateway (x-api-key).
${}en valores de env: en el.envde Dokploy,${algo}es interpolación de variable. Las credenciales van literales (unRABBITMQ_URL=amqp://${admin}:${pass}@…rompe el deploy).- Clone viejo: si el compose desplegado no refleja el último commit, Dokploy no re-pulleó el repo — forzá un Redeploy (el deploy loguea qué commit clona).
- Imagen
:devcacheada::deves mutable;pull_policy: alwaysgarantiza bajar la fresca. - New Relic: sin
NEW_RELIC_APP_NAMEtira error y no puede escribir su log (EACCES) →NEW_RELIC_ENABLED=falseen dev.
Log de arranque
Tanto api como worker loguean un RuntimeConfig al bootear (ver src/common/log-runtime-config.ts):
Proteus (mode + baseUrl), auth, DB (url enmascarada + ssl), redis, rabbit y el puerto. Sirve
para ver a qué quedó apuntando el proceso sin exponer secretos. La conectividad real a
Proteus/DB la confirma GET /health/live.