fix(docker): Make port configuration consistent and dynamic
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Fixed the mismatch between environment variables:
- docker-compose now passes PORT (what NestJS/Next.js read) instead of API_PORT
- API_PORT/WEB_PORT control host mapping, PORT controls container

Changes:
- docker-compose: Pass PORT=${API_PORT} and PORT=${WEB_PORT} to containers
- docker-compose: Dynamic port mapping on both host and container sides
- docker-compose: Traefik labels use ${API_PORT}/${WEB_PORT} variables
- docker-compose: Healthchecks use PORT env var
- Dockerfiles: Removed hardcoded port values
- Dockerfiles: Healthchecks read PORT at runtime

This allows changing ports via API_PORT/WEB_PORT environment variables
and have all components (app, healthcheck, Traefik) use the correct port.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 01:29:15 -06:00
parent 8f63b3e1dc
commit aa17b9cb3b
3 changed files with 39 additions and 19 deletions

View File

@@ -80,12 +80,12 @@ WORKDIR /app/apps/api
# Switch to non-root user # Switch to non-root user
USER nestjs USER nestjs
# Expose API port # Expose API port (default 3001, can be overridden via PORT env var)
EXPOSE 3001 EXPOSE ${PORT:-3001}
# Health check # Health check uses PORT env var (set by docker-compose or defaults to 3001)
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" CMD node -e "const port = process.env.PORT || 3001; require('http').get('http://localhost:' + port + '/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# Use dumb-init to handle signals properly # Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"] ENTRYPOINT ["dumb-init", "--"]

View File

@@ -90,17 +90,16 @@ WORKDIR /app/apps/web
# Switch to non-root user # Switch to non-root user
USER nextjs USER nextjs
# Expose web port # Expose web port (default 3000, can be overridden via PORT env var)
EXPOSE 3000 EXPOSE ${PORT:-3000}
# Environment variables # Environment variables
ENV NODE_ENV=production ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0" ENV HOSTNAME="0.0.0.0"
# Health check # Health check uses PORT env var (set by docker-compose or defaults to 3000)
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" CMD node -e "const port = process.env.PORT || 3000; require('http').get('http://localhost:' + port, (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# Use dumb-init to handle signals properly # Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"] ENTRYPOINT ["dumb-init", "--"]

View File

@@ -1,4 +1,4 @@
version: '3.9' version: "3.9"
services: services:
# ====================== # ======================
@@ -149,7 +149,15 @@ services:
authentik-redis: authentik-redis:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9000/-/health/live/"] test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://localhost:9000/-/health/live/",
]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -293,8 +301,8 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
NODE_ENV: production NODE_ENV: production
# API Configuration # API Configuration - PORT is what NestJS reads
API_PORT: ${API_PORT:-3001} PORT: ${API_PORT:-3001}
API_HOST: ${API_HOST:-0.0.0.0} API_HOST: ${API_HOST:-0.0.0.0}
# Database # Database
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_password}@postgres:5432/${POSTGRES_DB:-mosaic} DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_password}@postgres:5432/${POSTGRES_DB:-mosaic}
@@ -311,14 +319,20 @@ services:
# Ollama (optional) # Ollama (optional)
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434} OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
ports: ports:
- "${API_PORT:-3001}:3001" - "${API_PORT:-3001}:${API_PORT:-3001}"
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
valkey: valkey:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] test:
[
"CMD",
"sh",
"-c",
'node -e "require(''http'').get(''http://localhost:${PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -334,7 +348,7 @@ services:
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaic.local}`)" - "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaic.local}`)"
- "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
- "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}" - "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}"
- "traefik.http.services.mosaic-api.loadbalancer.server.port=3001" - "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}" - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
# Let's Encrypt (if enabled) # Let's Encrypt (if enabled)
- "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
@@ -352,14 +366,21 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
NODE_ENV: production NODE_ENV: production
PORT: ${WEB_PORT:-3000}
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001} NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
ports: ports:
- "${WEB_PORT:-3000}:3000" - "${WEB_PORT:-3000}:${WEB_PORT:-3000}"
depends_on: depends_on:
api: api:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] test:
[
"CMD",
"sh",
"-c",
'node -e "require(''http'').get(''http://localhost:${PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -374,7 +395,7 @@ services:
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.local}`)" - "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.local}`)"
- "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
- "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}" - "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}"
- "traefik.http.services.mosaic-web.loadbalancer.server.port=3000" - "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}" - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
# Let's Encrypt (if enabled) # Let's Encrypt (if enabled)
- "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"