compose.integration.yaml
Rama main · réplica de lectura del repositorio original.
name: rootguard
services:
unbound:
build:
context: ./rootguard-unbound
image: rootguard-unbound:dev
container_name: rootguard-unbound
restart: unless-stopped
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
volumes:
- unbound-config:/etc/unbound/unbound.d
- unbound-state:/var/lib/unbound
networks:
dns:
ipv4_address: 172.29.53.2
forwarding_test:
ipv4_address: 172.30.54.3
forwarder:
image: rootguard-unbound:dev
container_name: rootguard-forwarder
restart: unless-stopped
entrypoint: ["unbound", "-d", "-c", "/etc/unbound/forwarder.conf"]
user: "0:0"
read_only: true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
volumes:
- ./testdata/forwarder/unbound.conf:/etc/unbound/forwarder.conf:ro
networks:
forwarding_test:
ipv4_address: 172.30.54.2
healthcheck:
test: ["CMD", "dig", "+short", "+time=1", "+tries=1", "@127.0.0.1", "-p", "53", "example.", "A"]
interval: 2s
timeout: 2s
retries: 15
adguard:
image: "${ROOTGUARD_ADGUARD_IMAGE:-adguard/adguardhome:v0.107.80}"
container_name: rootguard-adguard
restart: unless-stopped
depends_on:
updater:
condition: service_healthy
unbound:
condition: service_healthy
ports:
- "${ROOTGUARD_DNS_BIND:-0.0.0.0}:${ROOTGUARD_DNS_PORT:-53}:53/tcp"
- "${ROOTGUARD_DNS_BIND:-0.0.0.0}:${ROOTGUARD_DNS_PORT:-53}:53/udp"
volumes:
- adguard-work:/opt/adguardhome/work
- adguard-config:/opt/adguardhome/conf
networks:
- dns
core:
build:
context: ./rootguard-core
image: rootguard-core:dev
container_name: rootguard-core
restart: unless-stopped
depends_on:
unbound:
condition: service_healthy
forwarder:
condition: service_healthy
adguard:
condition: service_started
environment:
PORT: "8081"
ROOTGUARD_API_TOKEN: "${ROOTGUARD_API_TOKEN:?set ROOTGUARD_API_TOKEN in .env}"
# This file's own unbound/adguard are pre-provisioned compose
# services (bootstrapped directly, never through
# installer.Manager.deploy()), so the attestation gate's code path
# isn't actually reached here today - added for consistency with
# compose.yaml/compose.release.yaml anyway, so a future test that
# does exercise POST /api/installation/deploy against this file
# doesn't hit the same live-testing surprise compose.yaml did
# (2026-08-29, see docs/security-audit-log.md).
ROOTGUARD_SKIP_ATTESTATION: "${ROOTGUARD_SKIP_ATTESTATION:-false}"
ROOTGUARD_INSTALLATION_DIR: /var/lib/rootguard/installation
ROOTGUARD_CONTROL_PLANE_UPDATER_URL: http://updater:8082
ROOTGUARD_CONTROL_PLANE_UPDATER_TOKEN: "${ROOTGUARD_API_TOKEN:?set ROOTGUARD_API_TOKEN in .env}"
UNBOUND_CONFIG_DIR: /var/lib/rootguard/unbound
UNBOUND_CONTAINER_CONFIG_DIR: /etc/unbound/unbound.d
UNBOUND_CONTAINER_NAME: rootguard-unbound
# Empty by default - Diagnose keeps querying the real internet
# (example.com/dnssec-failed.org), correct for an actual
# deployment. ci.yml sets both to a local, throwaway DNSSEC test
# zone (scripts/ci/dnssec-test-zone) so its own integration test of
# this exact endpoint doesn't depend on the runner's own network -
# found in review, confirmed live: main's own scheduled run of that
# check failed on a transient DNS hiccup unrelated to any code
# change.
ROOTGUARD_UNBOUND_DIAGNOSTIC_RESOLUTION_DOMAIN: "${ROOTGUARD_UNBOUND_DIAGNOSTIC_RESOLUTION_DOMAIN:-}"
ROOTGUARD_UNBOUND_DIAGNOSTIC_DNSSEC_DOMAIN: "${ROOTGUARD_UNBOUND_DIAGNOSTIC_DNSSEC_DOMAIN:-}"
ADGUARD_INSTALLER_URL: http://rootguard-adguard:3000
ADGUARD_API_URL: http://rootguard-adguard:80
ADGUARD_DATA_DIR: /var/lib/rootguard/adguard
ADGUARD_UPSTREAM: 172.29.53.2:5335
ADGUARD_DNS_ADDRESS: rootguard-adguard:53
# Propagates the real host checkout directory into a
# `docker compose -f /opt/rootguard/compose.yaml ...`
# re-invocation from inside this container - see the matching
# comment on the volumes line below and on compose.yaml's own core
# service (the identical fix, applied there first).
PWD: "${PWD}"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- rootguard-data:/var/lib/rootguard
- unbound-config:/var/lib/rootguard/unbound
# Aliased to the fixed "compose.yaml" name (not this file's own
# "compose.integration.yaml") because
# internal/updater/manager.go's composeUp hardcodes that filename -
# same reason compose.yaml/compose.release.yaml's own core service
# alias-mounts theirs. ${PWD}, not a relative "./...", for the same
# reason those two files use it: a relative source re-resolves
# against the wrong directory the moment this container re-invokes
# `docker compose -f <this mounted path> ...` against itself.
# Nothing in this file currently drives Core's own
# updater-of-updater self-update path (no ROOTGUARD_SELF_UPDATE_DIR/
# ROOTGUARD_COMPOSE_FILE wired here, no attestation-proxy service),
# so this mount is currently unused - added so the same silent
# "missing file" surprise found live in compose.yaml (2026-08-29,
# see docs/security-audit-log.md) can't recur here the moment that
# gets wired up.
- ${PWD}/compose.integration.yaml:/opt/rootguard/compose.yaml:ro
networks:
- control
- dns
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8081/api/health"]
interval: 15s
timeout: 5s
retries: 5
webapp:
build:
context: ./rootguard-webapp
image: rootguard-webapp:dev
container_name: rootguard-webapp
restart: unless-stopped
depends_on:
core:
condition: service_healthy
environment:
PORT: "8080"
ROOTGUARD_CORE_URL: http://core:8081
ROOTGUARD_API_TOKEN: "${ROOTGUARD_API_TOKEN:?set ROOTGUARD_API_TOKEN in .env}"
ROOTGUARD_ADMIN_USER: "${ROOTGUARD_ADMIN_USER:-admin}"
ROOTGUARD_ADMIN_PASSWORD: "${ROOTGUARD_ADMIN_PASSWORD:?set ROOTGUARD_ADMIN_PASSWORD in .env}"
ROOTGUARD_RECOVERY_TOKEN: "${ROOTGUARD_RECOVERY_TOKEN:?set ROOTGUARD_RECOVERY_TOKEN in .env}"
ROOTGUARD_SESSION_FILE: /var/lib/rootguard-sessions/sessions.json
volumes:
- rootguard-sessions:/var/lib/rootguard-sessions
ports:
- "${ROOTGUARD_WEB_PORT:-8080}:8080/tcp"
networks:
- control
- edge
# Found in review: missing here despite compose.yaml (dev) having it.
# The distroless runtime image has neither wget nor a shell - unlike
# core/updater's docker:29-cli base, there's nothing here for a
# "CMD wget ..." style check to exec. rootguard-webapp itself
# implements -healthcheck for exactly this (see cmd/rootguard-webapp).
healthcheck:
test: ["CMD", "/app/rootguard-webapp", "-healthcheck"]
interval: 15s
timeout: 5s
retries: 5
updater:
build:
context: ./rootguard-updater
image: rootguard-updater:dev
container_name: rootguard-updater
restart: unless-stopped
environment:
ROOTGUARD_UPDATER_TOKEN: "${ROOTGUARD_API_TOKEN:?set ROOTGUARD_API_TOKEN in .env}"
ROOTGUARD_UPDATER_DATA_DIR: /var/lib/rootguard/control-plane-updater
ROOTGUARD_COMPOSE_FILE: /opt/rootguard/compose.integration.yaml
ROOTGUARD_COMPOSE_PROJECT: rootguard
ROOTGUARD_CORE_UPDATE_IMAGE: rootguard-core:dev
ROOTGUARD_WEBAPP_UPDATE_IMAGE: rootguard-webapp:dev
ROOTGUARD_UPDATER_SKIP_PULL: "true"
ROOTGUARD_API_TOKEN: "${ROOTGUARD_API_TOKEN:?set ROOTGUARD_API_TOKEN in .env}"
ROOTGUARD_ADMIN_USER: "${ROOTGUARD_ADMIN_USER:-admin}"
ROOTGUARD_ADMIN_PASSWORD: "${ROOTGUARD_ADMIN_PASSWORD:?set ROOTGUARD_ADMIN_PASSWORD in .env}"
ROOTGUARD_RECOVERY_TOKEN: "${ROOTGUARD_RECOVERY_TOKEN:?set ROOTGUARD_RECOVERY_TOKEN in .env}"
ROOTGUARD_WEB_PORT: "${ROOTGUARD_WEB_PORT:-8080}"
ROOTGUARD_SESSION_DIR: /var/lib/rootguard-sessions
# See the matching PWD/${PWD} comments on the core service above
# and on compose.yaml/compose.release.yaml's identical updater
# service - required for the volumes line below to resolve
# correctly when this container re-invokes `docker compose -f
# /opt/rootguard/compose.integration.yaml ...` against itself.
PWD: "${PWD}"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- rootguard-data:/var/lib/rootguard
- rootguard-sessions:/var/lib/rootguard-sessions
# ${PWD}, not a relative "./compose.integration.yaml" - found in
# review: this file was missed when compose.yaml/compose.release.yaml
# got the identical fix (2026-08-22, docs/security-audit-log.md). A
# relative bind source resolves against the directory of whatever
# -f file is being read, not this file's own location - breaks the
# moment this container re-invokes `docker compose -f
# /opt/rootguard/compose.integration.yaml ...` against itself,
# silently mounting an auto-vivified empty directory instead of the
# real file.
- ${PWD}/compose.integration.yaml:/opt/rootguard/compose.integration.yaml:ro
networks:
- control
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8082/health"]
interval: 5s
timeout: 3s
retries: 10
networks:
edge:
control:
internal: true
dns:
ipam:
config:
- subnet: 172.29.53.0/24
forwarding_test:
internal: true
ipam:
config:
- subnet: 172.30.54.0/24
volumes:
adguard-work:
adguard-config:
unbound-config:
unbound-state:
rootguard-data:
rootguard-sessions: