services: # ───────────────────────────────────────────── # Caddy — reverse proxy + automatic HTTPS (TLS via Let's Encrypt) # Handles all inbound traffic on 80/443 # ───────────────────────────────────────────── caddy: image: caddy:2-alpine container_name: alpine_caddy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config depends_on: - gitea networks: - alpine_net # ───────────────────────────────────────────── # PostgreSQL — primary database # AlpineConsent app data: users, sites, consent logs # (Gitea uses its own SQLite — no DB config needed here for Gitea) # ───────────────────────────────────────────── postgres: image: postgres:${POSTGRES_VERSION:-16-alpine} container_name: alpine_postgres restart: unless-stopped environment: POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: memory: 512M networks: - alpine_net # No host-port mapping — only reachable inside alpine_net # ───────────────────────────────────────────── # Redis — in-memory cache + rate limiting # Password protected; not exposed to host # ───────────────────────────────────────────── redis: image: redis:${REDIS_VERSION:-7-alpine} container_name: alpine_redis restart: unless-stopped command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: memory: 256M networks: - alpine_net # No host-port mapping — only reachable inside alpine_net # ───────────────────────────────────────────── # Gitea — self-hosted Git repository server # Database: SQLite (built-in, sufficient for team size) # HTTP handled by Caddy — no port 3000 on host # SSH on 2222 for git push # Push Mirror to GitHub configured inside Gitea UI # ───────────────────────────────────────────── gitea: image: gitea/gitea:${GITEA_VERSION:-1.22} container_name: alpine_gitea restart: unless-stopped environment: - USER_UID=1000 - USER_GID=1000 - GITEA__server__ROOT_URL=https://${GIT_DOMAIN}/ - GITEA__server__HTTP_PORT=3000 - GITEA__actions__ENABLED=true ports: - "2222:22" # SSH for git push volumes: - gitea_data:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro deploy: resources: limits: memory: 1024M networks: - alpine_net # Note: no depends_on postgres — Gitea uses SQLite, not PostgreSQL # ───────────────────────────────────────────── # Gitea Act Runner — CI/CD job executor # Connects to Gitea over internal Docker network via Caddy/domain # ───────────────────────────────────────────── gitea_runner: image: gitea/act_runner:${RUNNER_VERSION:-0.6.1} container_name: alpine_runner restart: unless-stopped environment: - GITEA_INSTANCE_URL=http://gitea:3000 - GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN} - GITEA_RUNNER_NAME=alpine_runner - GITEA_RUNNER_LABELS=ubuntu-latest,ubuntu-24.04 volumes: - /var/run/docker.sock:/var/run/docker.sock - gitea_runner_data:/data deploy: resources: limits: memory: 512M depends_on: - gitea networks: - alpine_net # ───────────────────────────────────────────── volumes: postgres_data: redis_data: gitea_data: gitea_runner_data: caddy_data: caddy_config: networks: alpine_net: driver: bridge