dorfteich/deploy/compose/docker-compose.yml
Claude Sonnet 5 0fae699018
All checks were successful
CD / Build and push images (push) Successful in 2m2s
CI / Lint, typecheck, test (push) Successful in 1m43s
CI / Auth e2e pack (push) Successful in 1m48s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 10s
Add file storage service and image upload API (#27)
Implements the FileStorage abstraction (uploads/<pondId>/<fileId> on the
mounted volume), the attachments model, and POST /ponds/:id/files, GET
/media/:fileId, DELETE /files/:id. Uploads are validated by sniffing
magic bytes rather than trusting the client's Content-Type/filename
(catches a renamed .html-as-.png), checked against the max_file_bytes
and storage_bytes quotas, and served with nosniff + immutable caching.

Closes #27
2026-07-08 10:35:03 +02:00

94 lines
2.6 KiB
YAML

# Production Compose stack — one file for every stage and for self-hosters.
# Configuration comes from .env (see .env.example); the host reverse proxy
# routes to the two published localhost ports (deployment.md §Compose).
#
# Networks: `frontend` is what the reverse proxy reaches (via published
# ports); `internal` connects api/collab to db and (later) the converter
# sidecars, which are never exposed.
name: ${COMPOSE_PROJECT_NAME:-dorfteich}
x-logging: &logging
logging:
driver: json-file
options:
max-size: '10m'
max-file: '5'
services:
web:
image: ${IMAGE_PREFIX:-dorfteich}-web:${TAG:-latest}
build:
context: ../..
dockerfile: apps/web/Dockerfile
args:
APP_VERSION: ${TAG:-latest}
restart: unless-stopped
ports:
- '127.0.0.1:${WEB_PORT:-8100}:8080'
networks: [frontend]
depends_on:
api:
condition: service_started
<<: *logging
api:
image: ${IMAGE_PREFIX:-dorfteich}-api:${TAG:-latest}
build:
context: ../..
dockerfile: apps/api/Dockerfile
args:
APP_VERSION: ${TAG:-latest}
restart: unless-stopped
environment:
NODE_ENV: production
PORT: '3000'
LOG_LEVEL: ${LOG_LEVEL:-info}
DATABASE_URL: postgresql://dorfteich:${POSTGRES_PASSWORD:?set in .env}@db:5432/dorfteich
# Public URL of this stage — e-mail links and the CSRF origin check
# depend on it matching what browsers actually use.
APP_BASE_URL: ${APP_BASE_URL:-http://localhost:5173}
# SMTP relay; defaults are only useful with the dev Mailpit overlay.
SMTP_HOST: ${SMTP_HOST:-localhost}
SMTP_PORT: ${SMTP_PORT:-1025}
SMTP_SECURE: ${SMTP_SECURE:-false}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASS: ${SMTP_PASS:-}
SMTP_FROM: ${SMTP_FROM:-Dorfteich <no-reply@localhost>}
# Matches the `uploads` volume mount below (ADR 0011).
UPLOADS_DIR: /data/uploads
ports:
- '127.0.0.1:${API_PORT:-8101}:3000'
networks: [frontend, internal]
volumes:
- uploads:/data/uploads
depends_on:
db:
condition: service_healthy
<<: *logging
db:
image: postgres:17.5-alpine
restart: unless-stopped
environment:
POSTGRES_USER: dorfteich
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env}
POSTGRES_DB: dorfteich
networks: [internal]
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U dorfteich -d dorfteich']
interval: 10s
timeout: 3s
retries: 12
<<: *logging
networks:
frontend:
internal:
volumes:
db-data:
uploads: