# MailFlow — pre-built image install # # Quick start: # curl -o docker-compose.yml https://raw.githubusercontent.com/maathimself/mailflow/main/docker-compose.ghcr.yml # curl -o .env https://raw.githubusercontent.com/maathimself/mailflow/main/.env.example # # edit .env, then: # docker compose up -d # # To pin a specific version instead of latest, set MAILFLOW_VERSION in your .env: # MAILFLOW_VERSION=1.0.0 services: # ── Frontend (nginx — serves React SPA + proxies API) ─────────────────────── # Exposes HTTPS on APP_PORT and HTTP on APP_HTTP_PORT. # HTTP is intended for use behind a reverse proxy that terminates TLS. frontend: image: ghcr.io/maathimself/mailflow-frontend:${MAILFLOW_VERSION:-latest} container_name: mailflow-frontend restart: unless-stopped ports: - "${APP_PORT:-443}:443" - "${APP_HTTP_PORT:-80}:80" environment: # off: HTTP on port 80 only, behind your own TLS-terminating proxy (#516). NGINX_TLS: ${NGINX_TLS:-} volumes: - ./certs:/etc/nginx/ssl depends_on: backend: condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -qO/dev/null http://127.0.0.1:80/ || exit 1"] interval: 15s timeout: 5s retries: 3 start_period: 20s networks: - mailflow # ── Backend API ────────────────────────────────────────────────────────────── backend: image: ghcr.io/maathimself/mailflow-backend:${MAILFLOW_VERSION:-latest} container_name: mailflow-backend restart: unless-stopped # Room for a send in flight to finish on a restart (the backend waits up to 25 s). stop_grace_period: 30s healthcheck: test: ["CMD-SHELL", "wget -qO- http://localhost:3000/api/health || exit 1"] interval: 15s timeout: 5s retries: 3 start_period: 30s environment: NODE_ENV: production PORT: 3000 SESSION_SECRET: ${SESSION_SECRET} APP_URL: ${APP_URL:-} FRONTEND_URL: ${APP_URL:-} DB_HOST: ${DB_HOST:-postgres} DB_PORT: ${DB_PORT:-5432} DB_NAME: ${DB_NAME:-mailflow} DB_USER: ${DB_USER:-mailflow} DB_PASSWORD: ${DB_PASSWORD} REDIS_URL: redis://redis:6379 ENCRYPTION_KEY: ${ENCRYPTION_KEY:-} VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-} VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-} VAPID_SUBJECT: ${VAPID_SUBJECT:-} IMAP_MAX_PERSISTENT_PER_HOST: ${IMAP_MAX_PERSISTENT_PER_HOST:-} TRUST_PROXY_HOPS: ${TRUST_PROXY_HOPS:-} # false stops offering MailFlow's built-in Microsoft app for personal accounts (#674). MS_BUILTIN_APP: ${MS_BUILTIN_APP:-} # Your own OAuth apps (#676). Values saved under Settings > Email providers take # precedence, field by field; an empty value here counts as not set. MS_CLIENT_ID: ${MS_CLIENT_ID:-} MS_CLIENT_SECRET: ${MS_CLIENT_SECRET:-} MS_TENANT_ID: ${MS_TENANT_ID:-} MS_REDIRECT_URI: ${MS_REDIRECT_URI:-} GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-} GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-} GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-} depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - mailflow # ── PostgreSQL ─────────────────────────────────────────────────────────────── postgres: image: postgres:16-alpine container_name: mailflow-postgres restart: unless-stopped environment: POSTGRES_DB: ${DB_NAME:-mailflow} POSTGRES_USER: ${DB_USER:-mailflow} POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U mailflow -d mailflow"] interval: 10s timeout: 5s retries: 5 start_period: 20s networks: - mailflow # ── Redis (sessions + caching) ─────────────────────────────────────────────── redis: image: redis:7-alpine container_name: mailflow-redis restart: unless-stopped command: redis-server --save 60 1 --loglevel warning volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 5 start_period: 10s networks: - mailflow # ── Caddy (automatic HTTPS via Let's Encrypt — optional) ──────────────────── # Use docker-compose.https.yml to avoid the port conflict with the frontend: # docker compose -f docker-compose.yml -f docker-compose.https.yml --profile https up -d # Requires DOMAIN, ACME_EMAIL, and APP_URL to be set in .env, and ports 80/443 open. caddy: profiles: ["https"] image: caddy:2-alpine container_name: mailflow-caddy restart: unless-stopped ports: - "80:80" - "443:443" - "443:443/udp" environment: DOMAIN: ${DOMAIN:-localhost} ACME_EMAIL: ${ACME_EMAIL:-} volumes: - caddy_data:/data - caddy_config:/config configs: - source: caddyfile target: /etc/caddy/Caddyfile depends_on: - frontend networks: - mailflow volumes: postgres_data: redis_data: caddy_data: caddy_config: networks: mailflow: driver: bridge configs: caddyfile: content: | { email ${ACME_EMAIL:-} } ${DOMAIN:-localhost} { reverse_proxy frontend:443 { transport http { tls_insecure_skip_verify } } }