Files
wmair 4e43a6f713 Add custom Docker registry and hardened image support
All image references in docker-compose.yml and docker-compose.local.yml
are replaced with ${IMAGE_VAR:-default} env var syntax, so compose files
work standalone without .env while deploy.sh writes resolved image paths.

deploy.sh gains two new prompts:
- Custom registry prefix (prepended to all image names)
- Hardened images from dhi.io for Redis/PostgreSQL/Caddy (takes priority
  over custom registry for those three)

Compared to PR #10: interactive prompts instead of hardcoded vars,
no sed-based compose file mutation, all 14 images covered (PR #10 missed
element-admin, element-call, lk-jwt-service, and all 3 bridges), and
standalone compose usage is preserved via :-default fallbacks.

SETUP.md and README.md document the feature including a note on
pull-through cache registries (Harbor/Artifactory/Nexus) that require
the full docker.io/ path prefix in image names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:55:18 +01:00

288 lines
8.5 KiB
YAML

# Production docker-compose file for Matrix stack
# This configuration:
# - Uses real domain names with Let's Encrypt SSL certificates
# - Makes Authelia optional (use --profile authelia to enable)
# - Makes Caddy optional (use --profile single-machine for all-in-one deployment)
# - Configures all services for production use
#
# Deployment Modes:
# 1. Multi-machine (default): docker compose -f docker-compose.production.yml up -d
# - Runs: Synapse, MAS, Element, PostgreSQL only
# - Caddy and Authelia run on separate machines
# 2. Single-machine with Authelia: docker compose -f docker-compose.production.yml --profile single-machine --profile authelia up -d
# - Runs everything on one machine with Caddy + Authelia
# 3. Single-machine without Authelia: docker compose -f docker-compose.production.yml --profile single-machine up -d
# - Runs everything on one machine with Caddy, no Authelia
services:
# PostgreSQL Database
postgres:
image: ${POSTGRES_IMAGE:-postgres:16-alpine}
container_name: matrix-postgres
restart: unless-stopped
environment:
POSTGRES_DB: synapse
POSTGRES_USER: synapse
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C
volumes:
- ./postgres/data:/var/lib/postgresql/data
- ./postgres/init:/docker-entrypoint-initdb.d
networks:
- matrix-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U synapse"]
interval: 10s
timeout: 5s
retries: 5
# Matrix Synapse Server
synapse:
image: ${SYNAPSE_IMAGE:-matrixdotorg/synapse:latest}
container_name: matrix-synapse
restart: unless-stopped
environment:
SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
volumes:
- ./synapse/data:/data
- ./bridges:/bridges:ro
- ./appservices:/appservices:ro
# Ports published to host for multi-machine deployment
ports:
- "8008:8008"
- "8448:8448" # Federation port
networks:
- matrix-network
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8008/health"]
interval: 30s
timeout: 10s
retries: 3
# Element Web Client
element:
image: ${ELEMENT_IMAGE:-vectorim/element-web:latest}
container_name: matrix-element
restart: unless-stopped
volumes:
- ./element/config/config.json:/app/config.json:ro
# Port published to host for multi-machine deployment
ports:
- "8090:80" # Map container port 80 to host port 8090
networks:
- matrix-network
depends_on:
- synapse
# Element Admin - Web UI for managing users/rooms via MAS
element-admin:
image: ${ELEMENT_ADMIN_IMAGE:-oci.element.io/element-admin:latest}
container_name: matrix-element-admin
restart: unless-stopped
environment:
SERVER_NAME: "${MATRIX_DOMAIN}"
OIDC_CLIENT_ID: "01ADMN00000000000000000000"
OIDC_ISSUER: "https://${AUTH_DOMAIN}/"
# Port published to host for multi-machine deployment
ports:
- "8091:8080" # element-admin listens on 8080 (changed from 80 in recent versions)
networks:
- matrix-network
depends_on:
- synapse
- mas
# Redis for Authelia session storage
redis:
profiles:
- authelia # Only started when Authelia profile is active
image: ${REDIS_IMAGE:-redis:7-alpine}
container_name: matrix-redis
restart: unless-stopped
networks:
- matrix-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Matrix Authentication Service (MAS)
mas:
image: ${MAS_IMAGE:-ghcr.io/element-hq/matrix-authentication-service:latest}
container_name: matrix-mas
restart: unless-stopped
environment:
MAS_CONFIG: /config/config.yaml
volumes:
- ./mas/config:/config:ro
- ./mas/data:/data
# Ports published to host for multi-machine deployment
ports:
- "8080:8080"
- "8081:8081"
networks:
- matrix-network
depends_on:
postgres:
condition: service_healthy
# Note: Redis/Authelia dependency removed - works with or without Authelia
# Healthcheck disabled: MAS uses distroless image without curl
# healthcheck:
# test: ["CMD", "curl", "-f", "http://localhost:8081/health"]
# interval: 30s
# timeout: 10s
# retries: 3
# mautrix-telegram Bridge
mautrix-telegram:
image: ${TELEGRAM_IMAGE:-dock.mau.dev/mautrix/telegram:latest}
container_name: matrix-bridge-telegram
restart: unless-stopped
volumes:
- ./bridges/telegram/config:/data
networks:
- matrix-network
depends_on:
synapse:
condition: service_healthy
# mautrix-whatsapp Bridge
mautrix-whatsapp:
image: ${WHATSAPP_IMAGE:-dock.mau.dev/mautrix/whatsapp:latest}
container_name: matrix-bridge-whatsapp
restart: unless-stopped
volumes:
- ./bridges/whatsapp/config:/data
networks:
- matrix-network
depends_on:
synapse:
condition: service_healthy
# mautrix-signal Bridge
mautrix-signal:
image: ${SIGNAL_IMAGE:-dock.mau.dev/mautrix/signal:latest}
container_name: matrix-bridge-signal
restart: unless-stopped
volumes:
- ./bridges/signal/config:/data
networks:
- matrix-network
depends_on:
synapse:
condition: service_healthy
# LiveKit SFU — media server for Element Call (Optional - use profile "element-call" to enable)
livekit:
profiles:
- element-call
image: ${LIVEKIT_IMAGE:-livekit/livekit-server:latest}
container_name: matrix-livekit
restart: unless-stopped
ports:
- "7880:7880"
- "7881:7881/tcp"
- "50100-50200:50100-50200/udp"
volumes:
- ./livekit/livekit.yaml:/livekit.yaml
command: --config /livekit.yaml
networks:
- matrix-network
# LiveKit JWT Service — issues LiveKit tokens to authenticated Matrix users
lk-jwt-service:
profiles:
- element-call
image: ${LK_JWT_IMAGE:-ghcr.io/element-hq/lk-jwt-service:latest}
container_name: matrix-lk-jwt
restart: unless-stopped
ports:
- "8082:8080"
environment:
- LIVEKIT_URL=wss://${RTC_DOMAIN}/livekit/sfu
- LIVEKIT_KEY=livekit-key
- LIVEKIT_SECRET=${LIVEKIT_SECRET}
- LIVEKIT_FULL_ACCESS_HOMESERVERS=${MATRIX_DOMAIN}
networks:
- matrix-network
depends_on:
- livekit
# Element Call frontend — self-hosted web app for video/voice calls
# Reads livekit_service_url from .well-known/matrix/client (rtc_foci) automatically.
# No extra configuration needed: homeserver details are passed as URL params by Element Web.
element-call:
profiles:
- element-call
image: ${ELEMENT_CALL_IMAGE:-ghcr.io/element-hq/element-call:latest}
container_name: matrix-element-call
restart: unless-stopped
ports:
- "8083:8080"
networks:
- matrix-network
# Authelia SSO (Optional - use profile "authelia" to enable)
authelia:
profiles:
- authelia # Only started when Authelia profile is active
image: ${AUTHELIA_IMAGE:-authelia/authelia:latest}
container_name: matrix-authelia
restart: unless-stopped
environment:
TZ: ${TZ:-UTC}
AUTHELIA_SESSION_SECRET: ${AUTHELIA_SESSION_SECRET}
AUTHELIA_STORAGE_ENCRYPTION_KEY: ${AUTHELIA_STORAGE_ENCRYPTION_KEY}
AUTHELIA_JWT_SECRET: ${AUTHELIA_JWT_SECRET}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./authelia/config:/config
# Accessed via Caddy
expose:
- "9091"
networks:
- matrix-network
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# Caddy Reverse Proxy (HTTPS termination with Let's Encrypt)
# For multi-machine deployments: Don't use this profile (Caddy runs separately)
# For single-machine deployments: Use --profile single-machine
caddy:
profiles:
- single-machine # Only started for single-machine deployments
image: ${CADDY_IMAGE:-caddy:2-alpine}
container_name: matrix-caddy
restart: unless-stopped
ports:
- "443:443"
- "80:80"
- "2019:2019" # Admin API
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy/data:/data
- ./caddy/config:/config
networks:
- matrix-network
depends_on:
- synapse
- element
- mas
# Note: Authelia dependency removed - works with or without Authelia
networks:
matrix-network:
driver: bridge
volumes:
postgres-data:
synapse-data:
mas-data: