Files
LambdaMail/docker-compose.yaml
T
Lucas Rayan 7c91182111 #1: fix webmail healthcheck - Next standalone binds IPv4-only, wget resolved localhost to ::1
Verified live via WSL Docker (docker compose up --wait): every other F0
service came up healthy, but web stayed unhealthy. Root cause was two
compounding issues, both invisible to static review:

1. Docker auto-sets HOSTNAME to the container ID; Next's standalone
   server.js honors that env var and binds only to it instead of all
   interfaces. Fixed by explicitly setting ENV HOSTNAME=0.0.0.0 in the
   Dockerfile, after Docker's automatic assignment.
2. Even bound to 0.0.0.0 (IPv4), the healthcheck's `wget http://localhost`
   resolved localhost to ::1 (IPv6) first inside the container, where
   nothing was listening. Fixed by pointing the healthcheck at
   127.0.0.1 explicitly instead of localhost.

Full stack (db, redis, migrate, auth, storage, protocols, web) now
reaches healthy end-to-end under docker compose up --wait.
2026-07-23 06:51:28 -03:00

130 lines
4.0 KiB
YAML

x-logging: &default-logging
driver: json-file
options: { max-size: "10m", max-file: "3" }
services:
db:
image: postgres:16-alpine
restart: unless-stopped
logging: *default-logging
environment:
POSTGRES_DB: ${POSTGRES_DB:?required}
POSTGRES_USER: ${POSTGRES_USER:?required}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?required}
ports: ["5432:5432"]
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
restart: unless-stopped
logging: *default-logging
command: >
redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:?required}
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
volumes: [redis_data:/data]
healthcheck:
test: ["CMD-SHELL", "redis-cli --no-auth-warning -a $${REDIS_PASSWORD} ping | grep -q PONG"]
interval: 5s
timeout: 5s
retries: 10
migrate:
image: migrate/migrate:v4.17.1
restart: "no"
volumes: ["./migrations:/migrations"]
depends_on:
db: { condition: service_healthy }
entrypoint:
- migrate
- -path=/migrations
- -database
- postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?sslmode=disable
- up
auth:
build: { context: ./services/auth }
restart: unless-stopped
logging: *default-logging
environment:
PORT: 3001
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?sslmode=disable
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
JWT_SECRET: ${JWT_SECRET:?required}
LAMBDAMAIL_MASTER_KEY: ${LAMBDAMAIL_MASTER_KEY:?required}
depends_on:
db: { condition: service_healthy }
redis: { condition: service_healthy }
migrate: { condition: service_completed_successfully }
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:3001/health/ready').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
storage:
build: { context: ./services/storage }
restart: unless-stopped
logging: *default-logging
environment:
PORT: 3002
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?sslmode=disable
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
depends_on:
db: { condition: service_healthy }
redis: { condition: service_healthy }
migrate: { condition: service_completed_successfully }
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:3002/health/ready').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
protocols:
build: { context: ./services/protocols }
restart: unless-stopped
logging: *default-logging
ports: ["8080:8080"]
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?sslmode=disable
depends_on:
db: { condition: service_healthy }
migrate: { condition: service_completed_successfully }
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health/ready || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
web:
build: { context: ./services/webmail }
restart: unless-stopped
logging: *default-logging
ports: ["3000:3000"]
environment:
PORT: 3000
AUTH_SERVICE_URL: http://auth:3001
STORAGE_SERVICE_URL: http://storage:3002
depends_on:
auth: { condition: service_healthy }
storage: { condition: service_healthy }
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
volumes:
postgres_data:
redis_data: