Deploying a Bitcoin Regtest Network with Docker and CI/CD Tools

Hands-on + R&D guide.

We’ll stand up a private Bitcoin regtest network with two nodes, peer them, mine spendable coins and send/confirm transactions; wrapped in a clean repo with CI and an optional demo workflow.

📦 Repo: https://github.com/SubhanshuMG/bitcoin-regtest-devops


TL;DR

git clone https://github.com/SubhanshuMG/bitcoin-regtest-devops.git
cd bitcoin-regtest-devops

# Start both nodes (node1 mines 101 blocks on first run)
docker compose up -d

# Watch logs live while services get healthy
docker compose logs -f --tail=100

# Create & confirm a tx from node1 ➜ node2
bash ./scripts/create-tx.sh 0.10

# Tear down
docker compose down -v

Expected output:

🏦  Creating + broadcasting 0.10 BTC from node1 ➜ node2
🪙  TX broadcast – <txid>
✅  Confirmed in block (confirmations: 1)

What you’ll build

A two-node regtest network that’s easy to run and observe:

+----------------------------+         +----------------------------+
|        btc-node1           |         |          btc-node2         |
|  bitcoind (regtest)        | <-----> |   bitcoind (regtest)       |
|  RPC : 18443               |   P2P   |   RPC : 18445              |
|  P2P : 18444               |         |   P2P : 18446              |
|  Wallet "wallet"           |         |   Wallet "wallet"          |
|  Mines initial 101 blocks  |         |   Peers via addnode        |
+----------------------------+         +----------------------------+

Why regtest? Instant blocks, infinite coins (mine on demand), deterministic behavior—perfect for Ops, testing, and fee/mempool experiments.


Project layout (overview)

.
├─ docker-compose.yml          # 2 Bitcoin Core nodes, healthchecks (with start_period)
├─ Dockerfile                  # extends bitcoin/bitcoin:25 with jq
├─ scripts/
│  ├─ entrypoint.sh            # builds bitcoind args from env; idempotent 101-block bootstrap
│  ├─ create-tx.sh             # re-runnable: fresh address + confirm block each run
│  └─ wait-for-bitcoind.sh     # RPC readiness probe for healthchecks
└─ .github/workflows/
   ├─ ci.yml                   # lint + build + up + live logs + tx (with timings)
   └─ demo.yml                 # on-demand “Run workflow” demo; uploads node logs as artifact
└─ LICENSE                     # MIT

Step-by-step: try it locally

Pre-requisites

  • Docker Engine with docker compose v2

  • Internet for the first image pull

Clone & start

git clone https://github.com/SubhanshuMG/bitcoin-regtest-devops.git
cd bitcoin-regtest-devops
docker compose up -d

Observe live logs

docker compose logs -f --tail=100

Send a transaction (re-runnable)

bash ./scripts/create-tx.sh 0.10
bash ./scripts/create-tx.sh 0.25  # send another, new address each run

Quick checks

# Chain & peers
docker exec btc-node1 bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -rpcport=18443 getblockchaininfo
docker exec btc-node2 bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -rpcport=18445 getpeerinfo

# Balances
docker exec btc-node1 bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -rpcport=18443 getbalance
docker exec btc-node2 bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -rpcport=18445 getbalance

Clean up

docker compose down -v

Under the hood

  • Compose wires two nodes with distinct P2P/RPC ports and persistent volumes. Healthchecks call wait-for-bitcoind.sh to poll getblockchaininfo until RPC is ready.

  • Entrypoint script assembles all bitcoind flags from environment variables (robust—no brittle env expansion inside Compose). On first run, node1 mines 101 blocks so coinbase UTXOs mature and become spendable.

  • Transaction helper (create-tx.sh) asks node2 for a fresh address, sends coins from node1, mines one block on node1 to confirm, and reports confirmations via jq.


CI/CD that doubles as documentation

The repo ships with two workflows:

  1. CI (ci.yml) – runs on every push/PR

    • Lints Bash (ShellCheck) & Dockerfile (Hadolint)

    • Builds the image and brings the network up

    • Streams container logs live until both nodes are healthy (no silent hangs)

    • Sends a real transaction and prints timing metrics for build/up/tx

  2. Demo (demo.yml) – click Run workflow in GitHub Actions

    • Same bring-up, then broadcasts a tx (configurable amount)

    • Uploads docker logs from both nodes as an artifact—great for reviewers

See them in action here: https://github.com/SubhanshuMG/bitcoin-regtest-devops/actions

Tip: The repo badges in the README point to CI and the Demo workflow, so status is always visible at a glance.


R&D lab: experiments to try

  • Fee policy: tweak the -fallbackfee in the entrypoint or experiment with manual fees using sendtoaddress/fundrawtransaction/walletcreatefundedpsbt, then watch how confirmations behave when you mine.

  • Mining cadence: mine variable numbers of blocks between transactions to simulate different confirmation latencies:

      docker exec btc-node1 bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -rpcport=18443 \
      generatetoaddress 3 "$(docker exec btc-node1 bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -rpcport=18443 getnewaddress)"
    
  • Resilience: restart containers mid-run and confirm idempotency (node1 won’t re-mine its initial 101 blocks thanks to a .bootstrapped flag).

  • Scaling out: add node3..n in docker-compose.yml and set ADDNODE to point at node1 (or build a small mesh).

  • Deeper wallet ops: list UTXOs, craft raw transactions, try PSBT flows (walletcreatefundedpsbt, analyzepsbt, finalizepsbt).


Design choices & trade-offs

  • Compose over Kubernetes for local ergonomics; the same container pattern ports cleanly to Helm/k8s later.

  • One image for both nodes to minimize cache misses and binary drift.

  • Demo-level creds via env for clarity; prefer rpcauth or a secret manager in real deployments.

  • Observability-first CI: live logs + timeouts + explicit health waits—no mysterious red builds.

  • Idempotency: first-run mining happens once per data volume; every create-tx.sh invocation produces a fresh on-chain transaction.


Troubleshooting quick hits

  • Container unhealthy at start → RPC may not be ready yet; CI/healthchecks will wait. If it persists, check docker compose logs -f and verify env vars.

  • ShellCheck warnings → scripts are lint-clean; use _ for intentionally unused loop vars.

  • Hadolint DL3008 → we intentionally avoid pinning packages for CI portability; an inline ignore is documented.

  • Badges stale → ensure badges include ?branch=main (and &event=workflow_dispatch for the demo); run the workflow once on main.


Where to go next?

  • Add rpcauth or inject secrets from Vault/SOPS.

  • Enable txindex for advanced queries.

  • Export metrics/logs to your observability stack (Loki/Promtail, CloudWatch, etc.).

  • Integration tests: assert balances/confirmations post-tx as part of CI.


0
Subscribe to my newsletter

Read articles from Subhanshu Mohan Gupta directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Subhanshu Mohan Gupta
Subhanshu Mohan Gupta

A passionate AI DevOps Engineer specialized in creating secure, scalable, and efficient systems that bridge development and operations. My expertise lies in automating complex processes, integrating AI-driven solutions, and ensuring seamless, secure delivery pipelines. With a deep understanding of cloud infrastructure, CI/CD, and cybersecurity, I thrive on solving challenges at the intersection of innovation and security, driving continuous improvement in both technology and team dynamics.