Testing Beacon Chain Withdrawals with Docker

This guide will provide you with a minimal instruction set if you intend to test:

  • Spinning up a zhejiang testnet node using Geth&Lighthouse with Docker(-Compose)

  • Exiting/withdrawing a validator on zhejiang testnet

  • Preparing withdrawal credential changes

Feel free to check out elementary guides on how to run testnet nodes with the help of Docker or Docker-Compose.

Also, if you’re generally keen on running a production (or testnet) Ethereum node with Docker, check out the awesome eth-docker.net project.

Why run on zhejiang?

  • Very fast node initialisation time (i.e. no extended sync periods + takes less than 2GBs of disk space)

  • Easy to obtain testnet ETH

  • It’s a short-lived, public testnet particularly designed for brief operation tests

  • Dry-run prior to more established testnets like Sepolia & Goerli which will follow in February/March ´23

Run a zhejiang testnet node

As an example client pair we’ll use Geth & Lighthouse on an Ubuntu instance & pre-installed docker-compose - feel free to adapt to different client pairs using this documentation.


Navigate in a terminal/CLI to your user’s /home directory. Create a project directory with subfolders

$ mkdir -p zhejiang/{geth,lighthouse-bn,lighthouse-vc,JWT,validator_keys}; cd zhejiang


Retrieve configuration files for the zhejiang genesis from a repository maintained by the Ethereum Foundation

$ git clone https://github.com/ethpandaops/withdrawals-testnet.git


Generate a JasonWebToken (JWT) so that the execution and consensus client can communicate securely

$ openssl rand -hex 32 | tr -d "\n" > "$(pwd)/JWT/jwtsecret"


Provide Geth with the testnet genesis configuration

$ docker run -it -v $(pwd)/geth:/root/.ethereum -v $(pwd)/withdrawals-testnet/zhejiang-testnet/custom_config_data/genesis.json:/genesis.json ethpandaops/geth:master --datadir /root/.ethereum init genesis.json

Depending on your Docker configuration every Docker command should be preceded by sudo


Copy & paste the below attached Docker-Compose code in a newly created file to be named “docker-compose.yaml” located in the project directory /zhejiang 👇

version: "3.4"

        image: sigp/lighthouse:capella
        container_name: beacon
        tty: true
        restart: on-failure
            - ./lighthouse-bn:/root/.lighthouse
            - ./JWT:/JWT
            - ./withdrawals-testnet/zhejiang-testnet/custom_config_data:/zhejiang
            - # consensus http
            - 9000:9000/tcp # consensus p2p, open to internet
            - 9000:9000/udp # consensus p2p, open to internet
        command: >
          --testnet-dir zhejiang
          --datadir /root/.lighthouse
          --enr-udp-port 9000
          --enr-tcp-port 9000
          --discovery-port 9000
          --execution-endpoints http://geth:8551
          --execution-jwt /JWT/jwtsecret

        image: sigp/lighthouse:capella
        container_name: vc
        tty: true
        restart: on-failure
            - ./lighthouse-vc:/root/.lighthouse
            - ./withdrawals-testnet/zhejiang-testnet/custom_config_data:/zhejiang
            - consensus
        command: >
          --testnet-dir zhejiang
          --beacon-nodes http://beacon:5052
          --suggested-fee-recipient 0x0000000000000000000000000000000000000000

        image: ethpandaops/geth:master
        container_name: geth
        tty: true
        restart: on-failure
            - ./geth:/root/.ethereum
            - ./JWT:/JWT
            - # engine rpc
            - 30303:30303/tcp # execution p2p, open to internet
            - 30303:30303/udp # execution p2p, open to internet
            - 8545:8545
        command: >
          --datadir /root/.ethereum
          --http.vhosts *
          --http.corsdomain *
          --http.api engine,eth,web3,net,debug
          --http.port 8545
          --authrpc.jwtsecret /JWT/jwtsecret
          --authrpc.port 8551
          --authrpc.vhosts *
          --networkid 1337803
          --syncmode full
          --bootnodes "enode://691c66d0ce351633b2ef8b4e4ef7db9966915ca0937415bd2b408df22923f274873b4d4438929e029a13a680140223dcf701cabe22df7d8870044321022dfefa@,enode://89347b9461727ee1849256d78e84d5c86cc3b4c6c5347650093982b726d71f3d08027e280b399b7b6604ceeda863283dcfe1a01e93728b4883114e9f8c7cc8ef@,enode://c2892072efe247f21ed7ebea6637ade38512a0ae7c5cffa1bf0786d5e3be1e7f40ff71252a21b36aa9de54e49edbcfc6962a98032adadfa29c8524262e484ad3@,enode://71e862580d3177a99e9837bd9e9c13c83bde63d3dba1d5cea18e89eb2a17786bbd47a8e7ae690e4d29763b55c205af13965efcaf6105d58e118a5a8ed2b0f6d0@,enode://2f6cf7f774e4507e7c1b70815f9c0ccd6515ee1170c991ce3137002c6ba9c671af38920f5b8ab8a215b62b3b50388030548f1d826cb6c2b30c0f59472804a045@"


Start the Docker-Compose environment

$ docker-compose up

Eventually, stop the docker-compose environment: $ docker-compose stop

For stopping only the beacon or execution or validator client services use:

$ docker-compose stop execution/consensus/validator

Interlude: Becoming a validator (out of scope)

Steps 1-4 are well-documented elsewhere:

  1. Add network to your local metmask

  2. Receive 33 zETH from a faucet

  3. Generate validator keys (e.g. with Staking-CLI) using 0x00 credentials (for test purposes)

  4. Deposit via zhejiang launchpad

  5. Look out for your validator on a block explorer

  6. Move generated keys into project folder subdirectory /validator_keys

  7. Import validator key(s) to Lighthouse

    👉 based of the project directory /zhejiang prompt:

$ docker run -it -v $(pwd)/lighthouse-vc:/root/.lighthouse -v $(pwd)/validator_keys:/validator_keys -v  $(pwd)/withdrawals-testnet/zhejiang-testnet/custom_config_data:/zhejiang sigp/lighthouse:capella lighthouse --testnet-dir zhejiang account validator import --directory /validator_keys

Testing withdrawing / exiting a validator

Provide the consensus client with the necessary information in order to be released from consensus duties.

While your validator client is running in the background prompt:

$ docker-compose run --rm --no-deps -v $(pwd)/lighthouse-vc:/root/.lighthouse -v $(pwd)/withdrawals-testnet/zhejiang-testnet/custom_config_data:/zhejiang consensus lighthouse --testnet-dir zhejiang account validator exit --beacon-node http://beacon:5052  --keystore /root/.lighthouse/custom/validators/<0x_yourvalidatorkey>/voting-keystore.json

Edit the validator key directory and voting-keystore.json filename accordingly

Note for mainnet: node operators have to keep on running their node until the validator reaches its assigned exit epoch (s. block explorer) - if it is shutdown too early, the validator will incur penalties.

Addendum: Testing withdrawal credential change

…also known as “BLS to Execution” operation. This operation comprises of a message broadcast to the network which will lead to a change from the BLS withdrawal credentials (0x00) to a regular Ethereum address (0x01) of your choice.

Note: currently around ~60% or ~300,000 mainnet validators have BLS credentials and need to make a one-time change to a regular Ethereum address in order to fully or partially withdraw their stake or rewards.

Find out how to identify withdrawal credentials by entering a validator index on beaconcha.in:

staking with centralized custodians means: not your keys, not your crypto
staking with centralized custodians means: not your keys, not your crypto

If you want to test changing withdrawal credentials from 0x00 -> 0x01, check out these Docker instructions utilising a programm called ethdo by attestant.io.

The official Staking-Deposit-CLI by the EF will be updated and include similar functionality shortly.

👉 Broadcasting of your BLS-to-execution key change will NOT happen before the (Zhejiang) Shanghai/Capella fork scheduled for the 07th of February 3 p.m. UTC

Up until then the generated message is just stored in a local pool. Once Shanghai/Capella is triggered, the BLS-to-execution messages will be automatically broadcasted/gossiped over the network to your peers.

As soon as a node holding your BLS-to-execution message proposes a block, the message will be executed.

Note for mainnet:

  • Be aware that changing withdrawal credentials involves providing withdrawal private keys. Thus,*never trust* instructions from a random stranger writing blogposts on mirror.xyz or elsewhere - instead: follow official announcements from client teams and developers

  • BLS-to-execution messages cannot be broadcasted until after the Shanghai/Capella hard fork

  • beaconcha.in have announced they will provide a web interface for dragging & dropping a signed credential-change.json file and submit it to the network


Please keep in mind this is a testnet guide that may contain mistakes and that takes shortcuts which come with trade-offs. It could quickly become outdated as it’s subject to ever evolving network and client changes.


featured image CC BY-NC 2.0 by Matthew Warner


This post was supported by a grant from a CLR funding round held by EthStaker, mainly matched by the EF.

Subscribe to Ladislaus
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
This entry has been permanently stored onchain and signed by its creator.