Files
bitcoind/README.md
T
bitdeals bf2c2d3508 feat: docker image for Bitcoin Core, configured by environment
Replaces lncm/bitcoind, which pins Core 26 — a version that still has legacy
wallets, while the code that talks to it (bt's BitcoindClient) is written for
the descriptor-only behaviour of 29 and later.

The binaries are the official release build, verified by SHA-256 in the same
layer that downloads them; a version bump that forgets the checksum fails the
build instead of shipping something unverified. uid 1000 and /data/.bitcoin are
kept from the image this replaces, so an existing named volume survives the
switch without a recursive chown of a synced chain.

Verified on testnet2: regtest node healthy in ~12 s, descriptor wallet, 101
blocks mined, sendtoaddress accepted — the last one being the check for
BITCOIND_FALLBACKFEE, without which a fresh chain refuses to send.
2026-08-06 11:56:52 +00:00

6.7 KiB

Intro

Русская версия: README.ru-RU.md

Bitcoin Core is the reference Bitcoin implementation. bitcoind is its daemon: it keeps a full copy of the chain, relays transactions and serves a JSON-RPC interface.

Bitcoin Core running as a daemon in a docker container, configured by environment variables.

This repository covers the docker deployment only. The binaries are the official release build, checked against a SHA-256 pinned in the Dockerfile.

Usage

The container has two ports and they are not interchangeable. The RPC port (8332 main, 18332 test, 18443 regtest) controls the wallet and has no TLS: keep it on loopback, or on an internal docker network. The P2P port (8333 / 18333 / 18444) is the chain protocol: publish it to accept inbound peers, leave it unpublished to stay outbound-only.

The chain is selected by BITCOIND_CHAIN, and it also sets the default RPC port. An unknown value stops the container instead of falling back to mainnet.

docker-compose

services:
  bitcoind:
    build:
      context: https://git.bitdeals.org/private/bitcoind.git
      dockerfile: ./docker/Dockerfile
    image: registry.bitdeals.org/bitcoind
    environment:
      - BITCOIND_CHAIN=regtest
      - BITCOIND_USER=CHANGE_ME
      - BITCOIND_PASSWORD=CHANGE_ME
      - BITCOIND_FALLBACKFEE=0.00001
      - BITCOIND_TXINDEX=1            # both required by ElectrumX,
      - BITCOIND_TXOSPENDERINDEX=1    # drop them if nothing indexes this node
    ports:
      - 127.0.0.1:18443:18443   # RPC — loopback only
    volumes:
      - btcdata:/data/.bitcoin

volumes:
  btcdata:

docker cli

docker run -d \
  -e BITCOIND_CHAIN=regtest \
  -e BITCOIND_USER=CHANGE_ME \
  -e BITCOIND_PASSWORD=CHANGE_ME \
  -e BITCOIND_FALLBACKFEE=0.00001 \
  -p 127.0.0.1:18443:18443 \
  -v btcdata:/data/.bitcoin \
  registry.bitdeals.org/bitcoind

Anything after the image name is appended to the daemon's own arguments, so a one-off maintenance run needs no new image:

docker run --rm -v btcdata:/data/.bitcoin registry.bitdeals.org/bitcoind -reindex

build and publish

docker build . --file docker/Dockerfile --tag registry.bitdeals.org/bitcoind
docker push registry.bitdeals.org/bitcoind

A different Core version is a build argument, and the checksum must move with it — take both from https://bitcoincore.org/bin/bitcoin-core-<version>/SHA256SUMS:

docker build . --file docker/Dockerfile \
  --build-arg BITCOIN_VERSION=31.1 \
  --build-arg BITCOIN_SHA256_X86_64=b80d9c3e04da78fb6f0569685673418cf686fadba9042d926d13fb87ff503f9e \
  --tag registry.bitdeals.org/bitcoind:31.1

Parameters

Container images are configured using parameters passed at runtime.

Parameter Function
-p 127.0.0.1:8332 RPC port. The daemon binds what BITCOIND_RPCBIND says, so what you publish decides who reaches it. It has full control of the wallet — see "Notes"
-p 8333 P2P port. Optional: without it the node still connects out to peers, it just cannot be connected to. Follows the chain: 8333 main, 18333 test, 18444 regtest
-v /data/.bitcoin Data directory: the chain, the block index and the wallets. Without it a container update means downloading the chain again
-e BITCOIND_CHAIN Network: main, test, signet or regtest. Anything else stops the container. Default: main
-e BITCOIND_USER RPC user. Default: user — change it
-e BITCOIND_PASSWORD RPC password. Default: pass — change it
-e BITCOIND_PORT RPC port. Default: the standard port of the selected chain (8332/18332/38332/18443). Pin it to keep one RPC URL across networks
-e BITCOIND_RPCBIND Interface the RPC listens on inside the container. Default: 0.0.0.0, so other containers reach it by service name
-e BITCOIND_RPCALLOWIP Who may call the RPC. Default: 0.0.0.0/0 — the container is expected to be closed off by what you publish, not by this
-e BITCOIND_FALLBACKFEE Fee rate (BTC/kvB) used when the chain has no fee history to estimate from. Default: 0 (disabled). A fresh regtest chain needs it, e.g. 0.00001
-e BITCOIND_TXINDEX Build the full transaction index. Default: 0. Required by ElectrumX. Changing it on an existing datadir forces a reindex
-e BITCOIND_TXOSPENDERINDEX Build the index of which transaction spent each output. Default: 0. Required by ElectrumX 2.x. Changing it on an existing datadir forces a reindex
-e BITCOIND_EXTRA_ARGS Extra bitcoind arguments, split on whitespace and appended last — so they override everything above

Notes

  • The RPC has full control of the wallet and no TLS. Publish it to 127.0.0.1 only, or not at all — inside a compose project other services reach it over the internal network by service name. BITCOIND_RPCALLOWIP defaults to 0.0.0.0/0 because that is the only value that works for container-to-container calls; the port mapping is what keeps it private.
  • The RPC credentials are visible in the container's process list. They are daemon arguments, which is how Core takes network-specific options that a config file would apply only inside a [chain] section. bitcoin-cli inside the container reads them from cli.conf instead, so the health check does not add a second copy.
  • A datadir from another image needs one manual chown. The daemon runs as uid 1000 and the entrypoint fixes ownership of the data directory itself, but it deliberately does not walk it: a synced mainnet datadir is hundreds of gigabytes and a recursive chown would be added to every restart.
  • An ElectrumX in front of this node needs two indexes. Set both BITCOIND_TXINDEX=1 and BITCOIND_TXOSPENDERINDEX=1: ElectrumX 2.x asks getindexinfo at startup and exits naming the one that is missing — first txindex, then, once that is fixed, txospenderindex. Both default to 0 here because they cost disk and most other users do not need them.
  • Pruning is incompatible with ElectrumX. An indexer needs the whole chain; -prune through BITCOIND_EXTRA_ARGS will make it fail at some later, much less obvious point.
  • Core 29 and later have no legacy wallets. createwallet produces a descriptor wallet, and a private key is imported with importdescriptors, not importprivkey. Code that pins descriptors=false fails at wallet creation.
  • Bumping the version means bumping the checksum. The build downloads the release tarball and verifies it in the same layer; a version bump on its own fails at sha256sum -c rather than shipping something unverified.
  • The container turns healthy as soon as the RPC answers, which is long before the chain is synced. That is on purpose: "unhealthy for three days of initial block download" would get the node restarted by anything watching health.