name: webmail services: postgres: image: postgres:16 environment: POSTGRES_USER: webmail POSTGRES_PASSWORD: webmail_dev POSTGRES_DB: webmail_dev ports: ["127.0.0.1:5433:5432"] volumes: [pgdata:/var/lib/postgresql/data] healthcheck: test: ["CMD-SHELL", "pg_isready -U webmail -d webmail_dev"] interval: 5s timeout: 3s retries: 10 redis: image: redis:7 ports: ["127.0.0.1:6380:6379"] healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 meilisearch: image: getmeili/meilisearch:v1.8 environment: MEILI_MASTER_KEY: dev_master_key_change_me MEILI_ENV: development ports: ["127.0.0.1:7700:7700"] volumes: [meilidata:/meili_data] healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:7700/health | grep -q available"] interval: 5s timeout: 3s retries: 10 # Sync-engine v2 workers (refactor §1): two roles sharing one image. Scale the # sync role with `--scale worker-sync=N` (lease protocol prevents double-owning). worker-sync: build: { context: ../backend } command: ["python", "-m", "workers.run"] environment: &worker-env WORKER_ROLE: sync APP_DOMAIN: dev.webmail.local DATABASE_URL: postgresql+asyncpg://webmail:webmail_dev@postgres:5432/webmail_dev REDIS_URL: redis://redis:6379/0 MASTER_KEY: CHANGE_ME_base64_32_bytes BLOB_ROOT: /var/blobs volumes: [blobs:/var/blobs] ulimits: { nofile: 65536 } depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } worker-jobs: build: { context: ../backend } command: ["python", "-m", "workers.run"] environment: <<: *worker-env WORKER_ROLE: jobs volumes: [blobs:/var/blobs] ulimits: { nofile: 65536 } depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } # Anti-spam engine, mirroring the production stack (see ../docker-compose.yml). # Dev publishes the ports on loopback so the plugin can be driven from the host # (RSPAMD_URL=http://127.0.0.1:11333) as well as from a container. rspamd: image: rspamd/rspamd:3.9 # Root only long enough to write the controller password; rspamd then drops # to _rspamd itself. /etc/rspamd is root-owned in the image. user: root entrypoint: - /bin/sh - -c - | mkdir -p /etc/rspamd/override.d printf 'password = "%s";\nenable_password = "%s";\n' \ "$$RSPAMD_CONTROLLER_PASSWORD" "$$RSPAMD_CONTROLLER_PASSWORD" \ > /etc/rspamd/override.d/worker-controller.inc exec /usr/bin/rspamd -f -u _rspamd -g _rspamd environment: RSPAMD_CONTROLLER_PASSWORD: ${RSPAMD_CONTROLLER_PASSWORD:-devpass} volumes: - ./rspamd/local.d:/etc/rspamd/local.d:ro - rspamddata:/var/lib/rspamd ports: - "127.0.0.1:11333:11333" # scan worker (/checkv2) - "127.0.0.1:11334:11334" # controller (/learnspam, /stat) healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:11334/ping | grep -qi pong"] interval: 10s timeout: 5s retries: 12 start_period: 15s depends_on: redis-rspamd: { condition: service_healthy } # Bayes/fuzzy storage, separate from the app redis on purpose (SPEC §C.2). redis-rspamd: image: redis:7-alpine command: [redis-server, --save, "300", "1", --maxmemory, 128mb, --maxmemory-policy, allkeys-lru] volumes: - rspamdredis:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 # Test mail servers (integration tests + manual dev against a throwaway mailbox) greenmail: image: greenmail/standalone:2.0.1 environment: GREENMAIL_OPTS: >- -Dgreenmail.setup.test.all -Dgreenmail.hostname=0.0.0.0 -Dgreenmail.users=dev:devpass@dev.local -Dgreenmail.verbose ports: - "127.0.0.1:3025:3025" # SMTP - "127.0.0.1:3143:3143" # IMAP - "127.0.0.1:3993:3993" # IMAPS volumes: pgdata: meilidata: blobs: rspamddata: rspamdredis: