From 4e43a6f7130d5b87a058939a46d76d9eef7dc342 Mon Sep 17 00:00:00 2001 From: wmair Date: Mon, 2 Mar 2026 09:55:18 +0100 Subject: [PATCH] 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 --- README.md | 6 ++ SETUP.md | 54 +++++++++++++ compose-variants/docker-compose.local.yml | 24 +++--- deploy.sh | 92 +++++++++++++++++++++++ docker-compose.yml | 28 +++---- 5 files changed, 178 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index e1edc74..a8477d9 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,12 @@ TELEGRAM_API_HASH=your_hash Bridges use double puppet support (messages appear from your actual Matrix user, not a bridge bot) and have encryption disabled for compatibility with MAS. See [BRIDGE_SETUP_GUIDE.md](BRIDGE_SETUP_GUIDE.md) for details. +## Air-gapped / Custom Registry + +`deploy.sh` optionally prefixes all image references with a custom registry URL (for internal mirrors or air-gapped environments) and optionally switches Redis, PostgreSQL, and Caddy to hardened variants from [dhi.io](https://dhi.io). Both settings are written to `.env` and picked up automatically by Docker Compose. + +See [SETUP.md — Custom Docker Registry](SETUP.md#custom-docker-registry) for details, including a note on pull-through cache registries (Harbor, Artifactory, Nexus) that require the full registry path in image names. + ## Requirements - Docker and Docker Compose v2 diff --git a/SETUP.md b/SETUP.md index 1f182dc..a1542fe 100644 --- a/SETUP.md +++ b/SETUP.md @@ -23,6 +23,22 @@ Key variables: | `POSTGRES_PASSWORD` | Shared database password (same across all services) | | `MAS_SECRET_KEY` | 64-char hex encryption key for MAS | | `LIVEKIT_SECRET` | LiveKit API secret (only if Element Call enabled) | +| `POSTGRES_IMAGE` | PostgreSQL image (default: `postgres:16-alpine`) | +| `SYNAPSE_IMAGE` | Synapse image (default: `matrixdotorg/synapse:latest`) | +| `ELEMENT_IMAGE` | Element Web image (default: `vectorim/element-web:latest`) | +| `ELEMENT_ADMIN_IMAGE` | Element Admin image (default: `oci.element.io/element-admin:latest`) | +| `REDIS_IMAGE` | Redis image (default: `redis:7-alpine`) | +| `MAS_IMAGE` | MAS image (default: `ghcr.io/element-hq/matrix-authentication-service:latest`) | +| `TELEGRAM_IMAGE` | mautrix-telegram image (default: `dock.mau.dev/mautrix/telegram:latest`) | +| `WHATSAPP_IMAGE` | mautrix-whatsapp image (default: `dock.mau.dev/mautrix/whatsapp:latest`) | +| `SIGNAL_IMAGE` | mautrix-signal image (default: `dock.mau.dev/mautrix/signal:latest`) | +| `LIVEKIT_IMAGE` | LiveKit image (default: `livekit/livekit-server:latest`) | +| `LK_JWT_IMAGE` | lk-jwt-service image (default: `ghcr.io/element-hq/lk-jwt-service:latest`) | +| `ELEMENT_CALL_IMAGE` | Element Call image (default: `ghcr.io/element-hq/element-call:latest`) | +| `AUTHELIA_IMAGE` | Authelia image (default: `authelia/authelia:latest`) | +| `CADDY_IMAGE` | Caddy image (default: `caddy:2-alpine`) | + +All image variables are optional. If absent, each compose service falls back to the default tag via `${VAR:-default}` syntax, so `docker compose` works without a `.env` file. ### `synapse/data/homeserver.yaml` @@ -206,6 +222,44 @@ The key name `livekit-key` must match the `LIVEKIT_KEY` value in `docker-compose --- +## Custom Docker Registry + +`deploy.sh` prompts for two optional image settings: + +**Custom registry prefix** — prepends a registry URL to every image. Useful for air-gapped environments or internal mirrors: + +``` +Custom Docker registry prefix: myregistry.example.com +``` + +Results in `.env` entries like: +``` +SYNAPSE_IMAGE=myregistry.example.com/matrixdotorg/synapse:latest +REDIS_IMAGE=myregistry.example.com/redis:7-alpine +``` + +**Hardened images** — uses [dhi.io](https://dhi.io) hardened variants for Redis, PostgreSQL, and Caddy. Takes priority over the custom registry prefix for those three images: + +``` +REDIS_IMAGE=dhi.io/redis:7 +POSTGRES_IMAGE=dhi.io/postgres:16 +CADDY_IMAGE=dhi.io/caddy:2 +``` + +### Pull-through cache registries (Harbor, Artifactory, Nexus) + +Many enterprise registries act as pull-through caches that mirror images under the **full original registry path**. For example, Harbor's proxy cache serves Docker Hub images as: + +``` +myregistry.example.com/docker.io/library/redis:7-alpine +myregistry.example.com/docker.io/matrixdotorg/synapse:latest +myregistry.example.com/ghcr.io/element-hq/matrix-authentication-service:latest +``` + +The custom registry prefix in `deploy.sh` does **not** add the `docker.io/` or other intermediate path components — it produces `myregistry.example.com/redis:7-alpine`. If your mirror requires the full path structure, set the image variables manually in `.env` after running `deploy.sh`, or configure your registry to serve images without the intermediate path (most registries support this as an alias/rewrite). + +--- + ## Docker Compose Profiles The `docker-compose.yml` uses Docker Compose profiles to make services optional: diff --git a/compose-variants/docker-compose.local.yml b/compose-variants/docker-compose.local.yml index e8125dc..81f2c3b 100644 --- a/compose-variants/docker-compose.local.yml +++ b/compose-variants/docker-compose.local.yml @@ -1,7 +1,7 @@ services: # PostgreSQL Database postgres: - image: postgres:16-alpine + image: ${POSTGRES_IMAGE:-postgres:16-alpine} container_name: matrix-postgres restart: unless-stopped environment: @@ -22,7 +22,7 @@ services: # Matrix Synapse Server synapse: - image: matrixdotorg/synapse:latest + image: ${SYNAPSE_IMAGE:-matrixdotorg/synapse:latest} container_name: matrix-synapse restart: unless-stopped environment: @@ -52,7 +52,7 @@ services: # Element Web Client element: - image: vectorim/element-web:latest + image: ${ELEMENT_IMAGE:-vectorim/element-web:latest} container_name: matrix-element restart: unless-stopped volumes: @@ -69,7 +69,7 @@ services: livekit: profiles: - element-call - image: livekit/livekit-server:latest + image: ${LIVEKIT_IMAGE:-livekit/livekit-server:latest} container_name: matrix-livekit restart: unless-stopped ports: @@ -87,7 +87,7 @@ services: lk-jwt-service: profiles: - element-call - image: ghcr.io/element-hq/lk-jwt-service:latest + image: ${LK_JWT_IMAGE:-ghcr.io/element-hq/lk-jwt-service:latest} container_name: matrix-lk-jwt restart: unless-stopped expose: @@ -106,7 +106,7 @@ services: redis: profiles: - authelia # Only started when Authelia profile is active - image: redis:7-alpine + image: ${REDIS_IMAGE:-redis:7-alpine} container_name: matrix-redis restart: unless-stopped networks: @@ -121,7 +121,7 @@ services: authelia: profiles: - authelia # Only started when Authelia profile is active - image: authelia/authelia:latest + image: ${AUTHELIA_IMAGE:-authelia/authelia:latest} container_name: matrix-authelia restart: unless-stopped environment: @@ -145,7 +145,7 @@ services: # Matrix Authentication Service (MAS) mas: - image: ghcr.io/element-hq/matrix-authentication-service:latest + image: ${MAS_IMAGE:-ghcr.io/element-hq/matrix-authentication-service:latest} container_name: matrix-mas restart: unless-stopped environment: @@ -176,7 +176,7 @@ services: # Caddy Reverse Proxy (HTTPS termination) caddy: - image: caddy:2-alpine + image: ${CADDY_IMAGE:-caddy:2-alpine} container_name: matrix-caddy restart: unless-stopped ports: @@ -197,7 +197,7 @@ services: # mautrix-telegram Bridge mautrix-telegram: - image: dock.mau.dev/mautrix/telegram:latest + image: ${TELEGRAM_IMAGE:-dock.mau.dev/mautrix/telegram:latest} container_name: matrix-bridge-telegram restart: unless-stopped volumes: @@ -210,7 +210,7 @@ services: # mautrix-whatsapp Bridge mautrix-whatsapp: - image: dock.mau.dev/mautrix/whatsapp:latest + image: ${WHATSAPP_IMAGE:-dock.mau.dev/mautrix/whatsapp:latest} container_name: matrix-bridge-whatsapp restart: unless-stopped volumes: @@ -223,7 +223,7 @@ services: # mautrix-signal Bridge mautrix-signal: - image: dock.mau.dev/mautrix/signal:latest + image: ${SIGNAL_IMAGE:-dock.mau.dev/mautrix/signal:latest} container_name: matrix-bridge-signal restart: unless-stopped volumes: diff --git a/deploy.sh b/deploy.sh index d63ccb3..0bc0af6 100755 --- a/deploy.sh +++ b/deploy.sh @@ -180,6 +180,65 @@ else fi echo "" +# ============================================================================ +# DOCKER REGISTRY AND HARDENED IMAGES +# ============================================================================ +echo -e "${CYAN}Docker Image Configuration:${NC}" +echo "" +read -p "Custom Docker registry prefix (leave blank for default): " DOCKER_REGISTRY_INPUT +DOCKER_REGISTRY="${DOCKER_REGISTRY_INPUT%/}" # strip trailing slash +[ -n "$DOCKER_REGISTRY" ] && DOCKER_REGISTRY="${DOCKER_REGISTRY}/" + +if [ -n "$DOCKER_REGISTRY" ]; then + echo -e "${GREEN}✓${NC} Custom registry: ${DOCKER_REGISTRY}" +else + echo -e "${GREEN}✓${NC} Using default registries" +fi + +USE_HARDENED_IMAGES=false +read -p "Use hardened images from dhi.io for Redis/PostgreSQL/Caddy? [y/N]: " yn +[[ "$yn" =~ ^[Yy] ]] && USE_HARDENED_IMAGES=true +if [ "$USE_HARDENED_IMAGES" = true ]; then + echo -e "${GREEN}✓${NC} Hardened images (dhi.io) enabled for Redis/PostgreSQL/Caddy" +else + echo -e "${GREEN}✓${NC} Using standard images" +fi +echo "" + +# Build image reference helper +build_image() { + local image="$1" + if [ -n "$DOCKER_REGISTRY" ]; then + echo "${DOCKER_REGISTRY}${image}" + else + echo "${image}" + fi +} + +# Standard images (respect custom registry) +POSTGRES_IMAGE=$(build_image "postgres:16-alpine") +SYNAPSE_IMAGE=$(build_image "matrixdotorg/synapse:latest") +ELEMENT_IMAGE=$(build_image "vectorim/element-web:latest") +ELEMENT_ADMIN_IMAGE=$(build_image "oci.element.io/element-admin:latest") +MAS_IMAGE=$(build_image "ghcr.io/element-hq/matrix-authentication-service:latest") +TELEGRAM_IMAGE=$(build_image "dock.mau.dev/mautrix/telegram:latest") +WHATSAPP_IMAGE=$(build_image "dock.mau.dev/mautrix/whatsapp:latest") +SIGNAL_IMAGE=$(build_image "dock.mau.dev/mautrix/signal:latest") +LIVEKIT_IMAGE=$(build_image "livekit/livekit-server:latest") +LK_JWT_IMAGE=$(build_image "ghcr.io/element-hq/lk-jwt-service:latest") +ELEMENT_CALL_IMAGE=$(build_image "ghcr.io/element-hq/element-call:latest") +AUTHELIA_IMAGE=$(build_image "authelia/authelia:latest") + +# Hardened images take priority for redis/postgres/caddy +if [ "$USE_HARDENED_IMAGES" = true ]; then + REDIS_IMAGE="dhi.io/redis:7" + POSTGRES_IMAGE="dhi.io/postgres:16" + CADDY_IMAGE="dhi.io/caddy:2" +else + REDIS_IMAGE=$(build_image "redis:7-alpine") + CADDY_IMAGE=$(build_image "caddy:2-alpine") +fi + # Function to generate secure random string (32 bytes base64) generate_secret() { openssl rand -base64 32 | tr -d "=+/" | cut -c1-32 @@ -403,6 +462,22 @@ MAS_SECRET_KEY=${MAS_SECRET_KEY} # Timezone TZ=${TZ:-Europe/Berlin} + +# Docker images +POSTGRES_IMAGE=${POSTGRES_IMAGE} +SYNAPSE_IMAGE=${SYNAPSE_IMAGE} +ELEMENT_IMAGE=${ELEMENT_IMAGE} +ELEMENT_ADMIN_IMAGE=${ELEMENT_ADMIN_IMAGE} +REDIS_IMAGE=${REDIS_IMAGE} +MAS_IMAGE=${MAS_IMAGE} +TELEGRAM_IMAGE=${TELEGRAM_IMAGE} +WHATSAPP_IMAGE=${WHATSAPP_IMAGE} +SIGNAL_IMAGE=${SIGNAL_IMAGE} +LIVEKIT_IMAGE=${LIVEKIT_IMAGE} +LK_JWT_IMAGE=${LK_JWT_IMAGE} +ELEMENT_CALL_IMAGE=${ELEMENT_CALL_IMAGE} +AUTHELIA_IMAGE=${AUTHELIA_IMAGE} +CADDY_IMAGE=${CADDY_IMAGE} EOF # Add production-specific variables @@ -1247,6 +1322,23 @@ chmod 755 authelia/config mas/config element/config 2>/dev/null || true print_status "Permissions fixed" echo "" +# Image summary +echo -e "${CYAN}Docker images configured:${NC}" +echo -e " Synapse: $SYNAPSE_IMAGE" +echo -e " Postgres: $POSTGRES_IMAGE" +echo -e " Redis: $REDIS_IMAGE" +echo -e " MAS: $MAS_IMAGE" +echo -e " Element: $ELEMENT_IMAGE" +echo -e " Element Admin: $ELEMENT_ADMIN_IMAGE" +echo -e " Authelia: $AUTHELIA_IMAGE" +echo -e " Caddy: $CADDY_IMAGE" +if [[ "$USE_ELEMENT_CALL" == true ]]; then + echo -e " LiveKit: $LIVEKIT_IMAGE" + echo -e " LK JWT: $LK_JWT_IMAGE" + echo -e " Element Call: $ELEMENT_CALL_IMAGE" +fi +echo "" + # Step 14: Start the stack echo -e "${BLUE}[14/14] Starting the Matrix stack...${NC}" print_info "Using compose file: ${COMPOSE_FILE}" diff --git a/docker-compose.yml b/docker-compose.yml index a7075cf..a52d399 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: # PostgreSQL Database postgres: - image: postgres:16-alpine + image: ${POSTGRES_IMAGE:-postgres:16-alpine} container_name: matrix-postgres restart: unless-stopped environment: @@ -38,7 +38,7 @@ services: # Matrix Synapse Server synapse: - image: matrixdotorg/synapse:latest + image: ${SYNAPSE_IMAGE:-matrixdotorg/synapse:latest} container_name: matrix-synapse restart: unless-stopped environment: @@ -64,7 +64,7 @@ services: # Element Web Client element: - image: vectorim/element-web:latest + image: ${ELEMENT_IMAGE:-vectorim/element-web:latest} container_name: matrix-element restart: unless-stopped volumes: @@ -79,7 +79,7 @@ services: # Element Admin - Web UI for managing users/rooms via MAS element-admin: - image: oci.element.io/element-admin:latest + image: ${ELEMENT_ADMIN_IMAGE:-oci.element.io/element-admin:latest} container_name: matrix-element-admin restart: unless-stopped environment: @@ -99,7 +99,7 @@ services: redis: profiles: - authelia # Only started when Authelia profile is active - image: redis:7-alpine + image: ${REDIS_IMAGE:-redis:7-alpine} container_name: matrix-redis restart: unless-stopped networks: @@ -112,7 +112,7 @@ services: # Matrix Authentication Service (MAS) mas: - image: ghcr.io/element-hq/matrix-authentication-service:latest + image: ${MAS_IMAGE:-ghcr.io/element-hq/matrix-authentication-service:latest} container_name: matrix-mas restart: unless-stopped environment: @@ -139,7 +139,7 @@ services: # mautrix-telegram Bridge mautrix-telegram: - image: dock.mau.dev/mautrix/telegram:latest + image: ${TELEGRAM_IMAGE:-dock.mau.dev/mautrix/telegram:latest} container_name: matrix-bridge-telegram restart: unless-stopped volumes: @@ -152,7 +152,7 @@ services: # mautrix-whatsapp Bridge mautrix-whatsapp: - image: dock.mau.dev/mautrix/whatsapp:latest + image: ${WHATSAPP_IMAGE:-dock.mau.dev/mautrix/whatsapp:latest} container_name: matrix-bridge-whatsapp restart: unless-stopped volumes: @@ -165,7 +165,7 @@ services: # mautrix-signal Bridge mautrix-signal: - image: dock.mau.dev/mautrix/signal:latest + image: ${SIGNAL_IMAGE:-dock.mau.dev/mautrix/signal:latest} container_name: matrix-bridge-signal restart: unless-stopped volumes: @@ -180,7 +180,7 @@ services: livekit: profiles: - element-call - image: livekit/livekit-server:latest + image: ${LIVEKIT_IMAGE:-livekit/livekit-server:latest} container_name: matrix-livekit restart: unless-stopped ports: @@ -197,7 +197,7 @@ services: lk-jwt-service: profiles: - element-call - image: ghcr.io/element-hq/lk-jwt-service:latest + image: ${LK_JWT_IMAGE:-ghcr.io/element-hq/lk-jwt-service:latest} container_name: matrix-lk-jwt restart: unless-stopped ports: @@ -218,7 +218,7 @@ services: element-call: profiles: - element-call - image: ghcr.io/element-hq/element-call:latest + image: ${ELEMENT_CALL_IMAGE:-ghcr.io/element-hq/element-call:latest} container_name: matrix-element-call restart: unless-stopped ports: @@ -230,7 +230,7 @@ services: authelia: profiles: - authelia # Only started when Authelia profile is active - image: authelia/authelia:latest + image: ${AUTHELIA_IMAGE:-authelia/authelia:latest} container_name: matrix-authelia restart: unless-stopped environment: @@ -258,7 +258,7 @@ services: caddy: profiles: - single-machine # Only started for single-machine deployments - image: caddy:2-alpine + image: ${CADDY_IMAGE:-caddy:2-alpine} container_name: matrix-caddy restart: unless-stopped ports: