Pin images, rotate logs, cap Jaeger and bind dev ports to loopback

- Pin seaweedfs (4.47), jaeger (1.76.0), certbot (v5.8.0) and pgadmin
  (9.18) instead of :latest.
- Rotate container logs (json-file, 3 x 10 MB) in every stack.
- Cap Jaeger's in-memory storage at 100k traces and add a healthcheck.
- Publish dev ports on 127.0.0.1 (DEV_BIND_ADDRESS) so the dev
  credentials aren't reachable from the LAN.
- Fix the prod command in compose.yml's header, the README's note on who
  writes the admin PAT, and the certbot command's line breaks.
This commit is contained in:
2026-09-24 15:05:45 +02:00
parent 22213a5592
commit 0aa4cb1410
5 changed files with 76 additions and 22 deletions
+5
View File
@@ -3,6 +3,11 @@
# #
# cp .env.example .env # cp .env.example .env
# Address the dev ports are published on. 127.0.0.1 keeps them off the LAN.
# Use 0.0.0.0 only if your hosts file maps host.docker.internal to the LAN IP
# (then the browser can't reach 127.0.0.1-only ports through that name).
DEV_BIND_ADDRESS=127.0.0.1
# --- PostgreSQL (shared instance, one database per project) ----------------- # --- PostgreSQL (shared instance, one database per project) -----------------
POSTGRES_USER=postgres POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres POSTGRES_PASSWORD=postgres
+8 -5
View File
@@ -38,7 +38,9 @@ docker compose -f compose.yml -f compose.dev.yml up -d
``` ```
That starts Postgres, Zitadel (and its login UI), SeaweedFS, RabbitMQ and Jaeger, with their That starts Postgres, Zitadel (and its login UI), SeaweedFS, RabbitMQ and Jaeger, with their
ports published to `localhost`. Add `--profile tools` to also start pgAdmin. ports published on `127.0.0.1` only (`DEV_BIND_ADDRESS`), so the dev
credentials aren't reachable from the LAN. Add `--profile tools` to also
start pgAdmin.
In production, the base `compose.yml` file **is** the production shape In production, the base `compose.yml` file **is** the production shape
already. There are no published ports, and everything is reachable only on already. There are no published ports, and everything is reachable only on
@@ -106,9 +108,8 @@ match this repo's `.env`". This file is the source of truth for them.
### The Zitadel admin PAT ### The Zitadel admin PAT
`zitadel-init` (in the compose file of whichever project runs it first) Zitadel itself writes a service-account personal access token to
writes a service-account personal access token to `zitadel/.output/admin-sa.pat` `zitadel/.output/admin-sa.pat` when the instance is created. Every consuming project mounts that same folder read-only, via
on first init. Every consuming project mounts that same folder read-only, via
its own `ZITADEL_ADMIN_PAT_DIR` setting, to call the Zitadel management API its own `ZITADEL_ADMIN_PAT_DIR` setting, to call the Zitadel management API
and create its own project/apps. and create its own project/apps.
@@ -180,7 +181,9 @@ docker compose -f edge/compose.yml up -d
for new hostnames (their DNS has to already point at the host): for new hostnames (their DNS has to already point at the host):
```bash ```bash
docker compose -f edge/compose.yml run --rm --entrypoint certbot certbot certonly --webroot -w /var/www/certbot -d host1.example.com -d host2.example.com --non-interactive --agree-tos -m you@example.com docker compose -f edge/compose.yml run --rm --entrypoint certbot certbot \
certonly --webroot -w /var/www/certbot -d host1.example.com -d host2.example.com \
--non-interactive --agree-tos -m you@example.com
``` ```
Then add the `server` blocks pointing at Then add the `server` blocks pointing at
+25 -13
View File
@@ -5,43 +5,49 @@ name: infra-dev
# of joining infra-net. Adds pgAdmin. # of joining infra-net. Adds pgAdmin.
# #
# docker compose -f compose.yml -f compose.dev.yml up -d # docker compose -f compose.yml -f compose.dev.yml up -d
#
# Ports are bound to DEV_BIND_ADDRESS (127.0.0.1 by default), so the dev
# credentials in .env are not reachable from the LAN. Other containers still
# reach them through host.docker.internal. The browser on the host does too
# as long as the hosts file maps host.docker.internal to 127.0.0.1; if yours
# maps it to the LAN IP, set DEV_BIND_ADDRESS=0.0.0.0 (see .env.example).
services: services:
postgres: postgres:
ports: ports:
- "${POSTGRES_HOST_PORT}:5432" - "${DEV_BIND_ADDRESS:-127.0.0.1}:${POSTGRES_HOST_PORT}:5432"
zitadel: zitadel:
ports: ports:
- "${ZITADEL_EXTERNAL_PORT}:8080" - "${DEV_BIND_ADDRESS:-127.0.0.1}:${ZITADEL_EXTERNAL_PORT}:8080"
# No edge in dev to put the login under Zitadel's own port, so it gets its # No edge in dev to put the login under Zitadel's own port, so it gets its
# own (ZITADEL_LOGIN_BASE_URL in .env points the browser at it). # own (ZITADEL_LOGIN_BASE_URL in .env points the browser at it).
zitadel-login: zitadel-login:
ports: ports:
- "${ZITADEL_LOGIN_HOST_PORT}:3000" - "${DEV_BIND_ADDRESS:-127.0.0.1}:${ZITADEL_LOGIN_HOST_PORT}:3000"
seaweedfs: seaweedfs:
ports: ports:
- "9333:9333" # master / admin UI - "${DEV_BIND_ADDRESS:-127.0.0.1}:9333:9333" # master / admin UI
- "8888:8888" # filer - "${DEV_BIND_ADDRESS:-127.0.0.1}:8888:8888" # filer
- "8333:8333" # S3 API - "${DEV_BIND_ADDRESS:-127.0.0.1}:8333:8333" # S3 API
rabbitmq: rabbitmq:
ports: ports:
- "5672:5672" # AMQP - "${DEV_BIND_ADDRESS:-127.0.0.1}:5672:5672" # AMQP
- "15672:15672" # management UI - "${DEV_BIND_ADDRESS:-127.0.0.1}:15672:15672" # management UI
jaeger: jaeger:
ports: ports:
- "16686:16686" # UI - "${DEV_BIND_ADDRESS:-127.0.0.1}:16686:16686" # UI
- "4317:4317" # OTLP gRPC - "${DEV_BIND_ADDRESS:-127.0.0.1}:4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP - "${DEV_BIND_ADDRESS:-127.0.0.1}:4318:4318" # OTLP HTTP
# Optional Postgres UI: docker compose ... --profile tools up -d # Optional Postgres UI: docker compose ... --profile tools up -d
pgadmin: pgadmin:
profiles: [tools] profiles: [tools]
image: dpage/pgadmin4:latest image: dpage/pgadmin4:9.18
container_name: infra-pgadmin container_name: infra-pgadmin
environment: environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
@@ -51,9 +57,15 @@ services:
postgres: postgres:
condition: service_healthy condition: service_healthy
ports: ports:
- "${PGADMIN_HOST_PORT}:80" - "${DEV_BIND_ADDRESS:-127.0.0.1}:${PGADMIN_HOST_PORT}:80"
volumes: volumes:
- pgadmin_data:/var/lib/pgadmin - pgadmin_data:/var/lib/pgadmin
# Same rotation as compose.yml (YAML anchors don't cross files).
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+27 -3
View File
@@ -7,13 +7,21 @@
# of joining a network. See README.md for why. # of joining a network. See README.md for why.
# #
# dev docker compose -f compose.yml -f compose.dev.yml up -d # dev docker compose -f compose.yml -f compose.dev.yml up -d
# prod docker compose --env-file .env.prod -f compose.yml -f compose.prod.yml up -d # prod docker compose --env-file .env.prod -f compose.yml up -d
# #
# A consuming project's own deploy stack does NOT get its own copy of any of # A consuming project's own deploy stack does NOT get its own copy of any of
# these services. It only needs this stack to already be up, and its own # these services. It only needs this stack to already be up, and its own
# one-shot bootstrap jobs (zitadel-init, seaweedfs-init) to create its own # one-shot bootstrap jobs (zitadel-init, seaweedfs-init) to create its own
# OIDC apps / bucket against the shared instances below. # OIDC apps / bucket against the shared instances below.
# Docker's default json-file driver never rotates, so logs would grow until
# the disk fills up.
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks: networks:
infra-net: infra-net:
name: infra-net name: infra-net
@@ -38,6 +46,7 @@ services:
timeout: 5s timeout: 5s
retries: 10 retries: 10
networks: [infra-net] networks: [infra-net]
logging: *default-logging
restart: unless-stopped restart: unless-stopped
# --- Identity ----------------------------------------------------------- # --- Identity -----------------------------------------------------------
@@ -118,6 +127,7 @@ services:
retries: 12 retries: 12
start_period: 20s start_period: 20s
networks: [infra-net] networks: [infra-net]
logging: *default-logging
restart: unless-stopped restart: unless-stopped
# Login V2 UI (Next.js), served under /ui/v2/login. It calls the API # Login V2 UI (Next.js), served under /ui/v2/login. It calls the API
@@ -143,6 +153,7 @@ services:
volumes: volumes:
- zitadel_bootstrap:/bootstrap:ro - zitadel_bootstrap:/bootstrap:ro
networks: [infra-net] networks: [infra-net]
logging: *default-logging
restart: unless-stopped restart: unless-stopped
# --- Storage -------------------------------------------------------------- # --- Storage --------------------------------------------------------------
@@ -152,7 +163,7 @@ services:
# handed to consuming projects. No bucket is created here; each # handed to consuming projects. No bucket is created here; each
# consuming project's own seaweedfs-init one-shot creates its own. # consuming project's own seaweedfs-init one-shot creates its own.
seaweedfs: seaweedfs:
image: chrislusf/seaweedfs:latest image: chrislusf/seaweedfs:4.47
container_name: infra-seaweedfs container_name: infra-seaweedfs
entrypoint: ["/bin/sh", "-c"] entrypoint: ["/bin/sh", "-c"]
command: command:
@@ -184,6 +195,7 @@ services:
timeout: 5s timeout: 5s
retries: 10 retries: 10
networks: [infra-net] networks: [infra-net]
logging: *default-logging
restart: unless-stopped restart: unless-stopped
# --- Messaging -------------------------------------------------------- # --- Messaging --------------------------------------------------------
@@ -207,16 +219,28 @@ services:
timeout: 5s timeout: 5s
retries: 10 retries: 10
networks: [infra-net] networks: [infra-net]
logging: *default-logging
restart: unless-stopped restart: unless-stopped
# --- Tracing -------------------------------------------------------------- # --- Tracing --------------------------------------------------------------
# OTLP collector + trace UI, shared by any project's OpenTelemetry exporter. # OTLP collector + trace UI, shared by any project's OpenTelemetry exporter.
# Last Jaeger v1 release (v1 is deprecated; v2 is jaegertracing/jaeger).
jaeger: jaeger:
image: jaegertracing/all-in-one:latest image: jaegertracing/all-in-one:1.76.0
container_name: infra-jaeger container_name: infra-jaeger
environment: environment:
COLLECTOR_OTLP_ENABLED: "true" COLLECTOR_OTLP_ENABLED: "true"
# Traces are kept in memory; without a cap they grow until the
# container runs out of RAM. The oldest are dropped first.
MEMORY_MAX_TRACES: "100000"
healthcheck:
# Admin port: answers 200 once the collector and query service are up.
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:14269/"]
interval: 10s
timeout: 5s
retries: 10
networks: [infra-net] networks: [infra-net]
logging: *default-logging
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+11 -1
View File
@@ -10,6 +10,14 @@
name: edge name: edge
# Docker's default json-file driver never rotates, so logs would grow until
# the disk fills up.
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks: networks:
infra-net: infra-net:
name: infra-net name: infra-net
@@ -36,10 +44,11 @@ services:
- ./certbot/www:/var/www/certbot:ro - ./certbot/www:/var/www/certbot:ro
- ./certbot/conf:/etc/letsencrypt:ro - ./certbot/conf:/etc/letsencrypt:ro
networks: [gitea, infra-net] networks: [gitea, infra-net]
logging: *default-logging
restart: unless-stopped restart: unless-stopped
certbot: certbot:
image: certbot/certbot image: certbot/certbot:v5.8.0
container_name: edge-certbot container_name: edge-certbot
entrypoint: entrypoint:
- /bin/sh - /bin/sh
@@ -48,4 +57,5 @@ services:
volumes: volumes:
- ./certbot/conf:/etc/letsencrypt - ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot - ./certbot/www:/var/www/certbot
logging: *default-logging
restart: unless-stopped restart: unless-stopped