Skip to content

Commit dc0cd9c

Browse files
committed
deploy: docker-compose stack for the three simplepool services
Adds deploy/docker/ with multi-stage Dockerfiles for the C stratum proxy and the two Node services, plus a compose file that orchestrates all three against a Thunder daemon + bitcoind that live on the host. Design: - simplepool runs on host networking so miners hit :3334 without NAT and bitcoind_url in proxy.conf works unchanged from the bare-metal deploy. - dashboard + payout run on the default bridge and reach Thunder via host.docker.internal (extra_hosts maps to host-gateway on Linux; native on macOS/Windows). - Shared ../../data bind mount so all three see the same SQLite file at /data/shares.db. - Operator configures the stack via .env (env vars) + proxy.conf (bind-mounted read-only into the simplepool container). Not containerized: bitcoind, Thunder, bip300301_enforcer, electrs — the drivechain infrastructure has its own lifecycle and stays bare-metal. Smoke-tested locally: all three images build; simplepool binary loads and fails cleanly on missing config; better-sqlite3 native binding loads in the dashboard image; payout worker's env-var guard fires as expected. README gets a short subsection under "Deploy to a server" pointing at deploy/docker/README.md for the walkthrough.
1 parent 3085b73 commit dc0cd9c

8 files changed

Lines changed: 363 additions & 0 deletions

File tree

.dockerignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# -----------------------------------------------------------------------------
2+
# Keep the docker build context small. Docker reads this from the context
3+
# root (the repo root, per docker-compose.yml `context: ../..`).
4+
# -----------------------------------------------------------------------------
5+
6+
# host-only build outputs — the image builds fresh from source
7+
build/
8+
**/node_modules/
9+
10+
# runtime data lives in a bind-mounted volume, never in the image
11+
data/
12+
.regtest/
13+
14+
# operator secrets and local notes — never bake into an image
15+
proxy.conf
16+
FORKNET_CHEATSHEET.md
17+
memory.md
18+
*.pre-reconcile-*
19+
*.pre-restart-*
20+
21+
# vcs & editor cruft
22+
.git/
23+
.gitignore
24+
.DS_Store
25+
26+
# docs — not needed at runtime
27+
*.md
28+
!dashboard/README.md
29+
!payout/README.md
30+
!deploy/docker/README.md

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,22 @@ To pull edits made directly on a server back into a local checkout (so
317317
you can commit + push from here), use
318318
[`scripts/sync-from-server.sh`](scripts/sync-from-server.sh).
319319

320+
### Docker
321+
322+
An alternative to the bare-metal script: containerized builds of the
323+
three services (stratum proxy, dashboard, payout worker) under
324+
[`deploy/docker/`](deploy/docker/). One `docker compose up -d --build`
325+
gets the whole app stack running against a Thunder daemon and Bitcoin
326+
Core that live on the host (or wherever you point them). Shared bind
327+
mount on `data/` so the SQLite ledger is portable across restarts /
328+
image rebuilds. See [`deploy/docker/README.md`](deploy/docker/README.md)
329+
for the full walkthrough.
330+
331+
Note: the drivechain infrastructure (`bitcoind`, Thunder, the
332+
`bip300301_enforcer`, `electrs`) is intentionally NOT containerized —
333+
those daemons have their own lifecycles and typically run bare-metal on
334+
the same host.
335+
320336
## Config keys
321337

