fix(docker): reach services by unique aliases, not bare service names
In Dokploy, services with a domain join the shared dokploy-network, where another project also has an `api`. nginx proxied /api to `api:3000`, so Docker DNS returned both containers and half of the requests went to the other app: 404 "Cannot POST /api/auth/token" on sign-in and "sign-in not configured" when /api/auth/config was answered by it. Services now get task-stream-* aliases on the project network and nginx and the API use them (task-stream-api, -postgres, -mongo, -redis). The API's OPENAPI_FILE is empty in the container; docs 07 explain the aliases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
# Same-origin API for the SPA: REST under /api, GraphQL (HTTP + WebSocket) under /graphql.
|
# Same-origin API for the SPA: REST under /api, GraphQL (HTTP + WebSocket) under /graphql.
|
||||||
|
# The API is reached by its unique alias (task-stream-api), not by the service name `api`:
|
||||||
|
# on a shared network (Dokploy) another project's `api` would get half of the requests.
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
default upgrade;
|
default upgrade;
|
||||||
'' close;
|
'' close;
|
||||||
@@ -9,13 +11,13 @@ server {
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://api:3000;
|
proxy_pass http://task-stream-api:3000;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /graphql {
|
location /graphql {
|
||||||
proxy_pass http://api:3000;
|
proxy_pass http://task-stream-api:3000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
# No ports are published here: services only talk over the compose network, and the web is
|
# No ports are published here: services only talk over the compose network, and the web is
|
||||||
# exposed through the platform's proxy (Dokploy domain → service `web`, port 80).
|
# exposed through the platform's proxy (Dokploy domain → service `web`, port 80).
|
||||||
#
|
#
|
||||||
|
# Services talk to each other by unique aliases (task-stream-*), never by the bare service
|
||||||
|
# name: on a shared network (Dokploy's dokploy-network) other projects also have an `api`,
|
||||||
|
# `postgres` or `redis`, and Docker DNS would hand out theirs too.
|
||||||
|
#
|
||||||
# Locally, `docker compose` also loads docker-compose.override.yml, which publishes the ports:
|
# Locally, `docker compose` also loads docker-compose.override.yml, which publishes the ports:
|
||||||
# docker compose up -d --build → everything: web on http://localhost:8080
|
# docker compose up -d --build → everything: web on http://localhost:8080
|
||||||
# docker compose up -d postgres mongo redis → only infrastructure (then `npx nx serve api` / `npx nx serve web`)
|
# docker compose up -d postgres mongo redis → only infrastructure (then `npx nx serve api` / `npx nx serve web`)
|
||||||
@@ -16,6 +20,7 @@ services:
|
|||||||
secrets: [nexus_token]
|
secrets: [nexus_token]
|
||||||
image: task-stream-web
|
image: task-stream-web
|
||||||
expose: ['80']
|
expose: ['80']
|
||||||
|
networks: [default]
|
||||||
depends_on:
|
depends_on:
|
||||||
api: { condition: service_healthy }
|
api: { condition: service_healthy }
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -27,12 +32,15 @@ services:
|
|||||||
secrets: [nexus_token]
|
secrets: [nexus_token]
|
||||||
image: task-stream-api
|
image: task-stream-api
|
||||||
expose: ['3000']
|
expose: ['3000']
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases: [task-stream-api]
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_URL: postgres://taskstream:taskstream@postgres:5432/taskstream
|
POSTGRES_URL: postgres://taskstream:taskstream@task-stream-postgres:5432/taskstream
|
||||||
# No migrations yet: TypeORM creates the event store and lookup tables.
|
# No migrations yet: TypeORM creates the event store and lookup tables.
|
||||||
POSTGRES_SYNCHRONIZE: 'true'
|
POSTGRES_SYNCHRONIZE: 'true'
|
||||||
MONGO_URL: mongodb://mongo:27017/taskstream_read
|
MONGO_URL: mongodb://task-stream-mongo:27017/taskstream_read
|
||||||
REDIS_URL: redis://redis:6379
|
REDIS_URL: redis://task-stream-redis:6379
|
||||||
EVENT_STREAM_KEY: task-stream:events
|
EVENT_STREAM_KEY: task-stream:events
|
||||||
EVENTS_BRIDGE_ENABLED: 'true'
|
EVENTS_BRIDGE_ENABLED: 'true'
|
||||||
PIREON_BASE_URL: https://oauth.pireon.dev
|
PIREON_BASE_URL: https://oauth.pireon.dev
|
||||||
@@ -40,8 +48,10 @@ services:
|
|||||||
PIREON_CLIENT_ID: ${PIREON_CLIENT_ID:-}
|
PIREON_CLIENT_ID: ${PIREON_CLIENT_ID:-}
|
||||||
PIREON_CLIENT_SECRET: ${PIREON_CLIENT_SECRET:-}
|
PIREON_CLIENT_SECRET: ${PIREON_CLIENT_SECRET:-}
|
||||||
GRAPHQL_PLAYGROUND: 'true'
|
GRAPHQL_PLAYGROUND: 'true'
|
||||||
# The SDL file is only useful in development (codegen).
|
# The SDL / OpenAPI files are only useful in development (codegen); the
|
||||||
|
# document is still served at /api/docs-json.
|
||||||
GRAPHQL_SCHEMA_FILE: ''
|
GRAPHQL_SCHEMA_FILE: ''
|
||||||
|
OPENAPI_FILE: ''
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres: { condition: service_healthy }
|
postgres: { condition: service_healthy }
|
||||||
mongo: { condition: service_healthy }
|
mongo: { condition: service_healthy }
|
||||||
@@ -50,6 +60,9 @@ services:
|
|||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17-alpine
|
image: postgres:17-alpine
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases: [task-stream-postgres]
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: taskstream
|
POSTGRES_USER: taskstream
|
||||||
POSTGRES_PASSWORD: taskstream
|
POSTGRES_PASSWORD: taskstream
|
||||||
@@ -62,6 +75,9 @@ services:
|
|||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo:8
|
image: mongo:8
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases: [task-stream-mongo]
|
||||||
volumes: [mongo-data:/data/db]
|
volumes: [mongo-data:/data/db]
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'mongosh', '--quiet', '--eval', 'db.adminCommand("ping")']
|
test: ['CMD', 'mongosh', '--quiet', '--eval', 'db.adminCommand("ping")']
|
||||||
@@ -70,6 +86,9 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases: [task-stream-redis]
|
||||||
command: ['redis-server', '--appendonly', 'yes']
|
command: ['redis-server', '--appendonly', 'yes']
|
||||||
volumes: [redis-data:/data]
|
volumes: [redis-data:/data]
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ docker compose down # parar (-v borra los datos)
|
|||||||
|
|
||||||
Para probar en local exactamente lo de producción: `docker compose -f docker-compose.yml up -d --build`.
|
Para probar en local exactamente lo de producción: `docker compose -f docker-compose.yml up -d --build`.
|
||||||
|
|
||||||
|
### Nombres únicos en la red
|
||||||
|
|
||||||
|
Los servicios se llaman entre sí por **alias únicos** (`task-stream-api`, `task-stream-postgres`, `task-stream-mongo`,
|
||||||
|
`task-stream-redis`), no por el nombre del servicio. Al dar dominio a un servicio, Dokploy lo conecta a la red
|
||||||
|
compartida `dokploy-network`, donde otros proyectos también tienen un `api`, un `postgres` o un `redis`: con nombres
|
||||||
|
genéricos, el DNS de Docker devuelve también los suyos y nginx reparte las peticiones entre ambos (síntoma: la mitad
|
||||||
|
de las llamadas a `/api/*` responden `404 Cannot GET …` y el login dice «no configurado»). Si añades un servicio,
|
||||||
|
dale su alias `task-stream-*` y úsalo en las URLs.
|
||||||
|
|
||||||
Todos tienen healthcheck; `web` espera a que `api` esté sana, y `api` a las tres bases de datos.
|
Todos tienen healthcheck; `web` espera a que `api` esté sana, y `api` a las tres bases de datos.
|
||||||
|
|
||||||
### Imágenes
|
### Imágenes
|
||||||
@@ -78,6 +87,8 @@ donde los lee Compose.
|
|||||||
| `AUTH_SESSION_TTL_SECONDS` | `60` | Cuánto se confía en un token validado antes de volver a preguntar a Pireon |
|
| `AUTH_SESSION_TTL_SECONDS` | `60` | Cuánto se confía en un token validado antes de volver a preguntar a Pireon |
|
||||||
| `GRAPHQL_PLAYGROUND` | `true` | GraphiQL e introspección |
|
| `GRAPHQL_PLAYGROUND` | `true` | GraphiQL e introspección |
|
||||||
| `GRAPHQL_SCHEMA_FILE` | `apps/api/schema.gql` | Dónde escribir el SDL (vacío = no escribir; así está en Docker) |
|
| `GRAPHQL_SCHEMA_FILE` | `apps/api/schema.gql` | Dónde escribir el SDL (vacío = no escribir; así está en Docker) |
|
||||||
|
| `SWAGGER_ENABLED` | `true` | API REST documentada: Swagger UI en `/api/docs`, OpenAPI en `/api/docs-json` |
|
||||||
|
| `OPENAPI_FILE` | `apps/api/openapi.json` | Dónde escribir el documento OpenAPI al arrancar (vacío = no escribir; así en Docker) |
|
||||||
|
|
||||||
La web no tiene configuración de entorno: llama a la API en su mismo origen (`/api`, `/graphql`).
|
La web no tiene configuración de entorno: llama a la API en su mismo origen (`/api`, `/graphql`).
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user