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>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}"
|
||||
|
||||
+14
-14
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user