-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
481 lines (469 loc) · 21.4 KB
/
Copy pathdocker-compose.yml
File metadata and controls
481 lines (469 loc) · 21.4 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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# NOTE: there is intentionally no `env_file: .env` anywhere in this file.
# `.env` is docker compose's interpolation source only — a variable reaches a
# container solely when an `environment:` entry below wires it through. This
# keeps each service's env contract explicit and stops secrets like
# POSTGRES_PASSWORD from leaking into every container.
# Runtime uid/gid mapping, shared by every image that does the
# root-entrypoint -> chown /data -> gosu privilege-drop dance.
x-runtime-user: &runtime-user
PUID: ${PUID:-1000}
PGID: ${PGID:-1000}
# Every long-lived service forwards stdout to the colocated quip-syslog
# collector, which merges all of them into one host-readable file at
# data/logs/quip-node.log. This replaces the per-container json-file logs,
# which died with the container on every Watchtower update.
#
# `docker compose logs` and `docker logs` still work: Docker keeps a local
# ring-buffer cache alongside any non-readable driver (dual logging), sized by
# cache-max-size / cache-max-file below. Do NOT set cache-disabled.
#
# The address is a HOST address. The Docker daemon opens this socket, not the
# container, so the compose network name quip-syslog would not resolve.
x-logging: &default-logging
driver: syslog
options:
syslog-address: "udp://127.0.0.1:${QUIP_LOG_PORT:-5514}"
# Container name becomes the PROGRAM field, which is the per-line prefix
# in the merged file. Without it every line is tagged with a container id.
tag: "{{.Name}}"
cache-max-size: ${QUIP_LOG_MAX_SIZE:-32m}
# Quoted: docker rejects cache-max-file as a bare int.
cache-max-file: "${QUIP_LOG_MAX_FILE:-5}"
# Shared dashboard configuration. The dashboard is always present alongside
# every node profile; Caddy fronts it on the configured QUIP_HOSTNAME.
x-dashboard: &dashboard
image: registry.gitlab.com/quip.network/dashboard.quip.network:${QUIP_DASHBOARD_TAG:-${CHANNEL:-beta}}
pull_policy: always
container_name: quip-dashboard
restart: unless-stopped
environment:
# Adapter selection is driven by DATABASE_URL presence (postgres when
# set, sqlite fallback otherwise) — there is no DB_ADAPTER env var.
DATABASE_URL: postgresql://${POSTGRES_USER:-quip}:${POSTGRES_PASSWORD:-quip}@postgres:5432/${POSTGRES_DB:-quip}
# Front-door URL the dashboard uses for BOTH the chain RPC (/rpc, for the
# indexer) and — by stripping /rpc — the local miner REST surface
# (/api/v1, for self-identification). Both must resolve from one host, so
# this points at Caddy's stack-internal :8088 listener: /rpc → quip-validator
# and /api/v1 → quip-miner. Pointing it straight at quip-validator:9944
# would 404 the /api/v1 probe, so the dashboard would mis-identify "self" as
# some other reachable on-chain node. Miner-only nodes can override with a
# remote front door (e.g. wss://bootnode-1.aglais.quip.network/rpc).
QUIP_VALIDATOR_RPC_URLS: ${QUIP_VALIDATOR_RPC_URLS:-ws://quip-caddy:8088/rpc}
PORT: "3001"
RUN_SERVER: "true"
RUN_INDEXER: "true"
logging: *default-logging
volumes:
- ./dashboard-data:/data
# Caddy proxies quip-dashboard:3001. An explicit alias — not container_name
# DNS — so the localdev override's renamed containers still resolve.
networks:
default:
aliases:
- quip-dashboard
depends_on:
postgres:
condition: service_healthy
# The indexer scans from genesis, so against a validator that is still
# catching up it indexes an empty chain and caches that as the network.
# required: false keeps miner-only deployments working, where the dashboard
# reads a remote front door (QUIP_VALIDATOR_RPC_URLS) and runs no validator.
quip-validator:
condition: service_healthy
required: false
quip-syslog:
condition: service_started
services:
# Both miner images ship the v0.3 quip-coordinator with every miner binary
# it supports bundled in. The coordinator is the supervised process: it
# connects to the chain and launches the miners declared in
# /data/config.toml, which the image seeds from its own /app/config.toml on
# first run. There are no QUIP_* config env vars. The environment block
# carries only host concerns (PUID/PGID) plus the D-Wave credential.
cpu:
image: registry.gitlab.com/quip.network/quip-miner/v0.3/quip-miner:${QUIP_MINER_TAG:-${CHANNEL:-beta}}
pull_policy: always
container_name: quip-cpu
restart: unless-stopped
profiles:
- cpu
# Restrict visible CPUs so the upstream entrypoint's `num_cpus = $(nproc)`
# autodetect writes a polite default instead of saturating the host. Linux
# `nproc` reads sched_getaffinity(2), which honors the cgroup cpuset — so
# this is what the entrypoint actually sees, unlike `cpus:` (CFS quota).
# Operators with dedicated miner hosts override with e.g. QUIP_MINER_CPUSET=0-15.
cpuset: ${QUIP_MINER_CPUSET:-0}
# Each SA worker streams samples through a POSIX shared-memory ring
# (~75 MiB/worker at the Advantage2 topology's max reads). Docker's
# 64 MiB /dev/shm default can't back even one worker's ring, so the
# miner dies with SIGBUS (exitcode=-7) — reported on a 12-core host,
# but the default single-CPU config hits it too. tmpfs is allocated
# lazily, so a generous cap costs nothing until written. (The cuda
# service instead sets ipc:host, which shares the host's /dev/shm.)
# See quip-miner v0.2.1-rc45.
shm_size: "2gb"
# STOPGAP (v0.2). The miner's working set grows without bound during a
# round: a 27.1 GiB peak on a 32 GiB host triggered two *global* OOM-kills,
# taking the validator and dashboard down with it. This cap converts that
# host-wide failure into a single container kill that `restart:
# unless-stopped` recovers from — it does not fix the leak, which is
# scheduled for v0.3. Until then the operator-side mitigation is a periodic
# reboot (or `docker compose restart cpu`) between rounds.
#
# NOTE: the 2 GiB shm ring above is tmpfs, and tmpfs pages are charged to
# this same cgroup — so this is ~14 GiB of heap plus the ring, not 16 + 2.
# Size it below total RAM with headroom for the colocated validator,
# postgres and dashboard; raise it only on a dedicated miner host.
mem_limit: ${QUIP_MINER_MEM_LIMIT:-16g}
logging: *default-logging
environment:
<<: *runtime-user
# D-Wave connection settings, read by the QPU layer only. The miner
# passes solver/region/token to DWaveSampler solely as explicit
# overrides and otherwise lets the Ocean SDK resolve them, so these use
# the SDK's canonical names. ~/.config/dwave/dwave.conf remains the
# file-based alternative. Empty resolves identically to unset.
#
# DWAVE_API_TOKEN is the name the SDK and the miner actually read;
# DWAVE_API_KEY is accepted here only so existing .env files keep
# working. Nothing reads DWAVE_API_KEY itself.
DWAVE_API_TOKEN: ${DWAVE_API_TOKEN:-${DWAVE_API_KEY:-}}
# Pin the solver. Without it the SDK picks whatever the account defaults
# to, which may not be the Advantage2 system the chain topology targets.
DWAVE_API_SOLVER: ${DWAVE_API_SOLVER:-}
DWAVE_API_REGION: ${DWAVE_API_REGION:-}
volumes:
- ./data:/data
# First-run config template, mounted over the image's own. The image
# entrypoint seeds /data/config.toml from /app/config.toml when the file
# is missing, and the upstream template still names the retired testnet
# faucet. See config/quip-miner.toml.
- ./config/quip-miner.toml:/app/config.toml:ro
networks:
default:
aliases:
- quip-miner
depends_on:
# service_healthy, not service_started: the coordinator's preflight reads
# the runtime at the validator's best block, so against a node that is
# still catching up it reads the genesis runtime (quip/103, QuantumPowApi
# v1) and exits. Waiting for the sync to finish is what makes that check
# meaningful. A fresh testnet install therefore holds here until the
# initial sync completes; see "Initial sync" in README.md.
quip-validator:
condition: service_healthy
required: false
quip-syslog:
condition: service_started
cuda:
image: registry.gitlab.com/quip.network/quip-miner/v0.3/quip-miner-cuda:${QUIP_MINER_TAG:-${CHANNEL:-beta}}
pull_policy: always
container_name: quip-cuda
restart: unless-stopped
profiles:
- cuda
# GPU sharing. ipc:host lets the miner attach to a host NVIDIA MPS control
# daemon (started by the manager on native Linux GPU hosts) for hardware SM
# partitioning; pid:host lets the miner's NVML detect sibling GPU processes
# for process-based yielding. Both are harmless without a daemon — the
# mounted MPS pipe dir is empty and the miner falls back to software nonce
# reduction. NOTE: NVIDIA MPS is unsupported under WSL2 / Docker Desktop, so
# these only engage on a native Linux GPU host.
ipc: host
pid: host
logging: *default-logging
environment:
<<: *runtime-user
# D-Wave connection settings, read by the QPU layer only. The miner
# passes solver/region/token to DWaveSampler solely as explicit
# overrides and otherwise lets the Ocean SDK resolve them, so these use
# the SDK's canonical names. ~/.config/dwave/dwave.conf remains the
# file-based alternative. Empty resolves identically to unset.
#
# DWAVE_API_TOKEN is the name the SDK and the miner actually read;
# DWAVE_API_KEY is accepted here only so existing .env files keep
# working. Nothing reads DWAVE_API_KEY itself.
DWAVE_API_TOKEN: ${DWAVE_API_TOKEN:-${DWAVE_API_KEY:-}}
# Pin the solver. Without it the SDK picks whatever the account defaults
# to, which may not be the Advantage2 system the chain topology targets.
DWAVE_API_SOLVER: ${DWAVE_API_SOLVER:-}
DWAVE_API_REGION: ${DWAVE_API_REGION:-}
# When a host MPS daemon is up, cap this client's GPU SMs at the operator's
# configured utilization (the manager writes QUIP_GPU_UTILIZATION to .env).
# NVIDIA runtime vars — no config-file equivalent exists for these.
CUDA_MPS_PIPE_DIRECTORY: /tmp/nvidia-mps
CUDA_MPS_ACTIVE_THREAD_PERCENTAGE: ${QUIP_GPU_UTILIZATION:-100}
volumes:
- ./data:/data
# First-run config template, mounted over the image's own. The image
# entrypoint seeds /data/config.toml from /app/config.toml when the file
# is missing, and the upstream template still names the retired testnet
# faucet. See config/quip-miner.toml.
- ./config/quip-miner.toml:/app/config.toml:ro
# MPS control pipe shared with the host daemon. Resolves to an empty dir
# (no MPS) on hosts without a running daemon — safe to mount regardless.
- /tmp/nvidia-mps:/tmp/nvidia-mps
networks:
default:
aliases:
- quip-miner
depends_on:
# service_healthy for the same reason as the cpu service above: the
# coordinator's preflight fails against a validator that is still syncing.
quip-validator:
condition: service_healthy
required: false
quip-syslog:
condition: service_started
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# Substrate-based block-producing validator (quip-validator). Bundled
# into both the cpu and cuda profiles by default — every operator runs a
# local validator. TLS is best-effort: with a real QUIP_HOSTNAME + reachable
# port 80 (or DNS-01 creds), Caddy fronts the RPC at wss://<host>/rpc. With
# the dev default (QUIP_HOSTNAME=:20049) the validator still runs but is only
# reachable on the compose network and via http://localhost:20049/rpc.
# Inbound 30333/tcp and 30333/udp must be reachable from the public internet
# for libp2p peering.
quip-validator:
# On CHANNEL like every other image. quip-network-node:stable now names
# the v0.3 Aglais line, so CHANNEL=stable no longer resolves to the
# retired v0.2.2 build (runtime spec 116) that cannot run the runtime 117
# genesis. This repo published an explicit pin here while that upstream
# tag lagged. `latest` is still pre-Aglais and is not a release pointer:
# do not name it. QUIP_VALIDATOR_TAG overrides CHANNEL.
image: registry.gitlab.com/quip.network/quip-validator/quip-network-node:${QUIP_VALIDATOR_TAG:-${CHANNEL:-beta}}
pull_policy: always
container_name: quip-validator
restart: unless-stopped
profiles:
- cpu
- cuda
environment: *runtime-user
logging: *default-logging
volumes:
# Validator base path (db, keystore, libp2p key). Aglais keeps the
# chain id `quip_testnet`, so its db lands in the same chains/quip_testnet
# subdir the retired testnet used. A fresh directory keeps the two apart;
# the old one stays at ./data/validator-data until the operator deletes it.
- ./data/aglais-chain-db:/data
# Chain spec selector. Defaults to the Aglais spec (the Quip test
# network). Override in .env to join a private network with its own
# spec. Local development uses `make localdev` (--chain=dev) instead.
- ${QUIP_CHAIN_SPEC:-./chain-specs/aglais-network.json}:/etc/quip/chain-spec.json:ro
# Sync gate for the miner and the dashboard. Mounted rather than baked in
# because the node image ships no HTTP client to probe its own RPC with.
- ./scripts/validator-healthcheck.sh:/usr/local/bin/validator-healthcheck:ro
ports:
- "30333:30333/tcp"
- "30333:30333/udp"
networks:
default:
aliases:
- quip-validator
depends_on:
quip-syslog:
condition: service_started
command:
# Bootnodes come from the chain spec's bootNodes array. Override per
# deployment by adding additional `--bootnodes=<multiaddr>` entries
# via a docker-compose.override.yml. Compose's env-var expansion
# can't split a space-separated env var into multiple argv tokens,
# so SUBSTRATE_BOOTNODES isn't wired here.
- --chain=/etc/quip/chain-spec.json
- --base-path=/data
- --name=${VALIDATOR_NAME:-quip-validator}
- --validator
# Full archive node: keep every state trie + block body so historical RPC
# (e.g. the dashboard descriptor worker scanning from genesis) works
# without hitting "State already discarded" past the default 256-block
# pruning window. Operators who want to reclaim disk can override these
# via docker-compose.override.yml.
- --state-pruning=archive
- --blocks-pruning=archive
- --rpc-port=9944
- --unsafe-rpc-external
- --rpc-cors=*
- --rpc-methods=safe
- --prometheus-port=9615
- --prometheus-external
- --no-mdns
# Auto-generate the libp2p node identity on first boot. Required for
# joiner / observer nodes that don't have a published peer id (substrate
# otherwise refuses to start with NetworkKeyNotFound when --validator
# is set). Bootnode operators replace this with --node-key-file=/data/node-key
# per docs/testnet-deployment.md so their peer id matches the chain spec.
- --unsafe-force-node-key-generation
# Healthy means synced to the chain head. Initial sync of the live testnet
# replays every block under the archive pruning set above, so retries x
# interval has to cover hours, not minutes: docker marks a container
# unhealthy for good once retries are exhausted, and the gated services
# would never start. start_period keeps those early failures from counting.
healthcheck:
test: ["CMD", "validator-healthcheck"]
interval: 30s
timeout: 10s
retries: 5
start_period: 24h
# Optional faucet sidecar. Layered additively on top of a validator profile:
# docker compose --profile cpu --profile faucet up -d
# See https://gitlab.com/quip.network/faucet for the upstream image.
quip-faucet:
image: registry.gitlab.com/quip.network/faucet:${QUIP_FAUCET_TAG:-${CHANNEL:-beta}}
pull_policy: always
container_name: quip-faucet
restart: unless-stopped
profiles:
- faucet
logging: *default-logging
environment:
<<: *runtime-user
QUIP_FAUCET_ALLOW_ANY_CHAIN: ${QUIP_FAUCET_ALLOW_ANY_CHAIN:-0}
networks:
default:
aliases:
- quip-faucet
depends_on:
# The faucet signs and submits balance transfers, which a validator that
# is still catching up cannot process against the current runtime. An
# operator who points QUIP_FAUCET_NODE_URL at a remote node still waits
# for the local validator here; drop this dependency in an override if
# that wait is not wanted.
quip-validator:
condition: service_healthy
required: false
quip-syslog:
condition: service_started
command:
- --node-url=${QUIP_FAUCET_NODE_URL:-ws://quip-validator:9944}
- --faucet-key=${QUIP_FAUCET_KEY:-//Alice}
- --listen-host=0.0.0.0
- --port=8087
- --rate-limit-seconds=${QUIP_FAUCET_RATE_LIMIT_SECONDS:-60}
dashboard:
<<: *dashboard
profiles:
- cpu
- cuda
postgres:
image: postgres:16
container_name: quip-postgres
restart: unless-stopped
profiles:
- cpu
- cuda
logging: *default-logging
environment:
POSTGRES_DB: ${POSTGRES_DB:-quip}
POSTGRES_USER: ${POSTGRES_USER:-quip}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-quip}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-quip} -d ${POSTGRES_DB:-quip}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
depends_on:
quip-syslog:
condition: service_started
caddy:
image: caddy:2-alpine
pull_policy: always
container_name: quip-caddy
restart: unless-stopped
profiles:
- cpu
- cuda
logging: *default-logging
environment:
QUIP_HOSTNAME: ${QUIP_HOSTNAME:-:20049}
# ACME registration email. Operators set CERT_EMAIL in .env before
# exposing the site over TLS; the placeholder default keeps Caddy
# parseable in dev mode where no ACME runs.
CERT_EMAIL: ${CERT_EMAIL:-hostmaster@localhost}
ZEROSSL_API_KEY: ${ZEROSSL_API_KEY:-}
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
ports:
# 20049 is the public API port (telemetry + substrate RPC + dashboard).
# 80/443 are the canonical TLS pair when QUIP_HOSTNAME is a real DNS
# name; Caddy auto-redirects :80 -> :443 and provisions a Let's Encrypt
# cert that covers both :443 and :20049.
- "20049:20049"
- "80:80"
- "443:443"
# The dashboard self-discovers through ws://quip-caddy:8088/rpc. An
# explicit alias — not container_name DNS — so the localdev override's
# renamed containers still resolve.
networks:
default:
aliases:
- quip-caddy
depends_on:
dashboard:
condition: service_started
quip-syslog:
condition: service_started
# Merged log collector. Receives every service's stdout through the Docker
# syslog driver and writes one file to data/logs/quip-node.log.
#
# This service deliberately does NOT use *default-logging: pointing the
# collector's own driver at the collector is a feedback loop. It stays on
# json-file so its startup errors remain visible in `docker compose logs`.
quip-syslog:
image: linuxserver/syslog-ng:${QUIP_SYSLOG_TAG:-4.11.0}
pull_policy: always
container_name: quip-syslog
restart: unless-stopped
profiles:
- cpu
- cuda
- faucet
environment:
<<: *runtime-user
# Rotation, matching the v0.1 miner: 10MB x 5. The supervisor checks the
# size every QUIP_LOG_CHECK_INTERVAL seconds, so the file can overshoot
# by one interval's worth of output before it rotates.
QUIP_LOG_MAX_BYTES: ${QUIP_LOG_MAX_BYTES:-10485760}
QUIP_LOG_KEEP: ${QUIP_LOG_KEEP:-5}
QUIP_LOG_CHECK_INTERVAL: ${QUIP_LOG_CHECK_INTERVAL:-30}
logging:
driver: json-file
options:
max-size: 8m
max-file: "2"
ports:
# Loopback only. The daemon reaches this from the host; nothing off-box
# should be able to inject lines into the operator's log. The host side
# is overridable (QUIP_LOG_PORT) so a bind collision on 5514 does not
# abort `docker compose up` for the whole stack -- the container side
# stays fixed at 5514 to match syslog-ng.conf and the test fixture.
- "127.0.0.1:${QUIP_LOG_PORT:-5514}:5514/udp"
volumes:
- ./syslog-ng/syslog-ng.conf:/config/syslog-ng.conf:ro
- ./syslog-ng/entrypoint.sh:/entrypoint.sh:ro
- ./data/logs:/logs
# This override bypasses the image's s6 supervisor, so crond does not run.
# entrypoint.sh supervises syslog-ng and handles rotation itself.
entrypoint: ["/entrypoint.sh"]
volumes:
# Renamed from quip-pgdata at the Aglais relaunch. The dashboard indexer
# scans from genesis and keys nothing by chain, so reusing the retired
# network's volume leaves its blocks and miners in the tables next to
# Aglais data. A new name gives every operator a clean index on upgrade
# and leaves the old volume on disk to delete when they are ready.
pgdata:
name: aglais-pgdata
caddy-data:
name: quip-caddy-data
caddy-config:
name: quip-caddy-config