services: bitcoind: image: bitcoin/bitcoin:31.0 restart: unless-stopped labels: - autoheal=true environment: BITCOIN_DATA: /home/bitcoin/.bitcoin BITCOIN_RPC_USER: ${BITCOIN_RPC_USER:-mapper} BITCOIN_RPC_PASSWORD: ${BITCOIN_RPC_PASSWORD:-mapper-dev-password} BITCOIN_PRUNE_MIB: ${BITCOIN_PRUNE_MIB:-10000} # Passive mode: -listen=0 means outbound-only (no inbound P2P load), # which is all the mapper needs — RPC telemetry and getaddr gossip # still flow in from the outbound peers. Larger dbcache/maxmempool and # a larger prune target reduce the frequency and duration of UTXO # flushes and prune events, both of which can block RPC for long # enough to make the node appear frozen. command: - -printtoconsole - -server=1 - -listen=0 - -rpcbind=0.0.0.0 - -rpcallowip=172.16.0.0/12 - -rpcuser=${BITCOIN_RPC_USER:-mapper} - -rpcpassword=${BITCOIN_RPC_PASSWORD:-mapper-dev-password} - -prune=${BITCOIN_PRUNE_MIB:-10000} - -maxconnections=16 - -dbcache=${BITCOIN_DBCACHE_MIB:-512} - -maxmempool=${BITCOIN_MAXMEMPOOL_MIB:-300} - -disablewallet=1 healthcheck: test: - CMD-SHELL - bitcoin-cli -rpcconnect=127.0.0.1 -rpcport=8332 -rpcuser=$$BITCOIN_RPC_USER -rpcpassword=$$BITCOIN_RPC_PASSWORD getblockchaininfo >/dev/null 2>&1 interval: 30s timeout: 15s retries: 5 start_period: 180s volumes: - ./data/bitcoin:/home/bitcoin/.bitcoin mapper: build: context: . restart: unless-stopped labels: - autoheal=true environment: BITCOIN_NODE_HOST: ${BITCOIN_NODE_HOST:-bitcoind} BITCOIN_NODE_PORT: ${BITCOIN_NODE_PORT:-8333} BITCOIN_RPC_URL: ${BITCOIN_RPC_URL:-http://bitcoind:8332/} BITCOIN_RPC_USER: ${BITCOIN_RPC_USER:-mapper} BITCOIN_RPC_PASSWORD: ${BITCOIN_RPC_PASSWORD:-mapper-dev-password} EMBEDDED_BITCOIND_ENABLED: ${EMBEDDED_BITCOIND_ENABLED:-true} MAXMIND_DB_ROOT: /maxmind_databases STORAGE_PATH: /data/network_map.sqlite3 COLLECTION_INTERVAL_SECONDS: ${COLLECTION_INTERVAL_SECONDS:-600} REQUEST_TIMEOUT_SECONDS: ${REQUEST_TIMEOUT_SECONDS:-4} CRAWL_DEPTH: ${CRAWL_DEPTH:-2} MAX_NODES_PER_CYCLE: ${MAX_NODES_PER_CYCLE:-32} BOOTSTRAP_DNS_SEEDS: ${BOOTSTRAP_DNS_SEEDS:-seed.bitcoin.sipa.be,dnsseed.bluematt.me,dnsseed.bitcoin.dashjr.org,seed.bitcoinstats.com,seed.bitcoin.jonasschnelli.ch,seed.btc.petertodd.net} HTTP_PORT: 8080 LOG_LEVEL: ${LOG_LEVEL:-INFO} APP_LOG_PATH: /data/logs/mapper.log MAXMIND_ACCOUNT_ID: ${MAXMIND_ACCOUNT_ID:-} MAXMIND_LICENSE_KEY: ${MAXMIND_LICENSE_KEY:-} # ── Coinbase trading (optional) ────────────────────────────── COINBASE_API_KEY_NAME: ${COINBASE_API_KEY_NAME:-} COINBASE_API_KEY_SECRET: ${COINBASE_API_KEY_SECRET:-} PAPER_TRADE: ${PAPER_TRADE:-true} PAPER_BALANCE_GBP: ${PAPER_BALANCE_GBP:-100.0} TRADE_QUOTE_GBP: ${TRADE_QUOTE_GBP:-10.0} PROFIT_TARGET_PCT: ${PROFIT_TARGET_PCT:-1.5} STOP_LOSS_PCT: ${STOP_LOSS_PCT:-2.0} MAX_HOLD_HOURS: ${MAX_HOLD_HOURS:-24} MAX_OPEN_TRADES: ${MAX_OPEN_TRADES:-3} ports: - "${MAPPER_PORT:-8088}:8080" volumes: - ./data:/data - ./maxmind_databases:/maxmind_databases # /api/health returns 200 only when snapshot, RPC, and block-tip are # all fresh. A 503 here will flip the container to unhealthy, which # the autoheal sidecar then restarts. The start_period is generous # enough to absorb a cold bitcoind sync and the first collection cycle. healthcheck: test: - CMD - python - -c - | import sys, urllib.request try: r = urllib.request.urlopen("http://127.0.0.1:8080/api/health", timeout=8) sys.exit(0 if r.status == 200 else 1) except Exception: sys.exit(1) interval: 30s timeout: 12s retries: 4 start_period: 300s maxmind-updater: build: context: . restart: unless-stopped depends_on: - mapper environment: MAXMIND_ACCOUNT_ID: ${MAXMIND_ACCOUNT_ID:-} MAXMIND_LICENSE_KEY: ${MAXMIND_LICENSE_KEY:-} MAXMIND_REFRESH_INTERVAL_SECONDS: ${MAXMIND_REFRESH_INTERVAL_SECONDS:-86400} entrypoint: - /bin/sh - -c - | if [ -z "$${MAXMIND_ACCOUNT_ID}" ] || [ -z "$${MAXMIND_LICENSE_KEY}" ]; then echo "maxmind-updater: credentials not configured; sleeping" while true; do sleep 3600; done fi while true; do python /app/maxmind/download_maxmind_files.py \ --account-id "$${MAXMIND_ACCOUNT_ID}" \ --license-key "$${MAXMIND_LICENSE_KEY}" \ --dir /maxmind_databases || true sleep "$${MAXMIND_REFRESH_INTERVAL_SECONDS}" done volumes: - ./maxmind_databases:/maxmind_databases prometheus: image: prom/prometheus:latest restart: unless-stopped depends_on: - mapper ports: - "${PROMETHEUS_PORT:-9091}:9090" volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus # Watches containers labelled `autoheal=true` and restarts any that go # unhealthy per their Docker healthcheck. Mounts the Docker socket, which # is a modest privilege expansion — scope it to the local host only. autoheal: image: willfarrell/autoheal:latest restart: unless-stopped environment: AUTOHEAL_CONTAINER_LABEL: autoheal AUTOHEAL_INTERVAL: ${AUTOHEAL_INTERVAL_SECONDS:-30} AUTOHEAL_START_PERIOD: 60 AUTOHEAL_DEFAULT_STOP_TIMEOUT: 30 volumes: - /var/run/docker.sock:/var/run/docker.sock grafana: image: grafana/grafana:latest restart: unless-stopped depends_on: - prometheus ports: - "${GRAFANA_PORT:-3001}:3000" environment: GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin} GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin} GF_USERS_ALLOW_SIGN_UP: "false" volumes: - ./grafana/provisioning:/etc/grafana/provisioning:ro - ./grafana/dashboards:/var/lib/grafana/dashboards:ro - grafana_data:/var/lib/grafana volumes: grafana_data: prometheus_data: