Ctrl K
ComunidadGuías…

compose.release.yaml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
name: rootguard

# RootGuard 1.0.0 public evaluation stack.
# Every release image is pinned to its verified multi-architecture manifest.
# Copy .env.release.example to .env and replace all placeholder secrets.

services:
  # The one, narrow, auditable bridge letting cosign's own attestation
  # checks (see core's/updater's ROOTGUARD_ATTESTATION_PROXY_URL below)
  # reach GHCR/Sigstore from the fully internet-isolated `control`
  # network - found live, cutting 1.0.0-rc.2: Core/the Updater never had
  # any route to the internet at all, so `RequireAttestation`'s cosign
  # calls could never succeed once wired into the real deploy/update
  # path. See rootguard-attestation-proxy/README.md for the full
  # rationale and its hardcoded, 4-host allowlist (the 4th host,
  # api.github.com, was added for Core's GitHub Releases self-update
  # discovery - found in review that it ran on this same isolated
  # network but was never actually routed through this proxy).
  attestation-proxy:
    # Bootstrap pin: this component's very first published image, pushed
    # by hand (real, public, multi-arch manifest - no real release has
    # cut it yet) so `docker compose pull` and the "Clean install" CI
    # check have something real to fetch before release-alpha.yml's own
    # update-alpha-pins has ever run for it. Gets superseded by that
    # step's own real digest the moment a real release actually
    # promotes this component, same as every other component's own
    # first-ever pin.
    image: "${ROOTGUARD_ATTESTATION_PROXY_IMAGE:-ghcr.io/foxly-it/rootguard-attestation-proxy:1.0.12@sha256:0a8e65d95e62a07149989a98d9db71b68e5ff39da32b10b54bc53aeee80a6aaf}"
    container_name: rootguard-attestation-proxy
    restart: unless-stopped
    read_only: true
    cap_drop: [ALL]
    security_opt: ["no-new-privileges:true"]
    networks:
      - control
      - egress
    healthcheck:
      test: ["CMD", "/rootguard-attestation-proxy", "healthcheck"]
      interval: 15s
      timeout: 3s
      retries: 5

  # The only container holding the real Docker socket - Core and the
  # Updater reach it over `control` instead (see their DOCKER_HOST/
  # ROOTGUARD_DOCKER_PROXY_URL below), never mounting the socket
  # themselves. See docs/threat-model.md, actor 1 ("Docker socket
  # holders"), and rootguard-docker-proxy/README.md for the full
  # rationale - an allow-listed, request-body-validated subset of the
  # Docker Engine API, not a caller-authentication boundary between Core
  # and the Updater (they share the same allowlist). Unlike
  # attestation-proxy, this only ever dials the local socket, never the
  # internet, so it stays off `egress` entirely.
  docker-proxy:
    # Bootstrap pin: this component just joined release-alpha.yml's own
    # publish matrix (#666) - before this, nothing automatically refreshed
    # this pin, which went stale twice (1.0.2, then again 1.0.4; see
    # docs/release-history.md for both). Gets superseded by
    # update-alpha-pins' own real digest the moment the next real release
    # promotes it, same as every other component's own first-ever pin.
    image: "${ROOTGUARD_DOCKER_PROXY_IMAGE:-ghcr.io/foxly-it/rootguard-docker-proxy:1.0.12@sha256:a584efc34eb93dfff71d4a4258946124f499572044c7ea46499f7fe3f5724f74}"
    container_name: rootguard-docker-proxy
    restart: unless-stopped
    read_only: true
    cap_drop: [ALL]
    security_opt: ["no-new-privileges:true"]
    volumes:
      # Defaults to the rootful daemon's real socket. Rootless Docker's own
      # socket lives at a per-user path instead (typically
      # /run/user/<uid>/docker.sock) - install.sh detects a rootless
      # daemon and sets ROOTGUARD_DOCKER_SOCKET_PATH accordingly; a manual
      # deploy sets it directly. See docs/rootless-docker.md for the full
      # rootless-Docker findings (a real client-source-IP risk with the
      # default networking driver, resolved by using `pasta` instead).
      - ${ROOTGUARD_DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock
    networks:
      - control
    healthcheck:
      test: ["CMD", "/rootguard-docker-proxy", "healthcheck"]
      interval: 15s
      timeout: 3s
      retries: 5

  core:
    image: "${ROOTGUARD_CORE_IMAGE:-ghcr.io/foxly-it/rootguard-core:1.0.12@sha256:03873550b521e99656ab7a16684f2a234c91d788407b5c4ff0c39099cad8938d}"
    container_name: rootguard-core
    restart: unless-stopped
    depends_on:
      updater:
        condition: service_healthy
      attestation-proxy:
        condition: service_healthy
      docker-proxy:
        condition: service_healthy
    environment:
      PORT: "8081"
      # See rootguard-core/internal/stack/attestation.go's
      # runAttestationCommand doc comment for the full rationale - scoped
      # to cosign's own subprocess only, never Core's ambient environment.
      ROOTGUARD_ATTESTATION_PROXY_URL: http://attestation-proxy:8888
      # Redirects every `docker`/`docker compose` call dockercli.Run shells
      # out to (rootguard-core/internal/dockercli) at the real socket
      # docker-proxy itself holds - the Docker CLI already honors
      # DOCKER_HOST from the environment, so this needs no code change.
      # ROOTGUARD_DOCKER_PROXY_URL is this same endpoint, http:// instead
      # of tcp://, for CheckDockerProxyReachable's own /healthz check at
      # startup (rootguard-core/internal/stack/dockerproxy.go) - unset on
      # an installation whose compose.release.yaml predates this change,
      # which keeps mounting the real socket directly and keeps working
      # exactly as before (see that file's doc comment).
      DOCKER_HOST: tcp://docker-proxy:2375
      ROOTGUARD_DOCKER_PROXY_URL: http://docker-proxy:2375
      ROOTGUARD_API_TOKEN: "${ROOTGUARD_API_TOKEN:?set ROOTGUARD_API_TOKEN in .env}"
      ROOTGUARD_CORE_CONTAINER: rootguard-core
      # Test-only escape hatch - see cmd/rootguard/main.go's own doc
      # comment on ROOTGUARD_SKIP_ATTESTATION for what it disables and
      # why. Never set outside an E2E fixture; defaults to enforcing.
      ROOTGUARD_SKIP_ATTESTATION: "${ROOTGUARD_SKIP_ATTESTATION:-false}"
      ROOTGUARD_INSTALLATION_DIR: /var/lib/rootguard/installation
      ROOTGUARD_UNBOUND_IMAGE: "${ROOTGUARD_UNBOUND_IMAGE:-ghcr.io/foxly-it/rootguard-unbound:1.0.12@sha256:d1f257426769c54f6a89fcef0a40f4dfb2ab98ea47874d3074dad6c86c7a51e7}"
      ROOTGUARD_ADGUARD_IMAGE: "${ROOTGUARD_ADGUARD_IMAGE:-adguard/adguardhome:v0.107.80@sha256:e2e1b48c832f7705be915873f95de0d5114de573736441d7f2923a8a6293027c}"
      ROOTGUARD_ADGUARD_BETA_IMAGE: "${ROOTGUARD_ADGUARD_BETA_IMAGE:-adguard/adguardhome:beta@sha256:2b77703b27730d5c0c7045fcd6c98834169cd5c69af5f86a43947425f2d367fd}"
      ROOTGUARD_ADGUARD_UPDATE_IMAGE: "${ROOTGUARD_ADGUARD_UPDATE_IMAGE:-adguard/adguardhome:v0.107.80@sha256:e2e1b48c832f7705be915873f95de0d5114de573736441d7f2923a8a6293027c}"
      # Digest-pinned like every other RootGuard-built component now -
      # found in review: this was unset here entirely before, so Core
      # silently fell back to its own code default of a mutable :latest
      # tag with no digest pin and no attestation ever checked, unlike
      # core/webapp/unbound/the updater.
      ROOTGUARD_BLOCKPAGE_IMAGE: "${ROOTGUARD_BLOCKPAGE_IMAGE:-ghcr.io/foxly-it/rootguard-blockpage:1.0.12@sha256:d1b39a97f940598b60b0dd0b7a1efa2150fc6020d09bff1615f26c59dfa3d447}"
      # Found in review: Blockpage had no ServiceSpec in either update
      # manager at all, so an installation could only ever get a
      # Blockpage fix via reinstall or a manual compose refresh - unlike
      # every other RootGuard-built component. Same digest-pinned,
      # refreshed-every-release treatment as ROOTGUARD_UNBOUND_UPDATE_IMAGE
      # right below (live GitHub Releases discovery is the primary
      # source; this pin is only the fallback for when that lookup fails).
      ROOTGUARD_BLOCKPAGE_UPDATE_IMAGE: "${ROOTGUARD_BLOCKPAGE_UPDATE_IMAGE:-ghcr.io/foxly-it/rootguard-blockpage:1.0.12@sha256:d1b39a97f940598b60b0dd0b7a1efa2150fc6020d09bff1615f26c59dfa3d447}"
      ROOTGUARD_UNBOUND_UPDATE_IMAGE: "${ROOTGUARD_UNBOUND_UPDATE_IMAGE:-ghcr.io/foxly-it/rootguard-unbound:1.0.12@sha256:d1f257426769c54f6a89fcef0a40f4dfb2ab98ea47874d3074dad6c86c7a51e7}"
      ROOTGUARD_UPDATE_DIR: /var/lib/rootguard/updates
      ROOTGUARD_CONTROL_PLANE_UPDATER_URL: http://updater:8082
      ROOTGUARD_CONTROL_PLANE_UPDATER_TOKEN: "${ROOTGUARD_API_TOKEN:?set ROOTGUARD_API_TOKEN in .env}"
      # Found in review: this comment used to claim the value below stays
      # an unpinned :latest fallback, but it's digest-pinned right here
      # like every other value above and refreshed by the same
      # update-alpha-pins step (it iterates every managed component
      # generically, this one included) - stale ever since it was first
      # digest-pinned. Live GitHub Releases discovery
      # (ROOTGUARD_SELF_UPDATE_DIR below) is still the primary source;
      # this pin is only the fallback for when that lookup fails.
      ROOTGUARD_UPDATER_UPDATE_IMAGE: "${ROOTGUARD_UPDATER_UPDATE_IMAGE:-ghcr.io/foxly-it/rootguard-updater:1.0.12@sha256:c4cd325070ba9a7442e6b06705c621edcf581cd8a8683076c510c7b87c3c8317}"
      # Same fallback rationale as ROOTGUARD_UPDATER_UPDATE_IMAGE above -
      # live GitHub Releases discovery is the primary source
      # (rootguard#481: the attestation proxy joined self-update
      # management, no longer static/manually-pinned only).
      ROOTGUARD_ATTESTATION_PROXY_UPDATE_IMAGE: "${ROOTGUARD_ATTESTATION_PROXY_UPDATE_IMAGE:-ghcr.io/foxly-it/rootguard-attestation-proxy:1.0.12@sha256:0a8e65d95e62a07149989a98d9db71b68e5ff39da32b10b54bc53aeee80a6aaf}"
      ROOTGUARD_SELF_UPDATE_DIR: /var/lib/rootguard/updater-self-update
      # Core replaces the updater's own container the same way it already
      # replaces AdGuard/Unbound - via `docker compose ... up -d --no-deps
      # updater` against this same control-plane project. The mounted file
      # below is intentionally exposed as compose.yaml (not
      # compose.release.yaml) regardless of this stack's real filename,
      # since Core's generic update manager always looks for a file
      # literally named compose.yaml in whichever directory it's pointed at.
      ROOTGUARD_COMPOSE_FILE: /opt/rootguard/compose.yaml
      ROOTGUARD_COMPOSE_PROJECT: rootguard
      # Core never reads these five - they exist only so `docker compose -f
      # compose.yaml ... up -d --no-deps updater` (and, one hop further,
      # `... up -d --no-deps core webapp`) can interpolate the whole
      # compose file, which also references them for the webapp service.
      # Same reason the updater service carries them. Found in review:
      # only the two `${VAR:?...}` ones were carried here - the three
      # `${VAR:-default}` ones below were missed, so a self-update-
      # triggered `up -d --no-deps webapp` silently re-created it with
      # ROOTGUARD_WEB_BIND=0.0.0.0/ROOTGUARD_WEB_PORT=8080/
      # ROOTGUARD_ADMIN_USER=admin regardless of whatever an operator had
      # actually set in .env (e.g. binding to 127.0.0.1 behind a reverse
      # proxy, see docs/https-reverse-proxy.md) - it fails loudly instead
      # for the two required ones, silently for these three optional ones.
      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_ADMIN_USER: "${ROOTGUARD_ADMIN_USER:-admin}"
      ROOTGUARD_WEB_BIND: "${ROOTGUARD_WEB_BIND:-0.0.0.0}"
      ROOTGUARD_WEB_PORT: "${ROOTGUARD_WEB_PORT:-8080}"
      # Propagates the real host checkout directory into `docker compose -f
      # /opt/rootguard/compose.yaml ...` re-invocations (self-update and the
      # control-plane update both do this from inside a container). Without
      # this, `${PWD}` below re-resolves against the wrong process's cwd and
      # silently mounts an empty directory instead of the real file - see the
      # matching comment on the volumes line.
      PWD: "${PWD}"
      UNBOUND_CONFIG_DIR: /var/lib/rootguard/unbound
      UNBOUND_CONTAINER_CONFIG_DIR: /etc/unbound/unbound.d
      UNBOUND_CONTAINER_NAME: rootguard-unbound
      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
      ROOTGUARD_BLOCKPAGE_AUTH_DIR: /var/lib/rootguard/adguard-auth
    volumes:
      - rootguard-data:/var/lib/rootguard
      - unbound-config:/var/lib/rootguard/unbound
      - adguard-auth:/var/lib/rootguard/adguard-auth
      # ${PWD}, not a relative "./compose.release.yaml": see the matching
      # PWD/${PWD} comments on this file's core service, above - the same
      # self-referential-mount hazard applies here.
      - ${PWD}/compose.release.yaml:/opt/rootguard/compose.yaml:ro
    networks:
      - control
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8081/api/health"]
      interval: 15s
      timeout: 5s
      retries: 5

  webapp:
    image: "${ROOTGUARD_WEBAPP_IMAGE:-ghcr.io/foxly-it/rootguard-webapp:1.0.12@sha256:efbf103319426f06f84018d97b22803afb47b6a40f6d13a0f2cf0e1f030eeb7c}"
    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_BIND:-0.0.0.0}:${ROOTGUARD_WEB_PORT:-8080}:8080/tcp"
    networks:
      - control
      - edge
    # Found in review: missing here despite compose.yaml (dev) and
    # compose.integration.yaml both having it - the one public entrypoint
    # had no health state at all in the file this release actually ships.
    # 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:
    image: "${ROOTGUARD_UPDATER_IMAGE:-ghcr.io/foxly-it/rootguard-updater:1.0.12@sha256:c4cd325070ba9a7442e6b06705c621edcf581cd8a8683076c510c7b87c3c8317}"
    container_name: rootguard-updater
    restart: unless-stopped
    depends_on:
      attestation-proxy:
        condition: service_healthy
      docker-proxy:
        condition: service_healthy
    environment:
      # See rootguard-updater/attestation.go's verifyAttestation doc
      # comment for the full rationale - scoped to cosign's own
      # subprocess only, never the Updater's ambient environment.
      ROOTGUARD_ATTESTATION_PROXY_URL: http://attestation-proxy:8888
      # See core's identical comment above - same redirection, same
      # backward-compat behavior when unset.
      DOCKER_HOST: tcp://docker-proxy:2375
      ROOTGUARD_DOCKER_PROXY_URL: http://docker-proxy:2375
      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.release.yaml
      ROOTGUARD_COMPOSE_PROJECT: rootguard
      ROOTGUARD_CORE_UPDATE_IMAGE: "${ROOTGUARD_CORE_UPDATE_IMAGE:-ghcr.io/foxly-it/rootguard-core:1.0.12@sha256:03873550b521e99656ab7a16684f2a234c91d788407b5c4ff0c39099cad8938d}"
      ROOTGUARD_WEBAPP_UPDATE_IMAGE: "${ROOTGUARD_WEBAPP_UPDATE_IMAGE:-ghcr.io/foxly-it/rootguard-webapp:1.0.12@sha256:efbf103319426f06f84018d97b22803afb47b6a40f6d13a0f2cf0e1f030eeb7c}"
      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_BIND: "${ROOTGUARD_WEB_BIND:-0.0.0.0}"
      ROOTGUARD_WEB_PORT: "${ROOTGUARD_WEB_PORT:-8080}"
      ROOTGUARD_CORE_IMAGE: "${ROOTGUARD_CORE_IMAGE:-ghcr.io/foxly-it/rootguard-core:1.0.12@sha256:03873550b521e99656ab7a16684f2a234c91d788407b5c4ff0c39099cad8938d}"
      ROOTGUARD_WEBAPP_IMAGE: "${ROOTGUARD_WEBAPP_IMAGE:-ghcr.io/foxly-it/rootguard-webapp:1.0.12@sha256:efbf103319426f06f84018d97b22803afb47b6a40f6d13a0f2cf0e1f030eeb7c}"
      ROOTGUARD_SESSION_DIR: /var/lib/rootguard-sessions
      # See the matching PWD/${PWD} comments on the core service above - the
      # same self-referential-mount hazard applies here now that Core can
      # recreate this very service (self-update).
      PWD: "${PWD}"
    volumes:
      - rootguard-data:/var/lib/rootguard
      - rootguard-sessions:/var/lib/rootguard-sessions
      - ${PWD}/compose.release.yaml:/opt/rootguard/compose.release.yaml:ro
    networks:
      - control
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8082/health"]
      interval: 15s
      timeout: 5s
      retries: 5

networks:
  edge:
  control:
    internal: true
  # Not internal - real internet access, but only
  # rootguard-attestation-proxy sits here. Core/the Updater stay on
  # `control` only; they never join `egress` directly.
  egress:

volumes:
  unbound-config:
    name: rootguard-unbound-config
  rootguard-data:
    name: rootguard-data
  rootguard-sessions:
    name: rootguard-sessions
  adguard-auth:
    name: rootguard-adguard-auth