-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
504 lines (492 loc) · 24.2 KB
/
Copy pathdocker-compose.yml
File metadata and controls
504 lines (492 loc) · 24.2 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
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# Pin the Compose project name so the stack is always "pithead" — its images, network and
# volumes are prefixed `pithead*` regardless of the checkout directory's name. Without this,
# Compose derives the project from the directory, which left older checkouts named after the
# repo's previous name. `pithead up`/`apply`/`upgrade` migrate an old-named stack automatically.
name: pithead
x-logging: &default-logging
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
services:
# --- Tor Anonymity Service ---
tor:
# A source checkout builds the 5 first-party images locally from build/; a release install pulls
# these published images instead. pithead chooses build-vs-pull via its `--pull` policy and sets
# PITHEAD_REGISTRY / STACK_VERSION (export_build_provenance, #44); the defaults below keep a bare
# `docker compose` working (tag `:dev`).
image: ${PITHEAD_REGISTRY:-ghcr.io/p2pool-starter-stack}/pithead-tor:${STACK_VERSION:-dev}
build: ./build/tor
container_name: tor
# Memory ceiling (#132 — see monerod). Generous so an OOM-restart, which would blip every onion
# service, stays very unlikely (~0.07 GiB observed).
mem_limit: 512m
memswap_limit: 512m
restart: unless-stopped
logging: *default-logging
environment:
# Subnet prefix (#180): the entrypoint envsubst's it into the rendered torrc's bridge IPs.
- NETWORK_PREFIX=${NETWORK_PREFIX:-172.28.0}
volumes:
- ${TOR_DATA_DIR}:/var/lib/tor
networks:
mining_net:
ipv4_address: ${NETWORK_PREFIX:-172.28.0}.25
# Healthy only once Tor has finished bootstrapping (circuits built), not merely
# when the SOCKS port is open — so tari/monerod don't start against a Tor that
# can't route yet. start_period covers a cold bootstrap without counting failures.
healthcheck:
test: ["CMD", "/usr/local/bin/tor-healthcheck.sh"]
interval: 10s
timeout: 10s
retries: 5
start_period: 90s
# --- Monero Daemon (monerod) ---
# Only starts if "local_node" profile is active
monerod:
profiles: ["local_node"]
image: ${PITHEAD_REGISTRY:-ghcr.io/p2pool-starter-stack}/pithead-monero:${STACK_VERSION:-dev}
build: ./build/monero
container_name: monerod
# Memory CEILINGS (#132): cap each service so a leak/spike OOM-restarts the offender in its own
# cgroup instead of the kernel's host-wide OOM-killer picking a victim (e.g. monerod, the revenue
# service). These are ceilings, NOT reservations — steady-state is far lower (observed on a synced
# 32 GB host: monerod ~0.3 GiB RSS since its LMDB is reclaimable page cache; dashboard ~0.06;
# p2pool ~0.35). memswap_limit == mem_limit => zero swap => clean OOM-kill, no disk thrash (same as
# Tari). monerod's ceiling is generous + tunable via monero.mem_limit so heavy initial-sync
# verification never trips it — raise it if a low-RAM host OOMs monerod during IBD.
mem_limit: ${MONERO_MEM_LIMIT:-6g}
memswap_limit: ${MONERO_MEM_LIMIT:-6g}
restart: unless-stopped
stop_grace_period: 1m
logging: *default-logging
environment:
- MONERO_NODE_USERNAME=${MONERO_NODE_USERNAME}
- MONERO_NODE_PASSWORD=${MONERO_NODE_PASSWORD}
- MONERO_ONION_ADDRESS=${MONERO_ONION_ADDRESS}
- MONERO_PRUNE=${MONERO_PRUNE}
# Optional clearnet initial sync (#183): default off. When true the entrypoint strips the Tor
# P2P proxy from the rendered config so IBD runs over clearnet (tx broadcast stays on Tor).
- MONERO_CLEARNET_SYNC=${MONERO_CLEARNET_SYNC:-false}
- MONERO_PREP_THREADS=${MONERO_PREP_THREADS}
# Subnet prefix (#180): the entrypoint envsubst's it into the Tor SOCKS IP in bitmonero.conf.
- NETWORK_PREFIX=${NETWORK_PREFIX:-172.28.0}
volumes:
- ${MONERO_DATA_DIR}:/root/.bitmonero
- /dev/hugepages:/dev/hugepages
- ./build/monero/bitmonero.conf.template:/root/bitmonero.conf.template:ro
# Read-only view of the shared clearnet-state dir (#234): the entrypoint reads the
# dashboard-written "sync complete" marker to decide clearnet-vs-Tor at startup.
- ${CLEARNET_STATE_DIR:-./data/clearnet-state}:/clearnet-state:ro
ports:
# Default 127.0.0.1 = host-local only (no LAN exposure); p2pool reaches monerod over
# the internal Docker network regardless. Set monero.rpc_lan_access:true in config.json
# to publish on the LAN (0.0.0.0) for wallets on other machines.
- "${MONERO_RPC_BIND:-127.0.0.1}:18081:18081"
networks:
mining_net:
ipv4_address: ${NETWORK_PREFIX:-172.28.0}.26
depends_on:
tor:
condition: service_healthy
healthcheck:
# The RPC username/password are read from the container's env inside the script, NOT
# interpolated into the test command — so they no longer leak via `docker inspect` (#90).
test: ["CMD", "/usr/local/bin/monerod-healthcheck.sh"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
# --- Tari Base Node ---
tari:
# Pinned by digest (#135) so the v5.3.1-mainnet tag can't be silently re-pointed.
image: quay.io/tarilabs/minotari_node:v5.3.1-mainnet@sha256:824fd6ec21d618805317d7eede374d6782906eeae17d2fc8aaad4df6205f94e0
container_name: tari
restart: unless-stopped
stop_grace_period: 1m
logging: *default-logging
# Tari's memory grows unbounded over time (#55, first reported in #14); left uncapped it can
# pressure or OOM the whole host on small (e.g. 16 GB) machines. Cap it as a SAFETY CEILING so a
# runaway OOM-restarts cleanly instead of dragging the stack down — monerod/p2pool keep mining
# throughout. These are the legacy keys on purpose: deploy.resources has no swap field, and
# memswap_limit equal to mem_limit means zero swap => clean OOM-kill, no disk thrash. (Capping
# swap needs swapaccount=1 on older cgroup-v1 hosts; cgroup-v2 honors it by default.) pithead
# sets TARI_MEM_LIMIT from tari.mem_limit — "auto" gives Tari most of RAM after subtracting the
# ~6 GB RandomX HugePages reservation and a reserve for the rest of the stack. The :-7680m
# fallback only applies to a stale .env that predates this and hasn't been re-rendered (=> a safe
# value for a 16 GB host with HugePages on, until the next `pithead apply`).
mem_limit: ${TARI_MEM_LIMIT:-7680m}
memswap_limit: ${TARI_MEM_LIMIT:-7680m}
# Tari needs NO clearnet DNS: dns_seeds = [] (config.toml), onion peer_seeds resolve via Tor, and
# the Tor SOCKS is reached by IP (this container already overrode Docker's 127.0.0.11, so nothing
# here depends on service discovery). The one OS-resolver lookup left is the Tari Pulse service's
# checkpoints.tari.com TXT (~every 120 s) — an ADVISORY deep-reorg check, no in-binary off-switch,
# that TOLERATES a DNS failure (returns "passed", verified in tari_pulse_service/mod.rs). Point the
# resolver at a dead local address so that lookup fails WITHOUT a packet leaving the host — zero
# clearnet DNS, no functional impact (#162; #160 decision-4). Trade-off: lose the Pulse deep-reorg
# advisory, the same class as monerod's disable-dns-checkpoints (#161).
dns:
- 127.0.0.1
environment:
- WAIT_FOR_TOR=1
# Optional clearnet initial sync (#183/#234): the wrapper entrypoint reads this flag and the
# shared marker to decide clearnet-vs-Tor per start. Surfaced here so a flag change recreates
# tari (a bind-mount content change alone won't trigger a recreate).
- TARI_CLEARNET_SYNC=${TARI_CLEARNET_SYNC:-false}
volumes:
- ${TARI_DATA_DIR}:/var/tari/node
- ./build/tari:/var/tari/config
# Read-only view of the shared clearnet-state dir (#234): the wrapper entrypoint reads the
# dashboard-written "sync complete" marker to decide clearnet-vs-Tor at startup.
- ${CLEARNET_STATE_DIR:-./data/clearnet-state}:/clearnet-state:ro
# Pithead wrapper (#183/#234): renders the runtime config (clearnet-vs-Tor, marker-gated) from
# pithead's canonical Tor config, then chains to the upstream start_tari_app.sh unchanged.
entrypoint:
- /var/tari/config/entrypoint.sh
command:
- --disable-splash-screen
- --non-interactive
networks:
mining_net:
ipv4_address: ${NETWORK_PREFIX:-172.28.0}.27
depends_on:
tor:
condition: service_healthy
healthcheck:
# Liveness only — deliberately NOT a sync/gRPC check: that would block p2pool from ever
# starting while Tari is down or still syncing, breaking "Tari is optional" (#35/#51). The
# '[m]' bracket stops grep matching its own argv — the old `grep minotari_node` matched
# itself, so the check reported "healthy" even when the daemon had died.
test: ["CMD-SHELL", "ps | grep '[m]inotari_node' || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 90s # survive the post-OOM DB integrity check without flapping unhealthy
# --- P2Pool Node ---
p2pool:
image: ${PITHEAD_REGISTRY:-ghcr.io/p2pool-starter-stack}/pithead-p2pool:${STACK_VERSION:-dev}
build: ./build/p2pool
container_name: p2pool
# Memory ceiling (#132 — see monerod). RandomX HugePages are separate; this caps p2pool's own
# heap (~0.35 GiB observed).
mem_limit: 1g
memswap_limit: 1g
restart: unless-stopped
logging: *default-logging
# Replaces 'privileged: true'. p2pool only needs to lock HugePages for the
# RandomX dataset (IPC_LOCK) and raise thread priority (SYS_NICE).
cap_add:
- IPC_LOCK
- SYS_NICE
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- ${P2POOL_DATA_DIR}:/root
- ${P2POOL_DATA_DIR}/stats:/stats
- /dev/hugepages:/dev/hugepages
command:
- --host
- ${MONERO_NODE_HOST} # Dynamic Host
- --rpc-port
- "${MONERO_RPC_PORT}"
- --rpc-login
- "${MONERO_NODE_USERNAME}:${MONERO_NODE_PASSWORD}" # Only used if auth required
- --zmq-port
- "${MONERO_ZMQ_PORT}"
- --wallet
- ${MONERO_WALLET_ADDRESS}
- --merge-mine
- tari://${NETWORK_PREFIX:-172.28.0}.27:18142
- ${TARI_WALLET_ADDRESS}
- --onion-address
- ${P2POOL_ONION_ADDRESS}
- --local-api
- --stratum
- 0.0.0.0:3333
- --p2p
- 0.0.0.0:${P2POOL_PORT}
- --data-api
- /stats
# P2POOL_FLAGS (pool type + the #165 Tor SOCKS routing) is passed via the environment, not as a
# `- ${P2POOL_FLAGS}` command item: Compose passes such an item as a SINGLE argument (no
# word-splitting), which mangles a multi-flag value. The entrypoint word-splits this env var.
environment:
- P2POOL_FLAGS=${P2POOL_FLAGS:-}
networks:
mining_net:
ipv4_address: ${NETWORK_PREFIX:-172.28.0}.28
depends_on:
# monerod dependency removed to allow remote node operation
tari:
condition: service_healthy
healthcheck:
# Liveness for the core revenue service (#90). Probe the stratum listener directly: a TCP
# connect to :3333 proves p2pool is up and accepting miners, which is the failure miners
# actually feel. /dev/tcp is a bash builtin (the image ships bash) so it needs no extra
# tooling. Surfaced in `pithead status` / the dashboard; it does NOT gate startup or
# auto-restart — xmrig-proxy still depends on p2pool only being *started*.
test: ["CMD", "/usr/local/bin/p2pool-healthcheck.sh"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
# --- XMRig Proxy ---
xmrig-proxy:
image: ${PITHEAD_REGISTRY:-ghcr.io/p2pool-starter-stack}/pithead-xmrig-proxy:${STACK_VERSION:-dev}
build: ./build/xmrig-proxy
container_name: xmrig-proxy
# Memory ceiling (#132 — see monerod). Tiny in practice (~2 MiB observed).
mem_limit: 512m
memswap_limit: 512m
restart: unless-stopped
logging: *default-logging
# Defense-in-depth: this leaf service never needs extra privileges or Linux capabilities
# (it only listens on :3333/:3344 and forwards to p2pool). Drop them all (#90).
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
ports:
# The stratum endpoint your rigs connect to. Published on 0.0.0.0 (all interfaces) by
# default so LAN workers can reach it; set p2pool.stratum_bind in config.json to a specific
# address (e.g. your LAN IP, or 127.0.0.1 to disable LAN access) to narrow exposure on a
# public-IP host. Either way, firewall :3333 to your LAN — see docs/workers.md.
- "${STRATUM_BIND:-0.0.0.0}:3333:3333"
environment:
- PROXY_API_PORT=${PROXY_API_PORT}
- PROXY_AUTH_TOKEN=${PROXY_AUTH_TOKEN}
command:
- -o
- ${P2POOL_URL}
- -u
- ${MONERO_WALLET_ADDRESS}
- -b
- 0.0.0.0:3333
- -m
- simple
- --coin
- monero
- --verbose
- --http-host
- 0.0.0.0
- --http-port
- "${PROXY_API_PORT:?PROXY_API_PORT is empty; run 'pithead apply' (#153)}"
# Fail closed (#153): this control API is writable (--http-no-restricted, required for XvB
# pool-switching) and reachable on the mining_net bridge + the host, so it must NEVER run
# unauthenticated. ':?' makes the stack REFUSE TO START — instead of exposing an open API —
# if a hand-edited or pre-token stale .env left PROXY_AUTH_TOKEN empty (note 'pithead up'
# does not re-render .env, only setup/apply do). pithead auto-generates the token on apply.
- --http-access-token
- "${PROXY_AUTH_TOKEN:?PROXY_AUTH_TOKEN is empty; refusing to start an unauthenticated xmrig-proxy control API. Run 'pithead apply' to generate one (#153)}"
- --http-no-restricted
# Optional miner authentication on the stratum port (#152). When p2pool.stratum_password is set,
# xmrig-proxy rejects rigs whose stratum 'pass' doesn't match — so only devices that know the
# secret can mine through the proxy (this also shrinks the #122 SSRF surface). The ':+' form is
# deliberate: when the password is SET this expands to the whole '--access-password=<secret>'
# flag; when it's EMPTY (the default) it expands to an empty token that xmrig-proxy ignores —
# i.e. NO flag, so any rig may mine, preserving today's open behavior. (A literal
# '--access-password=' with an empty value would instead demand an *empty* password and reject
# every normal rig — verified against xmrig-proxy 6.26.0.) Cleartext over stratum — pair with a
# LAN-only stratum_bind / firewall; this is access control, not encryption.
- "${PROXY_STRATUM_PASSWORD:+--access-password=$PROXY_STRATUM_PASSWORD}"
# xmrig-proxy dev-fee donation level (#173). Rendered from proxy.donate_level; xmrig-proxy's
# own default is 0% (no fee), which we now pass EXPLICITLY so the effective level is visible
# and operator-controllable instead of an invisible compiled-in default. NOT the XvB donation.
- --donate-level=${PROXY_DONATE_LEVEL:-0}
networks:
mining_net:
ipv4_address: ${NETWORK_PREFIX:-172.28.0}.29
depends_on:
- p2pool
# --- Monitoring Dashboard ---
dashboard:
image: ${PITHEAD_REGISTRY:-ghcr.io/p2pool-starter-stack}/pithead-dashboard:${STACK_VERSION:-dev}
build:
context: ./build/dashboard
# Stack version baked into the image so the dashboard header can show it (Issue #58).
# pithead exports these for the build (PITHEAD_VERSION from the top-level VERSION file;
# git branch/commit for dev builds — see export_build_provenance); empty defaults keep a
# bare `docker compose build` working (the badge then falls back to a generic dev indicator).
args:
PITHEAD_VERSION: ${PITHEAD_VERSION:-}
PITHEAD_GIT_COMMIT: ${PITHEAD_GIT_COMMIT:-}
PITHEAD_GIT_BRANCH: ${PITHEAD_GIT_BRANCH:-}
PITHEAD_RELEASE: ${PITHEAD_RELEASE:-}
container_name: dashboard
# Memory ceiling (#132 — see monerod). A small Flask + SQLite app (~0.06 GiB observed); a tight
# cap is pure upside — a memory regression OOM-restarts the dashboard, not the revenue services.
mem_limit: 512m
memswap_limit: 512m
restart: unless-stopped
logging: *default-logging
network_mode: "host"
# Hardened with no-new-privileges (#90). Deliberately NOT cap_drop: [ALL] like the other
# leaf services: the dashboard runs as root and persists its SQLite history into the
# ${DASHBOARD_DATA_DIR} bind mount, which pithead creates owned by the host user. Dropping
# all capabilities strips CAP_DAC_OVERRIDE, so root could no longer create the SQLite
# WAL/journal files in that user-owned directory and history writes would fail.
security_opt:
- no-new-privileges:true
volumes:
- ${P2POOL_DATA_DIR}/stats:/app/stats:ro
- ${DASHBOARD_DATA_DIR}:/data
# Read-WRITE: the supervisor drops the per-chain "clearnet sync complete" marker here once a
# node finishes a clearnet initial sync, then restarts it onto Tor (#234). monerod/tari mount
# the same dir read-only. Holds only non-secret state markers.
- ${CLEARNET_STATE_DIR:-./data/clearnet-state}:/clearnet-state
environment:
- HOST_IP=${HOST_IP:-127.0.0.1}
# Timezone for dashboard timestamps/charts. Set via `dashboard.timezone` in config.json
# (`auto` = the host's timezone, auto-detected); pithead renders it to DASHBOARD_TZ in
# .env. The Etc/UTC here is only a last-resort fallback if DASHBOARD_TZ is unset.
- TZ=${DASHBOARD_TZ:-Etc/UTC}
- MONERO_NODE_HOST=${MONERO_NODE_HOST:-172.28.0.26}
# monerod get_info RPC creds (Issue #29): the dashboard reads sync height/target over
# 127.0.0.1:18081 (reachable via network_mode: host) using digest auth, instead of
# scraping docker logs. Only used for a local node; harmless for remote-node setups.
- MONERO_NODE_USERNAME=${MONERO_NODE_USERNAME}
- MONERO_NODE_PASSWORD=${MONERO_NODE_PASSWORD}
# Whether the local node prunes — shown as Pruned/Full in the Monero panel (Issue #32).
- MONERO_PRUNE=${MONERO_PRUNE:-true}
# Clearnet initial sync (#183/#234): the supervisor watches these flags and, once a clearnet
# node is synced, drops a marker in /clearnet-state and restarts it back onto Tor.
- MONERO_CLEARNET_SYNC=${MONERO_CLEARNET_SYNC:-false}
- TARI_CLEARNET_SYNC=${TARI_CLEARNET_SYNC:-false}
- CLEARNET_STATE_DIR=/clearnet-state
- P2POOL_URL=${P2POOL_URL}
- MONERO_WALLET_ADDRESS=${MONERO_WALLET_ADDRESS}
- XVB_ENABLED=${XVB_ENABLED}
- XVB_POOL_URL=${XVB_POOL_URL}
- XVB_DONOR_ID=${XVB_DONOR_ID}
# Route XvB donation mining over Tor by default (#166); xvb.tor:false opts out.
- XVB_TOR_ENABLED=${XVB_TOR_ENABLED:-true}
- XVB_DONATION_LEVEL=${XVB_DONATION_LEVEL:-auto}
- PROXY_HOST=${NETWORK_PREFIX:-172.28.0}.29
- PROXY_API_PORT=${PROXY_API_PORT}
- PROXY_AUTH_TOKEN=${PROXY_AUTH_TOKEN}
- DOCKER_PROXY_URL=tcp://${NETWORK_PREFIX:-172.28.0}.30:2375
- DOCKER_CONTROL_URL=tcp://${NETWORK_PREFIX:-172.28.0}.31:2375
- TARI_GRPC_ADDRESS=${NETWORK_PREFIX:-172.28.0}.27:18142
# Local monerod bridge IP (local-vs-remote detection), the internal SSRF-guard CIDR, and the
# Tor SOCKS endpoint all track the configured subnet prefix (#180).
- LOCAL_MONERO_HOST=${NETWORK_PREFIX:-172.28.0}.26
- MINING_NET_CIDR=${NETWORK_SUBNET:-172.28.0.0/24}
- XVB_TOR_PROXY=socks5h://${NETWORK_PREFIX:-172.28.0}.25:9050
# Notify-only new-release check (#224, config.json: dashboard.check_for_updates). Default ON;
# the dashboard checks GitHub for a newer release and shows a link badge — routed over the same
# bridge Tor SOCKS as XVB_TOR_PROXY so it leaks neither the host IP nor a DNS lookup (safe to
# default on). Set dashboard.check_for_updates:false to opt out. See docs/privacy.md.
- DASHBOARD_CHECK_UPDATES=${DASHBOARD_CHECK_UPDATES:-true}
# monerod is required: a monerod outage always rejects workers (Issue #31) and the miner
# is always held until monerod syncs (Issue #35) — not configurable. Tari is optional:
# TARI_REQUIRED (default true) decides whether a Tari outage rejects workers, whether the
# miner waits for Tari's sync, and whether a Tari-only sync takes over the dashboard
# (Issue #51). Set dashboard.tari_required:false to make Tari non-blocking.
- TARI_REQUIRED=${TARI_REQUIRED:-true}
# --- Docker Socket Proxy (read-only) ---
# Read-only window onto the Docker API for the dashboard's container stats/logs.
# POST stays off, so this proxy can never write — container control goes through the
# separate, tightly-scoped docker-control proxy below.
docker-proxy:
image: tecnativa/docker-socket-proxy:v0.4.2@sha256:1f3a6f303320723d199d2316a3e82b2e2685d86c275d5e3deeaf182573b47476
container_name: docker-proxy
# Memory ceiling (#132 — see monerod). A tiny socket proxy (~13 MiB observed).
mem_limit: 128m
memswap_limit: 128m
restart: unless-stopped
logging: *default-logging
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# Immutable root filesystem (#90): HAProxy reads its config and the (read-only) Docker socket
# and writes nothing persistent — only ephemeral scratch is needed.
read_only: true
tmpfs:
- /run
- /tmp
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- CONTAINERS=1
- LOGS=1
networks:
mining_net:
ipv4_address: ${NETWORK_PREFIX:-172.28.0}.30
# --- Docker Control Proxy (start/stop only) ---
# A second, minimal proxy used ONLY to stop/start p2pool + xmrig-proxy: node-down worker
# failover (Issue #31) and holding the miner until the nodes finish syncing (Issue #35).
# With POST=1 but every section (CONTAINERS, EXEC, IMAGES, …) left off, the v0.4.2 ruleset
# allows exactly `POST /containers/<id>/{start,stop}` and denies everything else
# (create/kill/exec/reads). Kept separate from the read-only proxy above so opening POST
# here can't widen that proxy's container access.
docker-control:
image: tecnativa/docker-socket-proxy:v0.4.2@sha256:1f3a6f303320723d199d2316a3e82b2e2685d86c275d5e3deeaf182573b47476
container_name: docker-control
# Memory ceiling (#132 — see monerod). A tiny socket proxy (~13 MiB observed).
mem_limit: 128m
memswap_limit: 128m
restart: unless-stopped
logging: *default-logging
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# Immutable root filesystem (#90): HAProxy reads its config and the (read-only) Docker socket
# and writes nothing persistent — only ephemeral scratch is needed.
read_only: true
tmpfs:
- /run
- /tmp
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- POST=1
- ALLOW_START=1
- ALLOW_STOP=1
networks:
mining_net:
ipv4_address: ${NETWORK_PREFIX:-172.28.0}.31
# --- Caddy Reverse Proxy ---
# Bridges the local 127.0.0.1 application binding to the LAN
# Serves HTTPS or HTTP depending on configuration
caddy:
image: caddy:2.11.4@sha256:cfeb0b281bc44a5a51fecde39e9e577c60d863c0b6196e6bbdf58fd00960887f
container_name: caddy
# Memory ceiling (#132 — see monerod). Reverse proxy + local TLS only (~13 MiB observed).
mem_limit: 128m
memswap_limit: 128m
restart: unless-stopped
logging: *default-logging
network_mode: "host"
# Drop all capabilities, then add back only NET_BIND_SERVICE — Caddy serves the dashboard on
# :80/:443 (privileged ports) and would otherwise fail to bind once ALL caps are dropped (#90).
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# Immutable root filesystem (#90). Persistent state (the internal CA + issued certs) lives in
# the caddy_data volume; the only other writes are Caddy's config autosave (/config) and temp
# scratch (/tmp), which are ephemeral tmpfs. Nothing else on the rootfs can be modified.
read_only: true
tmpfs:
- /tmp
- /config
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
# --- Volumes ---
volumes:
caddy_data:
# --- Network Configuration ---
networks:
mining_net:
driver: bridge
name: mining_net
ipam:
config:
- subnet: ${NETWORK_SUBNET:-172.28.0.0/24}