Ctrl K
ComunidadGuías…

docker-compose.unbound.yml

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

Archivo original ↗Preguntar a KiBot
# DOMINUS Unbound Recursor — ADR-0072 Hardened DNS Architecture.
#
# Single source of truth for DNS: one properly configured Unbound recursive
# resolver with DNSSEC validation, DoT upstream, and anycast-free resolution.
# Replaces the multi-leg consensus architecture (DoH/DoT/tertiary).
#
# Usage (Community):
#   docker compose -f docker-compose.yml -f docker-compose.unbound.yml up -d
#
# Usage (Cloud):
#   docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.unbound.yml up -d
#
# The recursor listens on port 5300 (plain UDP/TCP) and 853 (DoT) on the
# compose subnet. Application services query it via
# DNS_UNBOUND_HOSTS=unbound:5300 with DNS_UNBOUND_ENABLED=true (default).
# That hop is PLAIN DNS on the internal compose network — node:dns has no DoT
# capability, so DNS_UNBOUND_TLS is reserved for a future transport and has
# no effect today (see its JSDoc in src/config.ts). The DoT that is actually
# active is Unbound's own upstream leg to Cloudflare/Google/Quad9 below.
#
# Privacy: with this architecture, NO candidate domain name leaves the host
# except to the pinned Unbound recursor. The recursor itself queries
# authoritative servers directly (root → TLD → authoritative) with DNSSEC
# validation, eliminating the commercial signal leakage of public DoH/DoT.
#
# Upstreams (DoT): Cloudflare (1.1.1.1), Google (8.8.8.8), Quad9 (9.9.9.9)
# are used as fallback forwarders when authoritative resolution fails or for
# zones not in the cache. All DoT connections use TLS 1.3 with certificate
# validation (tls-upstream: yes).
#
# Image (ADR-0046): mvance/unbound@sha256:... — community-maintained image
# recommended by the Unbound project, digest-pinned. Multi-arch (amd64/arm64).

x-unbound-env: &unbound-env
  DNS_UNBOUND_ENABLED: 'true'
  DNS_UNBOUND_HOSTS: 'unbound:5300'
  # The sidecar provides validated resolution, so strict mode is safe and
  # desired here on both editions (ADR-0075 hardened path). The base file
  # leaves STRICT unset for the context-aware default (ADR-0076); this
  # overlay opts into the hardened posture explicitly.
  DNS_UNBOUND_STRICT: 'true'
  # Reserved for a future app-to-Unbound DoT transport; currently a no-op
  # (see JSDoc in src/config.ts). Left false to not overclaim encryption.
  DNS_UNBOUND_TLS: 'false'
  DNS_UNBOUND_TIMEOUT_MS: '1500'
  DNS_PARKING_CHECK_ENABLED: 'false'

services:
  unbound:
    image: mvance/unbound@sha256:76906da36d1806f3387338f15dcf8b357c51ce6897fb6450d6ce010460927e90
    restart: always
    # No static ipv4_address on unbound-net: nothing addresses this service
    # by IP (the app connects to `unbound:5300` by name), and a hardcoded
    # address silently falls outside the range when DOMINUS_UNBOUND_SUBNET
    # is changed, which fails `up -d` with an address-out-of-range error.
    networks:
      - unbound-net
    deploy:
      resources:
        limits:
          cpus: '0.25'
          memory: 512M
        reservations:
          cpus: '0.1'
          memory: 128M
    volumes:
      - ./deploy/unbound/unbound.conf:/opt/unbound/unbound.conf:ro
    # NOTE: `command` replaces the image CMD (`/unbound.sh`), so the image
    # entrypoint's config generation and `unbound-anchor` bootstrap never run.
    # The anchor bootstrap is reproduced here explicitly: without a root
    # trust anchor the validator treats every zone as insecure and the app's
    # negative-control probe cannot prove DNSSEC validation — strict boot
    # fails closed. The anchor path is chroot-relative (`var/root.key` under
    # /opt/unbound/etc/unbound): an absolute /tmp path fails checkconf
    # because unbound resolves it inside the chroot. Ownership mirrors the
    # image entrypoint (_unbound) so RFC 5011 auto-updates keep working on
    # long-lived deployments. The fetch is best-effort (`|| true`): unbound
    # itself performs RFC 5011 tracking from the initial key once it has
    # egress, and a missing key fails visibly at the app readiness probe
    # instead of wedging container startup on exit codes.
    command:
      - sh
      - -c
      - mkdir -p /opt/unbound/etc/unbound/var && chown _unbound:_unbound /opt/unbound/etc/unbound/var && /opt/unbound/sbin/unbound-anchor -a /opt/unbound/etc/unbound/var/root.key || true; exec unbound -d -c /opt/unbound/unbound.conf
    # Liveness probe via a bash TCP connect to the plain-DNS listener.
    # The previous `ps aux | grep unbound` probe always failed: the
    # mvance/unbound image ships no `ps` binary (exit 127), so the service
    # never became healthy and every compose profile depending on it
    # (E2E, smoke) failed at startup. A TCP connect proves the accept
    # loop is alive without requiring upstream egress (a `drill` query
    # would couple container health to external network/DNSSEC state).
    healthcheck:
      test: ['CMD-SHELL', 'bash -c "</dev/tcp/127.0.0.1/5300"']
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s
    logging:
      driver: 'json-file'
      options:
        max-size: '10m'
        max-file: '3'
        compress: 'true'

  api:
    environment:
      <<: *unbound-env
    networks:
      - default
      - unbound-net
    depends_on:
      unbound:
        condition: service_healthy

  worker:
    environment:
      <<: *unbound-env
    networks:
      - default
      - unbound-net
    depends_on:
      unbound:
        condition: service_healthy

  scheduler:
    environment:
      <<: *unbound-env
    networks:
      - default
      - unbound-net
    depends_on:
      unbound:
        condition: service_healthy

networks:
  unbound-net:
    driver: bridge
    ipam:
      config:
        # Must not overlap any network already on this Docker host: a
        # collision aborts `up -d` with "invalid pool request: Pool
        # overlaps with other one on this address space", before any
        # container starts.
        #
        # 10.89.0.0/24 is deliberately OUTSIDE Docker's default address
        # pool (172.16.0.0/12). The previous literal 172.21.0.0/24 lived
        # inside that pool, so any other user-defined network on the host
        # could collide with it — which is exactly what broke the README
        # quick-start on stock Docker Desktop. When it still collides (for
        # example with a host LAN on the same range), override the whole
        # thing with DOMINUS_UNBOUND_SUBNET.
        - subnet: ${DOMINUS_UNBOUND_SUBNET:-10.89.0.0/24}