Posted on Feb 01, 2023Read on

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 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


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

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

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 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

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

Bonus section

Kindly ask if there will be a POAP distribution for participating in the zhejiang testnet on the Ethstaker Discord server 🤓


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.