189 lines
5.6 KiB
YAML
189 lines
5.6 KiB
YAML
# ---------------------------------------------------------------------------
|
|
# Local development.
|
|
#
|
|
# Default (`pnpm infra:up`): backing services only — PostgreSQL, Redis, MinIO,
|
|
# Mailpit. The three applications run on the host via `pnpm dev`, because
|
|
# native file watching and HMR are dramatically faster than bind-mounted
|
|
# containers, especially on macOS.
|
|
#
|
|
# Full stack (`docker compose --profile full up`): everything containerised
|
|
# behind Nginx. This mirrors production topology and is what you use to verify
|
|
# routing, headers and container builds before shipping.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
name: sport-store
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:17-alpine
|
|
container_name: sport-postgres
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER:-sport}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-sport}
|
|
POSTGRES_DB: ${POSTGRES_DB:-sport_store}
|
|
# Deterministic collation: index ordering must not depend on the host.
|
|
POSTGRES_INITDB_ARGS: "--locale=C --encoding=UTF8"
|
|
ports:
|
|
- "${POSTGRES_PORT:-5433}:5432"
|
|
volumes:
|
|
- postgres-data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-sport} -d ${POSTGRES_DB:-sport_store}"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: sport-redis
|
|
restart: unless-stopped
|
|
# Cache-first policy: evict rather than refuse writes. Nothing in Redis is
|
|
# a system of record, so eviction is always preferable to an outage.
|
|
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru --appendonly no
|
|
ports:
|
|
- "${REDIS_PORT:-6380}:6379"
|
|
volumes:
|
|
- redis-data:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
|
|
# Stands in for Cloudflare R2. Same S3 API, so application code is identical.
|
|
minio:
|
|
image: minio/minio:latest
|
|
container_name: sport-minio
|
|
restart: unless-stopped
|
|
command: server /data --console-address ":9001"
|
|
environment:
|
|
MINIO_ROOT_USER: ${STORAGE_ACCESS_KEY_ID:-sportminio}
|
|
MINIO_ROOT_PASSWORD: ${STORAGE_SECRET_ACCESS_KEY:-sportminio}
|
|
ports:
|
|
- "9000:9000"
|
|
- "9001:9001"
|
|
volumes:
|
|
- minio-data:/data
|
|
healthcheck:
|
|
test: ["CMD", "mc", "ready", "local"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
minio-init:
|
|
image: minio/mc:latest
|
|
container_name: sport-minio-init
|
|
depends_on:
|
|
minio:
|
|
condition: service_healthy
|
|
entrypoint: >
|
|
/bin/sh -c "
|
|
mc alias set local http://minio:9000 ${STORAGE_ACCESS_KEY_ID:-sportminio} ${STORAGE_SECRET_ACCESS_KEY:-sportminio};
|
|
mc mb --ignore-existing local/${STORAGE_BUCKET:-sport-media};
|
|
mc anonymous set download local/${STORAGE_BUCKET:-sport-media};
|
|
echo 'MinIO bucket ready';
|
|
"
|
|
|
|
# Catches every outgoing email locally. UI on http://localhost:8025
|
|
mailpit:
|
|
image: axllent/mailpit:latest
|
|
container_name: sport-mailpit
|
|
restart: unless-stopped
|
|
ports:
|
|
- "1025:1025"
|
|
- "8025:8025"
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Application containers — `--profile full` only.
|
|
# ------------------------------------------------------------------------
|
|
|
|
api:
|
|
profiles: ["full"]
|
|
build:
|
|
context: .
|
|
dockerfile: infrastructure/docker/api.Dockerfile
|
|
container_name: sport-api
|
|
restart: unless-stopped
|
|
env_file: apps/api/.env
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-sport}:${POSTGRES_PASSWORD:-sport}@postgres:5432/${POSTGRES_DB:-sport_store}?schema=public
|
|
REDIS_URL: redis://redis:6379
|
|
STORAGE_ENDPOINT: http://minio:9000
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
expose:
|
|
- "4000"
|
|
healthcheck:
|
|
test:
|
|
[
|
|
"CMD",
|
|
"node",
|
|
"-e",
|
|
"fetch('http://127.0.0.1:4000/api/v1/health/live').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
|
|
]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 20s
|
|
|
|
storefront:
|
|
profiles: ["full"]
|
|
build:
|
|
context: .
|
|
dockerfile: infrastructure/docker/storefront.Dockerfile
|
|
args:
|
|
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost/api}
|
|
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost}
|
|
container_name: sport-storefront
|
|
restart: unless-stopped
|
|
environment:
|
|
# Server-side rendering talks to the API container directly.
|
|
API_INTERNAL_URL: http://api:4000
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
expose:
|
|
- "3000"
|
|
|
|
admin:
|
|
profiles: ["full"]
|
|
build:
|
|
context: .
|
|
dockerfile: infrastructure/docker/admin.Dockerfile
|
|
args:
|
|
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost/api}
|
|
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_ADMIN_URL:-http://admin.localhost}
|
|
container_name: sport-admin
|
|
restart: unless-stopped
|
|
environment:
|
|
API_INTERNAL_URL: http://api:4000
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
expose:
|
|
- "3001"
|
|
|
|
nginx:
|
|
profiles: ["full"]
|
|
image: nginx:1.27-alpine
|
|
container_name: sport-nginx
|
|
restart: unless-stopped
|
|
ports:
|
|
- "80:80"
|
|
volumes:
|
|
- ./infrastructure/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
- ./infrastructure/nginx/conf.d:/etc/nginx/conf.d:ro
|
|
depends_on:
|
|
- storefront
|
|
- admin
|
|
- api
|
|
|
|
volumes:
|
|
postgres-data:
|
|
redis-data:
|
|
minio-data:
|