version: '3.8' networks: traefik-network: external: false default: driver: bridge services: traefik: image: traefik:v3.0 container_name: traefik restart: unless-stopped ports: - "80:80" - "443:443" - "8080:8080" # Traefik dashboard (optional, can be removed in production) volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik/dynamic.yml:/dynamic.yml:ro - traefik-letsencrypt:/letsencrypt networks: - traefik-network labels: - "traefik.enable=true" - "traefik.http.routers.traefik.rule=Host(`traefik.jotko.site`)" - "traefik.http.routers.traefik.entrypoints=websecure" - "traefik.http.routers.traefik.tls.certresolver=letsencrypt" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.routers.traefik.middlewares=auth" - "traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH:-admin:$$apr1$$password}" # Set TRAEFIK_DASHBOARD_AUTH env var command: - --api.dashboard=true - --api.insecure=false - --providers.docker=true - --providers.docker.exposedbydefault=false - --providers.docker.network=traefik-network - --providers.file.filename=/dynamic.yml - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.web.http.redirections.entrypoint.permanent=true - --certificatesresolvers.letsencrypt.acme.email=admin@jotko.site - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web postgres: image: postgres:16-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: mymail volumes: - postgres-data:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 minio: image: minio/minio:latest command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin ports: - "9000:9000" - "9001:9001" volumes: - minio-data:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 20s retries: 3 api: build: context: . dockerfile: Dockerfile.api environment: DATABASE_URL: postgresql://postgres:postgres@postgres:5432/mymail REDIS_URL: redis://redis:6379 MINIO_ENDPOINT: minio:9000 MINIO_ACCESS_KEY: minioadmin MINIO_SECRET_KEY: minioadmin MINIO_BUCKET: mails JWT_SECRET: ${JWT_SECRET:-change-me-in-production} API_PORT: 3000 API_HOST: 0.0.0.0 networks: - default - traefik-network labels: - "traefik.enable=true" - "traefik.http.routers.api.rule=Host(`api.jotko.site`)" - "traefik.http.routers.api.entrypoints=websecure" - "traefik.http.routers.api.tls.certresolver=letsencrypt" - "traefik.http.services.api.loadbalancer.server.port=3000" - "traefik.http.routers.api.middlewares=cors" depends_on: postgres: condition: service_healthy redis: condition: service_healthy minio: condition: service_healthy restart: unless-stopped smtp: build: context: . dockerfile: Dockerfile.smtp environment: DATABASE_URL: postgresql://postgres:postgres@postgres:5432/mymail?sslmode=disable REDIS_URL: redis://redis:6379 MINIO_ENDPOINT: minio:9000 MINIO_ACCESS_KEY: minioadmin MINIO_SECRET_KEY: minioadmin MINIO_BUCKET: mails SMTP_HOST: 0.0.0.0 SMTP_PORT: 25 SMTP_DOMAIN: ${SMTP_DOMAIN:-mymail.com} SMTP_MAX_SIZE: 10485760 ports: - "25:25" depends_on: postgres: condition: service_healthy redis: condition: service_healthy minio: condition: service_healthy restart: unless-stopped worker: build: context: . dockerfile: Dockerfile.worker environment: DATABASE_URL: postgresql://postgres:postgres@postgres:5432/mymail?sslmode=disable REDIS_URL: redis://redis:6379 WORKER_CONCURRENCY: 10 WORKER_BATCH_SIZE: 100 depends_on: postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stopped deploy: replicas: 2 ui: build: context: . dockerfile: Dockerfile.ui networks: - default - traefik-network labels: - "traefik.enable=true" - "traefik.http.routers.ui.rule=Host(`jotko.site`) || Host(`www.jotko.site`)" - "traefik.http.routers.ui.entrypoints=websecure" - "traefik.http.routers.ui.tls.certresolver=letsencrypt" - "traefik.http.services.ui.loadbalancer.server.port=80" - "traefik.http.routers.ui.middlewares=security-headers" # API routing - route /api requests from main domain to API service - "traefik.http.routers.ui-api.rule=Host(`jotko.site`) && PathPrefix(`/api`)" - "traefik.http.routers.ui-api.entrypoints=websecure" - "traefik.http.routers.ui-api.tls.certresolver=letsencrypt" - "traefik.http.routers.ui-api.service=api" - "traefik.http.routers.ui-api.middlewares=strip-api-prefix" - "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api" # Redirect www to non-www - "traefik.http.routers.ui-www.rule=Host(`www.jotko.site`)" - "traefik.http.routers.ui-www.entrypoints=websecure" - "traefik.http.routers.ui-www.tls.certresolver=letsencrypt" - "traefik.http.routers.ui-www.middlewares=redirect-to-non-www" - "traefik.http.middlewares.redirect-to-non-www.redirectregex.regex=^https://www\\.jotko\\.site/(.*)" - "traefik.http.middlewares.redirect-to-non-www.redirectregex.replacement=https://jotko.site/$${1}" - "traefik.http.middlewares.redirect-to-non-www.redirectregex.permanent=true" depends_on: - api restart: unless-stopped volumes: postgres-data: redis-data: minio-data: traefik-letsencrypt: