name: dchain-single # ══════════════════════════════════════════════════════════════════════════ # Single-node DChain deployment. # # One validator (or observer) + Caddy TLS edge + optional # Prometheus/Grafana. Intended for: # - Personal nodes: operator runs their own, optionally private. # - Tail of a larger network: joins via --join, participates / observes. # - First node of a brand-new network: starts with --genesis. # # Quick start: # cp node.env.example node.env # edit DOMAIN / API_TOKEN / JOIN # docker compose up -d # node + Caddy # docker compose --profile monitor up -d # # For a multi-validator cluster see deploy/prod/ (3-of-3 PBFT setup). # ══════════════════════════════════════════════════════════════════════════ networks: dchain: name: dchain_single driver: bridge volumes: node_data: caddy_data: caddy_config: prom_data: grafana_data: services: # ── The node ────────────────────────────────────────────────────────── # One process does everything: consensus (if validator), relay, HTTP, # WebSocket, metrics. Three knobs are worth knowing before first boot: # # 1. DCHAIN_GENESIS=true → creates block 0 with THIS node's key as sole # validator. Use only once, on the very first node of a fresh chain. # Drop the flag on subsequent restarts (no-op but noisy). # 2. DCHAIN_JOIN=http://...,http://... → fetch /api/network-info from # the listed seeds, auto-populate --peers / --validators, sync chain. # Use this when joining an existing network instead of --genesis. # 3. DCHAIN_API_TOKEN=... → if set, gates POST /api/tx (and WS submit). # With DCHAIN_API_PRIVATE=true, gates reads too. Empty = public. node: build: context: ../.. dockerfile: deploy/prod/Dockerfile.slim container_name: dchain_node restart: unless-stopped env_file: ./node.env networks: [dchain] volumes: - node_data:/data - ./keys/node.json:/keys/node.json:ro # 4001 → libp2p P2P (MUST be publicly routable for federation) # 8080 → HTTP + WebSocket, only exposed internally to Caddy by default ports: - "4001:4001" expose: - "8080" cap_drop: [ALL] read_only: true tmpfs: [/tmp] security_opt: [no-new-privileges:true] healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/api/netstats >/dev/null || exit 1"] interval: 10s timeout: 3s retries: 6 start_period: 15s command: - "--db=/data/chain" - "--mailbox-db=/data/mailbox" - "--key=/keys/node.json" - "--relay-key=/data/relay.json" - "--listen=/ip4/0.0.0.0/tcp/4001" - "--stats-addr=:8080" # All other config comes via DCHAIN_* env vars from node.env. # ── TLS edge ────────────────────────────────────────────────────────── caddy: image: caddy:2.8-alpine container_name: dchain_caddy restart: unless-stopped networks: [dchain] ports: - "80:80" - "443:443" - "443:443/udp" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config environment: DOMAIN: ${DOMAIN:-localhost} ACME_EMAIL: ${ACME_EMAIL:-admin@example.com} depends_on: node: { condition: service_healthy } # ── Observability (opt-in) ──────────────────────────────────────────── prometheus: profiles: [monitor] image: prom/prometheus:v2.53.0 container_name: dchain_prometheus restart: unless-stopped networks: [dchain] volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - prom_data:/prometheus command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.retention.time=30d" grafana: profiles: [monitor] image: grafana/grafana:11.1.0 container_name: dchain_grafana restart: unless-stopped networks: [dchain] ports: - "3000:3000" depends_on: [prometheus] environment: GF_SECURITY_ADMIN_USER: admin GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PW:-change-me} GF_USERS_ALLOW_SIGN_UP: "false" volumes: - grafana_data:/var/lib/grafana