322338
```

deploy/docker/.env.example

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -----------------------------------------------------------------------------
2+
# simplepool docker-compose configuration.
3+
# Copy to `.env` (same directory as docker-compose.yml) and edit.
4+
# -----------------------------------------------------------------------------
5+
6+
# Dashboard HTTP port on the host (container always listens on 8081).
7+
DASHBOARD_PORT=8081
8+
9+
# What miners see on the dashboard "Connect a miner" card.
10+
PUBLIC_STRATUM_URL=stratum+tcp://pool.example.com:3334
11+
12+
# Thunder RPC endpoint. host.docker.internal is the docker host on
13+
# Linux (needs extra_hosts, already set in the compose file) and native
14+
# on macOS/Windows. Use a hostname or IP if Thunder lives elsewhere.
15+
THUNDER_RPC_URL=http://host.docker.internal:6009
16+
17+
# Pool's Thunder reserve address. Must match pool_thunder_reserve_address
18+
# in proxy.conf — the address the coinbase deposits into (pps mode) or
19+
# the operator batches BTC into (pps-classic).
20+
POOL_THUNDER_RESERVE_ADDRESS=
21+
22+
# Sats-per-difficulty rate (must equal proxy.conf's pps_sats_per_diff).
23+
POOL_PPS_SATS_PER_DIFF=1000
24+
25+
# --- /admin panel — leave BOTH unset to disable; the route 503s ---
26+
# ADMIN_USER=admin
27+
# ADMIN_PASSWORD= # generate with: openssl rand -base64 21
28+
29+
# --- payout worker ---
30+
# Thunder address the payout worker spends from. Must equal
31+
# pool_thunder_reserve_address (same wallet the coinbase deposits to).
32+
THUNDER_FROM_ADDRESS=
33+
# Don't create payouts smaller than this (avoids fee-dominated txs).
34+
PAYOUT_MIN_SATS=10000
35+
# Reconciliation loop interval.
36+
PAYOUT_INTERVAL_MS=30000
37+
# Set to 1 to compute and log payouts without broadcasting them (safe first run).
38+
PAYOUT_DRY_RUN=0

deploy/docker/Dockerfile.dashboard

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -----------------------------------------------------------------------------
2+
# simplepool dashboard — read-only Express web UI (Node.js)
3+
# -----------------------------------------------------------------------------
4+
# `better-sqlite3` builds a native binding on `npm ci`; a slim build stage
5+
# pulls in python3/make/g++ for that, and the runtime stage carries only the
6+
# already-built node_modules plus the app source.
7+
# -----------------------------------------------------------------------------
8+
9+
FROM node:20-bookworm-slim AS deps
10+
RUN apt-get update && apt-get install -y --no-install-recommends \
11+
python3 make g++ ca-certificates \
12+
&& rm -rf /var/lib/apt/lists/*
13+
WORKDIR /app
14+
COPY dashboard/package.json dashboard/package-lock.json ./
15+
RUN npm ci --omit=dev
16+
17+
# -----------------------------------------------------------------------------
18+
FROM node:20-bookworm-slim
19+
RUN apt-get update && apt-get install -y --no-install-recommends tini \
20+
&& rm -rf /var/lib/apt/lists/*
21+
WORKDIR /app
22+
COPY --from=deps /app/node_modules ./node_modules
23+
COPY dashboard/ ./
24+
USER node
25+
EXPOSE 8081
26+
ENTRYPOINT ["/usr/bin/tini", "--", "node", "server.js"]

deploy/docker/Dockerfile.payout

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -----------------------------------------------------------------------------
2+
# simplepool payout worker — Thunder tx sender (Node.js)
3+
# -----------------------------------------------------------------------------
4+
5+
FROM node:20-bookworm-slim AS deps
6+
RUN apt-get update && apt-get install -y --no-install-recommends \
7+
python3 make g++ ca-certificates \
8+
&& rm -rf /var/lib/apt/lists/*
9+
WORKDIR /app
10+
COPY payout/package.json payout/package-lock.json ./
11+
RUN npm ci --omit=dev
12+
13+
# -----------------------------------------------------------------------------
14+
FROM node:20-bookworm-slim
15+
RUN apt-get update && apt-get install -y --no-install-recommends tini \
16+
&& rm -rf /var/lib/apt/lists/*
17+
WORKDIR /app
18+
COPY --from=deps /app/node_modules ./node_modules
19+
COPY payout/ ./
20+
USER node
21+
ENTRYPOINT ["/usr/bin/tini", "--", "node", "index.js"]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# -----------------------------------------------------------------------------
2+
# simplepool — Bitcoin stratum proxy (C11)
3+
# -----------------------------------------------------------------------------
4+
# Multi-stage: heavy build tools + -dev headers stay out of the runtime image.
5+
# Build context expected to be the repo root (see deploy/docker/docker-compose.yml).
6+
# -----------------------------------------------------------------------------
7+
8+
FROM debian:bookworm-slim AS build
9+
RUN apt-get update && apt-get install -y --no-install-recommends \
10+
build-essential \
11+
libsqlite3-dev \
12+
libcurl4-openssl-dev \
13+
libhiredis-dev \
14+
&& rm -rf /var/lib/apt/lists/*
15+
WORKDIR /src
16+
COPY Makefile ./
17+
COPY include/ ./include/
18+
COPY src/ ./src/
19+
# The Makefile does `include tests/*.mk` at the top level, so those files
20+
# must be present for make to parse — we only build the main binary target
21+
# though, not the tests themselves.
22+
COPY tests/ ./tests/
23+
RUN make -j"$(nproc)" build/simplepool
24+
25+
# -----------------------------------------------------------------------------
26+
FROM debian:bookworm-slim
27+
RUN apt-get update && apt-get install -y --no-install-recommends \
28+
libsqlite3-0 \
29+
libcurl4 \
30+
libhiredis0.14 \
31+
ca-certificates \
32+
tini \
33+
&& rm -rf /var/lib/apt/lists/* \
34+
&& groupadd -r -g 1000 simplepool \
35+
&& useradd -r -u 1000 -g simplepool -m -d /home/simplepool simplepool \
36+
&& mkdir -p /data && chown simplepool:simplepool /data
37+
38+
COPY --from=build /src/build/simplepool /usr/local/bin/simplepool
39+
40+
USER simplepool
41+
WORKDIR /home/simplepool
42+
EXPOSE 3334
43+
# `tini` reaps zombies and forwards SIGTERM so docker stop is clean.
44+
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/simplepool"]
45+
# Bind-mount proxy.conf to /etc/simplepool/proxy.conf (see docker-compose.yml).
46+
CMD ["/etc/simplepool/proxy.conf"]

deploy/docker/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# simplepool — docker deployment
2+
3+
Runs the three simplepool services (stratum proxy, dashboard, payout worker)
4+
against a Thunder daemon and a Bitcoin Core node that live outside this
5+
compose stack (typically on the same host).
6+
7+
## Prerequisites
8+
9+
- Docker Engine 24+ with the `compose` plugin
10+
- A running Bitcoin Core (mainnet / forknet) reachable from this host
11+
- A running Thunder daemon with JSON-RPC exposed on some host:port
12+
- Ports **3334** (stratum) and **8081** (dashboard, override via env) free
13+
14+
## First run
15+
16+
```bash
17+
cd deploy/docker
18+
19+
# 1. Configure the compose stack
20+
cp .env.example .env
21+
$EDITOR .env
22+
23+
# 2. Provide a proxy.conf. Start from the repo template and edit:
24+
cp ../../proxy.conf.example proxy.conf
25+
$EDITOR proxy.conf
26+
# IMPORTANT: inside the container the DB path must be /data/shares.db
27+
# set: db_path = /data/shares.db
28+
# bitcoind_url can stay 127.0.0.1 because simplepool runs in host network mode.
29+
30+
# 3. Build images and bring the stack up
31+
docker compose up -d --build
32+
33+
# 4. Watch the logs
34+
docker compose logs -f simplepool
35+
docker compose logs -f dashboard
36+
docker compose logs -f payout
37+
```
38+
39+
## Container-facing paths
40+
41+
Everything reads/writes the same SQLite file via a shared bind mount:
42+
43+
| host path | container path | who writes |
44+
|------------------------|-----------------------|-------------------------------|
45+
| `../../data/` | `/data/` | simplepool (RW), payout (RW), dashboard (RO) |
46+
| `./proxy.conf` | `/etc/simplepool/proxy.conf` | simplepool reads |
47+
48+
`../../data` is the repo's `data/` folder — same as the bare-metal deploy.
49+
50+
## Networking
51+
52+
- **simplepool** uses `network_mode: host` so miners hit `:3334` without NAT
53+
and so `bitcoind_url = http://127.0.0.1:8332` in `proxy.conf` works
54+
unchanged from bare-metal setups.
55+
- **dashboard** and **payout** use the default bridge; they reach Thunder via
56+
`host.docker.internal:6009` (which `extra_hosts` maps to the host gateway
57+
on Linux, native on macOS/Windows). If Thunder lives on a different box,
58+
set `THUNDER_RPC_URL` to a real hostname/IP.
59+
60+
## Updating after a code change
61+
62+
```bash
63+
git pull
64+
docker compose up -d --build
65+
```
66+
67+
Compose rebuilds only images whose sources changed (via layer cache) and
68+
restarts only the affected containers. Stratum drops connections on restart —
69+
miners auto-reconnect within seconds.
70+
71+
## Common operations
72+
73+
```bash
74+
# Stop the stack
75+
docker compose down
76+
77+
# Tail one service's log
78+
docker compose logs -f payout
79+
80+
# Get a shell in the running dashboard container
81+
docker compose exec dashboard sh
82+
83+
# Rebuild just one image after editing its Dockerfile
84+
docker compose build simplepool
85+
docker compose up -d simplepool
86+
87+
# Payout worker: do a dry-run cycle (no broadcast)
88+
PAYOUT_DRY_RUN=1 docker compose up payout
89+
```
90+
91+
## What this stack does NOT include
92+
93+
- **bitcoind / drivechain-forknet** — the mainchain node
94+
- **Thunder daemon** — the sidechain node
95+
- **bip300301_enforcer** — the BMM enforcer
96+
- **electrs / esplora** — mainchain indexer used by the enforcer
97+
98+
Those form the drivechain infrastructure below simplepool. Install them on
99+
the host (see the project's [INSTALL.md](../../INSTALL.md)) and point
100+
`bitcoind_url` in `proxy.conf` + `THUNDER_RPC_URL` in `.env` at them.

deploy/docker/docker-compose.yml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# -----------------------------------------------------------------------------
2+
# simplepool — docker-compose orchestration
3+
# -----------------------------------------------------------------------------
4+
# Runs the three simplepool services against a Thunder daemon that lives on
5+
# the host (or wherever THUNDER_RPC_URL points). Bitcoin Core is likewise
6+
# expected to be reachable from the simplepool container.
7+
#
8+
# Directory layout on the operator's box:
9+
# deploy/docker/
10+
# ├── docker-compose.yml (this file)
11+
# ├── .env (copied from .env.example, edited)
12+
# └── proxy.conf (copied from repo root proxy.conf.example)
13+
# data/ (created on first run — holds shares.db)
14+
#
15+
# Bring the stack up:
16+
# cd deploy/docker
17+
# cp .env.example .env && $EDITOR .env
18+
# cp ../../proxy.conf.example proxy.conf && $EDITOR proxy.conf
19+
# docker compose up -d --build
20+
#
21+
# The simplepool container uses host networking so miners can reach :3334
22+
# without NAT and so it can talk to bitcoind on 127.0.0.1. dashboard and
23+
# payout use the default bridge and reach Thunder via host.docker.internal.
24+
# -----------------------------------------------------------------------------
25+
26+
services:
27+
simplepool:
28+
build:
29+
context: ../..
30+
dockerfile: deploy/docker/Dockerfile.simplepool
31+
image: simplepool:latest
32+
container_name: simplepool
33+
restart: unless-stopped
34+
# host mode: miners hit :3334 directly, and bitcoind_url in proxy.conf
35+
# can point at 127.0.0.1 exactly like the bare-metal deploy.
36+
network_mode: host
37+
volumes:
38+
- ../../data:/data
39+
- ./proxy.conf:/etc/simplepool/proxy.conf:ro
40+
stop_grace_period: 15s
41+
42+
dashboard:
43+
build:
44+
context: ../..
45+
dockerfile: deploy/docker/Dockerfile.dashboard
46+
image: simplepool-dashboard:latest
47+
container_name: simplepool-dashboard
48+
restart: unless-stopped
49+
ports:
50+
- "${DASHBOARD_PORT:-8081}:8081"
51+
environment:
52+
PORT: "8081"
53+
PROXY_DB_PATH: /data/shares.db
54+
PUBLIC_STRATUM_URL: ${PUBLIC_STRATUM_URL:-stratum+tcp://<pool-host>:3334}
55+
THUNDER_RPC_URL: ${THUNDER_RPC_URL:-http://host.docker.internal:6009}
56+
POOL_THUNDER_RESERVE_ADDRESS: ${POOL_THUNDER_RESERVE_ADDRESS:-}
57+
POOL_PPS_SATS_PER_DIFF: ${POOL_PPS_SATS_PER_DIFF:-1000}
58+
ADMIN_USER: ${ADMIN_USER:-}
59+
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-}
60+
volumes:
61+
- ../../data:/data:ro
62+
extra_hosts:
63+
# Linux: makes host.docker.internal resolve to the host gateway.
64+
# No-op on macOS/Windows where docker adds it natively.
65+
- "host.docker.internal:host-gateway"
66+
stop_grace_period: 10s
67+
68+
payout:
69+
build:
70+
context: ../..
71+
dockerfile: deploy/docker/Dockerfile.payout
72+
image: simplepool-payout:latest
73+
container_name: simplepool-payout
74+
restart: unless-stopped
75+
environment:
76+
PAYOUT_DB_PATH: /data/shares.db
77+
THUNDER_RPC_URL: ${THUNDER_RPC_URL:-http://host.docker.internal:6009}
78+
THUNDER_FROM_ADDRESS: ${THUNDER_FROM_ADDRESS:-}
79+
PAYOUT_MIN_SATS: ${PAYOUT_MIN_SATS:-10000}
80+
PAYOUT_INTERVAL_MS: ${PAYOUT_INTERVAL_MS:-30000}
81+
PAYOUT_DRY_RUN: ${PAYOUT_DRY_RUN:-0}
82+
volumes:
83+
- ../../data:/data
84+
extra_hosts:
85+
- "host.docker.internal:host-gateway"
86+
stop_grace_period: 30s

0 commit comments

Comments
 (0)