How to spin up your own PoA EVM chain?

We will set up 1 bootnode and 2 geth nodes in 3 different servers. They will form the network for the EVM chain. All the nodes will run in containers.

My setup

  • I created 3 droplets on digitalocean (CentOS; shared CPU; 1GB 1Core)

  • install and start docker (with the compose plugin) in all droplets

Step 1: set up bootnode (droplet A)

first create boot.key

mkdir /root/devnet/ && docker run -d --rm -v /root:/root ethereum/client-go:alltools-latest bootnode --genkey=/root/devnet/boot.key

then create a compose file and run docker compose up -d:

    image: ethereum/client-go:alltools-latest
    container_name: bootnode
      - '30303:30303/udp'
    restart: always
      - /root:/root

in docker compose log, you will see a encode URL like this:


replace with <the public IP>:30303 and take it down some where. We will refer it as encodeURL later.

Step 2: create account (droplet B and C)

echo 123456 > /root/devnet/password.txt
docker run -d --rm -v /root:/root ethereum/client-go:alltools-latest geth account new --password /root/devnet/password.txt

then you can find the address by ls /root/.ethereum/keystore/

you will see a file: UTC--2022-10-28…--<address> where you can extract the address.

Step 3: create genesis file

you can run this command in any server to create the genesis file (Note: don’t forget to export the file to /root/devnet/devnet.json)

docker run -it --rm -v /root:/root ethereum/client-go:alltools-latest puppeth

you should provide the two addresses generated in previous step for these questions:

  • Which accounts are allowed to seal?

  • Which accounts should be pre-funded?

then modify the "nonce" field to be a random value as recommended here

last, copy the genesis file to both droplet B and C

Step 4: init genesis block and start the node (droplet B and C)

docker run -d --rm -v /root:/root ethereum/client-go:alltools-latest geth init /root/devnet/devnet.json

then create a compose file and run docker compose up -d:

    image: ethereum/client-go
    container_name: geth
        '--networkid=<read from the genesis file>',
        '--unlock=<step2 generated address>',
      - '8545:8545'
      - '30303:30303'
    restart: always
      - /root:/root

Step 5: verify

in the bootnode server, run docker compose log you should see:

bootnode  | TRACE[10-29|03:28:44.657] << FINDNODE/v4                           id=<REDACTED> addr=<REDACTED>  err=nil
bootnode  | TRACE[10-29|03:28:44.657] >> NEIGHBORS/v4                          id=<REDACTED> addr=<REDACTED>  err=nil

in the other servers, you should see:

geth  | INFO [10-29|00:32:41.507] Looking for peers                        peercount=1 tried=0 static=0
geth  | INFO [10-29|00:32:43.326] Successfully sealed new block            number=42 sealhash=<REDACTED> hash=<REDACTED> elapsed=14.483s
geth  | INFO [10-29|00:32:43.326] 🔨 mined potential block                  number=42 hash=<REDACTED>
geth  | INFO [10-29|00:32:43.327] Commit new sealing work                  number=43 sealhash=<REDACTED> uncles=0 txs=0 gas=0 fees=0 elapsed="269.928µs"

you can also run:

docker run -it --rm -v /root:/root ethereum/client-go:alltools-latest geth attach
> eth.blockNumber

and you will see the blockNumber increasing.

congrats on spinning up your own EVM chain!


Subscribe to bap2pecs
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.