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
# 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) -----------------
POSTGRES_USER=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
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
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
`zitadel-init` (in the compose file of whichever project runs it first)
writes a service-account personal access token to `zitadel/.output/admin-sa.pat`
on first init. Every consuming project mounts that same folder read-only, via
Zitadel itself writes a service-account personal access token to
`zitadel/.output/admin-sa.pat` when the instance is created. Every consuming project mounts that same folder read-only, via
its own `ZITADEL_ADMIN_PAT_DIR` setting, to call the Zitadel management API
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):
```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
+25 -13
View File
@@ -5,43 +5,49 @@ name: infra-dev
# of joining infra-net. Adds pgAdmin.
#
# 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:
postgres:
ports:
- "${POSTGRES_HOST_PORT}:5432"
- "${DEV_BIND_ADDRESS:-127.0.0.1}:${POSTGRES_HOST_PORT}:5432"
zitadel:
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
# own (ZITADEL_LOGIN_BASE_URL in .env points the browser at it).
zitadel-login:
ports:
- "${ZITADEL_LOGIN_HOST_PORT}:3000"
- "${DEV_BIND_ADDRESS:-127.0.0.1}:${ZITADEL_LOGIN_HOST_PORT}:3000"
seaweedfs:
ports:
- "9333:9333" # master / admin UI
- "8888:8888" # filer
- "8333:8333" # S3 API
- "${DEV_BIND_ADDRESS:-127.0.0.1}:9333:9333" # master / admin UI
- "${DEV_BIND_ADDRESS:-127.0.0.1}:8888:8888" # filer
- "${DEV_BIND_ADDRESS:-127.0.0.1}:8333:8333" # S3 API
rabbitmq:
ports:
- "5672:5672" # AMQP
- "15672:15672" # management UI
- "${DEV_BIND_ADDRESS:-127.0.0.1}:5672:5672" # AMQP
- "${DEV_BIND_ADDRESS:-127.0.0.1}:15672:15672" # management UI
jaeger:
ports:
- "16686:16686" # UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "${DEV_BIND_ADDRESS:-127.0.0.1}:16686:16686" # UI
- "${DEV_BIND_ADDRESS:-127.0.0.1}:4317:4317" # OTLP gRPC
- "${DEV_BIND_ADDRESS:-127.0.0.1}:4318:4318" # OTLP HTTP
# Optional Postgres UI: docker compose ... --profile tools up -d
pgadmin:
profiles: [tools]
image: dpage/pgadmin4:latest
image: dpage/pgadmin4:9.18
container_name: infra-pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
@@ -51,9 +57,15 @@ services:
postgres:
condition: service_healthy
ports:
- "${PGADMIN_HOST_PORT}:80"
- "${DEV_BIND_ADDRESS:-127.0.0.1}:${PGADMIN_HOST_PORT}:80"
volumes:
- 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
volumes:
+27 -3
View File
@@ -7,13 +7,21 @@
# of joining a network. See README.md for why.
#
# 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
# 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
# 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:
infra-net:
name: infra-net
@@ -38,6 +46,7 @@ services:
timeout: 5s
retries: 10
networks: [infra-net]
logging: *default-logging
restart: unless-stopped
# --- Identity -----------------------------------------------------------
@@ -118,6 +127,7 @@ services:
retries: 12
start_period: 20s
networks: [infra-net]
logging: *default-logging
restart: unless-stopped
# Login V2 UI (Next.js), served under /ui/v2/login. It calls the API
@@ -143,6 +153,7 @@ services:
volumes:
- zitadel_bootstrap:/bootstrap:ro
networks: [infra-net]
logging: *default-logging
restart: unless-stopped
# --- Storage --------------------------------------------------------------
@@ -152,7 +163,7 @@ services:
# handed to consuming projects. No bucket is created here; each
# consuming project's own seaweedfs-init one-shot creates its own.
seaweedfs:
image: chrislusf/seaweedfs:latest
image: chrislusf/seaweedfs:4.47
container_name: infra-seaweedfs
entrypoint: ["/bin/sh", "-c"]
command:
@@ -184,6 +195,7 @@ services:
timeout: 5s
retries: 10
networks: [infra-net]
logging: *default-logging
restart: unless-stopped
# --- Messaging --------------------------------------------------------
@@ -207,16 +219,28 @@ services:
timeout: 5s
retries: 10
networks: [infra-net]
logging: *default-logging
restart: unless-stopped
# --- Tracing --------------------------------------------------------------
# OTLP collector + trace UI, shared by any project's OpenTelemetry exporter.
# Last Jaeger v1 release (v1 is deprecated; v2 is jaegertracing/jaeger).
jaeger:
image: jaegertracing/all-in-one:latest
image: jaegertracing/all-in-one:1.76.0
container_name: infra-jaeger
environment:
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]
logging: *default-logging
restart: unless-stopped
volumes:
+11 -1
View File
@@ -10,6 +10,14 @@
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:
infra-net:
name: infra-net
@@ -36,10 +44,11 @@ services:
- ./certbot/www:/var/www/certbot:ro
- ./certbot/conf:/etc/letsencrypt:ro
networks: [gitea, infra-net]
logging: *default-logging
restart: unless-stopped
certbot:
image: certbot/certbot
image: certbot/certbot:v5.8.0
container_name: edge-certbot
entrypoint:
- /bin/sh
@@ -48,4 +57,5 @@ services:
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
logging: *default-logging
restart: unless-stopped