Repository navigation
Expand file tree
/
Copy pathdocker-compose.testnet.yml
More file actions
123 lines (115 loc) · 4.76 KB
/
Copy pathdocker-compose.testnet.yml
File metadata and controls
123 lines (115 loc) · 4.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# Always-on testnet DVT — 3 independent signer nodes + a Cloudflare named tunnel.
#
# Anyone can clone this repo and stand up their OWN DVT:
# 1. cp deploy/.env.testnet.example deploy/.env.testnet && fill it in
# 2. put each node's node_state.json (its OWN secret BLS key) in deploy/node{1,2,3}/
# 3. docker compose -f docker-compose.testnet.yml --env-file deploy/.env.testnet up -d --build
# 4. register each node's BLS pubkey on the validator (see deploy/README.md)
#
# The cloudflared sidecar exposes the 3 nodes at stable public hostnames you configure
# in the Cloudflare dashboard (Public Hostnames → http://dvt-node-N:300N). See deploy/README.md.
name: dvt-testnet
# ⚠️ PREFLIGHT: each deploy/node{1,2,3}/node_state.json MUST exist before `up`.
# A missing bind-mount source makes Docker silently create an EMPTY DIRECTORY there,
# which mounts over /app/node_state.json and breaks the node confusingly. Guard with:
# for i in 1 2 3; do test -f deploy/node$i/node_state.json || { echo "missing node$i key"; exit 1; }; done
x-dvt-env: &dvt-env
ETH_RPC_URL: ${ETH_RPC_URL:?set ETH_RPC_URL in deploy/.env.testnet}
VALIDATOR_CONTRACT_ADDRESS: ${VALIDATOR_CONTRACT_ADDRESS:?set VALIDATOR_CONTRACT_ADDRESS (v0.20.0 = 0xAF525A161CB17e0A1b6254ef0B8d8473bdA05174)}
ENTRY_POINT_ADDRESS: ${ENTRY_POINT_ADDRESS:-0x0000000071727De22E5E9d8BAf0edAc6f37da032}
POLICY_ENABLED: ${POLICY_ENABLED:-false}
POLICY_REGISTRY_ADDRESS: ${POLICY_REGISTRY_ADDRESS:-}
x-dvt-node: &dvt-node
build:
context: .
dockerfile: Dockerfile
image: aastar-dvt:latest
# Self-heal layer 1: restart on crash / OOM / non-zero exit + on host reboot.
restart: unless-stopped
networks: [dvt]
# Self-heal layer 2: detect a HUNG-but-alive process (serving stopped, port still
# bound → restart:unless-stopped alone would NOT catch it). The autoheal sidecar
# watches this status and restarts the container when it goes unhealthy.
# Uses the Dockerfile HEALTHCHECK ($PORT-aware); overridden here for visibility.
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:'+process.env.PORT+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
# Opt the container into autoheal (only containers with this label are watched).
labels:
autoheal: "true"
services:
dvt-node-1:
<<: *dvt-node
container_name: dvt-node-1
environment:
<<: *dvt-env
PORT: 3001
PUBLIC_URL: ${NODE1_PUBLIC_URL:-}
volumes:
- ./deploy/node1/node_state.json:/app/node_state.json:ro
ports: ["127.0.0.1:3001:3001"] # localhost-only; public access is via the tunnel
dvt-node-2:
<<: *dvt-node
container_name: dvt-node-2
environment:
<<: *dvt-env
PORT: 3002
PUBLIC_URL: ${NODE2_PUBLIC_URL:-}
volumes:
- ./deploy/node2/node_state.json:/app/node_state.json:ro
ports: ["127.0.0.1:3002:3002"]
dvt-node-3:
<<: *dvt-node
container_name: dvt-node-3
environment:
<<: *dvt-env
PORT: 3003
PUBLIC_URL: ${NODE3_PUBLIC_URL:-}
volumes:
- ./deploy/node3/node_state.json:/app/node_state.json:ro
ports: ["127.0.0.1:3003:3003"]
# Cloudflare named tunnel (token-based). Public hostnames are configured in the
# Cloudflare dashboard and point at http://dvt-node-N:300N (same docker network).
cloudflared:
image: cloudflare/cloudflared:latest
container_name: dvt-cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN:?set CLOUDFLARE_TUNNEL_TOKEN in deploy/.env.testnet}
networks: [dvt]
# Wait until each node is actually SERVING (healthy), not merely started — else
# the tunnel comes up and routes to nodes that aren't answering yet (502s on boot).
depends_on:
dvt-node-1:
condition: service_healthy
dvt-node-2:
condition: service_healthy
dvt-node-3:
condition: service_healthy
# Self-heal layer 2 (executor): restarts any container marked `autoheal=true` that
# Docker reports UNHEALTHY. This is what turns a failing healthcheck into an actual
# restart — `restart: unless-stopped` only reacts to exit, not to "unhealthy".
# Mounts the Docker socket read-only-ish (needs write to restart); scoped by label.
autoheal:
image: willfarrell/autoheal:latest
container_name: dvt-autoheal
restart: unless-stopped
environment:
AUTOHEAL_CONTAINER_LABEL: autoheal # only watch containers with label autoheal=true
AUTOHEAL_INTERVAL: 10 # check every 10s
AUTOHEAL_START_PERIOD: 30 # grace before first check
CURL_TIMEOUT: 30
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks: [dvt]
networks:
dvt:
driver: bridge