Reference: Messages compose.yml
name: messages
services:
postgresql:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: messages
POSTGRES_USER: messages
POSTGRES_PASSWORD: <db-password>
healthcheck:
test: ["CMD-SHELL", "pg_isready -U messages -d messages"]
interval: 1s
timeout: 2s
retries: 300
volumes: [messages_db:/var/lib/postgresql/data]
networks: [internal]
redis:
image: redis:7
restart: unless-stopped
networks: [internal]
opensearch:
image: opensearchproject/opensearch:2.19.2
restart: unless-stopped
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
- "DISABLE_INSTALL_DEMO_CONFIG=true"
- "DISABLE_SECURITY_PLUGIN=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9200"]
interval: 5s
timeout: 5s
retries: 60
start_period: 30s
ulimits:
memlock: {soft: -1, hard: -1}
nofile: {soft: 65536, hard: 65536}
volumes: [messages_opensearch:/usr/share/opensearch/data]
networks: [internal]
minio:
image: minio/minio:latest
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: messages
MINIO_ROOT_PASSWORD: <minio-password>
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 5s
timeout: 5s
retries: 20
start_period: 10s
volumes: [messages_minio:/data]
labels:
- "traefik.enable=true"
- "traefik.http.routers.messages-minio.rule=Host(`s3.messages.<domain>`)"
- "traefik.http.routers.messages-minio.entrypoints=websecure"
- "traefik.http.routers.messages-minio.tls=true"
- "traefik.http.routers.messages-minio.tls.certresolver=letsencrypt"
- "traefik.http.services.messages-minio.loadbalancer.server.port=9000"
networks: [proxy, internal]
minio-init:
image: minio/mc:latest
restart: "no"
depends_on: {minio: {condition: service_healthy}}
entrypoint: ["/bin/sh", "-c"]
command:
- |
mc alias set myminio http://minio:9000 messages <minio-password>
mc mb myminio/messages-media --ignore-existing
mc mb myminio/msg-imports --ignore-existing
mc mb myminio/msg-blobs --ignore-existing
networks: [internal]
kc-postgresql:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: <kc-db-password>
volumes: [kc_db:/var/lib/postgresql/data]
networks: [internal]
keycloak:
image: quay.io/keycloak/keycloak:latest
restart: unless-stopped
command: [start, --import-realm]
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: <kc-admin-password>
KC_DB: postgres
KC_DB_URL_HOST: kc-postgresql
KC_DB_URL_DATABASE: keycloak
KC_DB_PASSWORD: <kc-db-password>
KC_DB_USERNAME: keycloak
KC_HOSTNAME: https://auth.messages.<domain>
KC_PROXY_HEADERS: xforwarded
KC_HTTP_ENABLED: "true"
volumes:
- ./keycloak-realm.json:/opt/keycloak/data/import/realm.json:ro
- ../calendars/keycloak-realm.json:/opt/keycloak/data/import/calendars-realm.json:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.messages-keycloak.rule=Host(`auth.messages.<domain>`)"
- "traefik.http.routers.messages-keycloak.entrypoints=websecure"
- "traefik.http.routers.messages-keycloak.tls=true"
- "traefik.http.routers.messages-keycloak.tls.certresolver=letsencrypt"
- "traefik.http.services.messages-keycloak.loadbalancer.server.port=8080"
depends_on: [kc-postgresql]
networks: [proxy, internal]
backend:
image: ghcr.io/suitenumerique/messages-backend:main
restart: unless-stopped
command: ["gunicorn", "messages.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "2", "--timeout", "90"]
env_file: .env
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request as u; u.urlopen('http://localhost:8000/__heartbeat__/', timeout=2)"]
interval: 10s
timeout: 5s
retries: 20
start_period: 30s
depends_on:
postgresql: {condition: service_healthy}
redis: {condition: service_started}
opensearch: {condition: service_healthy}
minio: {condition: service_healthy}
labels:
- "traefik.enable=true"
- "traefik.http.routers.messages-backend.rule=Host(`api.messages.<domain>`)"
- "traefik.http.routers.messages-backend.entrypoints=websecure"
- "traefik.http.routers.messages-backend.tls=true"
- "traefik.http.routers.messages-backend.tls.certresolver=letsencrypt"
- "traefik.http.services.messages-backend.loadbalancer.server.port=8000"
networks:
proxy:
internal:
messages_static: {ipv4_address: 172.29.0.10}
worker:
image: ghcr.io/suitenumerique/messages-backend:main
restart: unless-stopped
command: ["python", "worker.py"]
env_file: .env
depends_on: {backend: {condition: service_healthy}}
networks: [internal]
frontend:
image: ghcr.io/suitenumerique/messages-frontend:main
restart: unless-stopped
env_file: .env
depends_on: {backend: {condition: service_healthy}}
labels:
- "traefik.enable=true"
- "traefik.http.routers.messages-frontend.rule=Host(`messages.<domain>`)"
- "traefik.http.routers.messages-frontend.entrypoints=websecure"
- "traefik.http.routers.messages-frontend.tls=true"
- "traefik.http.routers.messages-frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.messages-frontend.loadbalancer.server.port=8080"
networks:
proxy:
internal:
messages_static: {ipv4_address: 172.29.0.20}
mta-in:
image: ghcr.io/suitenumerique/messages-mta-in:main
restart: unless-stopped
user: root
environment:
MDA_API_BASE_URL: http://backend:8000/api/v1.0/
MDA_API_SECRET: <mda-secret>
MDA_API_TIMEOUT: 5
MAX_INCOMING_EMAIL_SIZE: 30000000
MYHOSTNAME: mx.<domain>
ports: ["25:25"]
depends_on: {backend: {condition: service_healthy}}
networks: [internal]
mta-out:
image: ghcr.io/suitenumerique/messages-mta-out:main
restart: unless-stopped
user: root
entrypoint: ["/bin/sh", "-c", "mkdir -p /var/spool/postfix/etc && chown -R postfix:postfix /var/spool/postfix /var/spool/postfix/public /var/spool/postfix/private 2>/dev/null; chown postfix:postdrop /var/spool/postfix/public 2>/dev/null; exec /usr/local/bin/entrypoint.sh"]
environment:
MYHOSTNAME: mx.<domain>
SMTP_USERNAME: messages
SMTP_PASSWORD: <mta-password>
SMTP_RELAY_HOST: smtp-relay.brevo.com:587
SMTP_RELAY_USERNAME: <brevo-username>
SMTP_RELAY_PASSWORD: <brevo-password>
depends_on: {backend: {condition: service_healthy}}
networks: [internal]
volumes:
messages_db:
messages_minio:
messages_opensearch:
kc_db:
networks:
proxy: {external: true}
internal:
messages_static: {external: true}