diff --git a/.env.example b/.env.example index 88ab36e..61c2885 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/README.md b/README.md index 7d132da..c73a447 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/compose.dev.yml b/compose.dev.yml index b235237..f62ab4f 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -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: diff --git a/compose.yml b/compose.yml index d7964e6..eeaf141 100644 --- a/compose.yml +++ b/compose.yml @@ -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: diff --git a/edge/compose.yml b/edge/compose.yml index 9199f3d..4e882f1 100644 --- a/edge/compose.yml +++ b/edge/compose.yml @@ -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