Building a local Ethereum network with Docker and Geth

Tim Zöller
8 min readMar 2, 2018

--

The following article describes how I set up a private, local Ethereum blockchain for development purposes. I assume readers to have a basic understanding of blockchains, Ethereum and Docker. If you are not interested in this process but only want a working out-of-the-box solution to host your own private network, feel free to pull the results from my GitHub Repo: https://github.com/javahippie/geth-dev

Why build a local network at all?

I am still learning how to interact with the Ethereum network, how to build ÐApps and how to write smart contracts. While there are several test networks that can already be used by developers, working with them is not always easy. You need to synchronize the state, you need to participate in mining (or get yourself some Ether from the faucets) and you need a constant internet connection. This makes working on these networks very hard if you are on the road. At first, my plan was to create a Docker image with a single node, which can be contacted with RPC. Due to the great Ethereum documentation, this was achieved in an hour, so I wondered if it was possible to set up a whole network which can be started with a simple docker-compose up command, without the need of any configuration. I tried to find a working solution on GitHub and on Blogs, but most of them required some configuration after the containers were started (creating accounts, adding peers manually, …). The process of setting up such a self-contained network was harder than I first expected, but I learned a lot about geth and the Ethereum network while trying to achieve this.

The bootnode

When we set up a new network, we need to make sure that the nodes will be able to find each other. Theoretically, the nodes should be able to discover each other, if they are on the same network. As we need our network to be instantly ready when we want to use it, we are implementing a mechanism to support the nodes finding its peers faster. One way to achieve this is to define a fixed set of static nodes which we pass to the nodes. I decided to use a different approach and defined a boot node in the network. This type of node is not a part of the normal Geth install package but shipped with the geth Tools. Bootnodes do not keep any state of the blockchain, they help other nodes in the network to find each other. Setting up a bootnode in Docker is quite easy:

Dockerfile for an Ethereum Bootnode

From line 1 to 14 we are just installing the Geth tools from the official website. In line 17 the bootnode is started with a special nodekeyhex. We need to provide this, so we can derive the enode ID of the bootnode. In our example our bootnode will start with the nodekeyhex 08f0e1dee5c1b4645f3331a566009e41a4514b6cd28656d63d0449ecf812812b and print the enode ID 76a8171707eca17647a47ca99ffd348927dfa60102792ea349a25d5192e61855be83b786e376276a306afcceea4ffd1c9c77e4370b417efc39f328a0d068004c to the console, which we will save for later.

Defining basic properties of our blockchain

At the root of every Ethereum network is the first block which is called genesis block. We can predefine some of its properties with a JSON file that is used to initialize the network.

Note: I used puppeth to create the genesis block, which made it much easier.

genesis.json for our local test network

We don’t need to understand every single line, but it is interesting to take a closer look at some of the properties. The first important property is the chainId (line 3). The nodes are scanning the network regularly to find new peers. To prevent nodes from connecting to a foreign blockchain, nodes will only connect to those who have the same chainId.

One of the most important parts is the property clique (line 10ff) which specifies Proof-of-Authority (PoA) as our consensus protocol. Opposing to Proof-of-Work (PoW), which is currently used as a consensus protocol in the Ethereum main net, PoA is designed to be used in local test nets as it skips the mining work entirely. With the period property (line 11) we specify that a new block should be created every 15 seconds. If we decrease this value, the blocks are generated faster and the transactions can be processed faster. On the downside, the size of the blockchain will grow much faster. All the accounts which are contained in the extraData (line 17) property are allowed to create blocks, they take turns in creating them. Nodes that create blocks in a PoA network are called sealing nodes, you need at least two of them in your network.

With the alloc property (line 22ff) we prefund Ether to the accounts we will use later, so they are able to create transactions and pay the network to process them.

Starting and connecting nodes

After writing our genesis.json file we can finally initialize and start our blockchain. Again we do this with a Dockerfile:

Dockerfile for a mining Ethereum node

From line 1 to 14 we are again installing the binaries. In line 15 we copy the genesis.json file from above into the Docker image. In line 16 we initialize a new node with the genesis file. The chain which is defined by our genesis block is now the default chain for our image. With the args in line 18 and 19, we specify the password and the private key for our Ethereum account, which we write to local files in the following lines. Please never do this with public parameters in a network you want to use in production. The account is then imported into our geth client on line 22. The account address can be derived from the private key we specified in the arguments of the docker files.

After this setup is completed, the node is started with the command in line 27. Again, we don’t need to understand every parameter in detail and will only take a look at the most important ones. The --bootnodes parameter tells our node where to look for the bootnode. We need to specify the bootnodes enode ID, IP-Address, and Port Number. When starting up, our node will ask the bootnode for other peers in the network and will connect to them.

With the parameters --rpc --rcpaddr "0.0.0.0" --rpccorsdomain "*" we are preparing our node to accept connections via RPC. This will enable us to connect to the node from our laptop, once it is started. We will be allowed to connect from every host and on every network interface.

The flags --mine --etherbase $address will make the node start mining once it was started, the miners’ rewards will be sent to the $address of our account.

Finally, we are unlocking our account on the client, so it will always allow us to execute commands without any password checks once it is started. Do not set this flag in a production environment, this is a major security issue! The parameters to do so are --unlock $address --password ~/.accountpassword.

Monitoring

It is important to be aware of the status of the network. Are the peers connecting as we want them to? Are the blocks really created? How many transactions are pending? Of course, we can check the status of the network from the logs and the command line, but it is always nice to have a graphical dashboard. A working solution is the Ethereum network status monitor which is set up easily.

The status monitor is composed of a backend server and a client software. Both modules are installed with npm:

Dockerfile for the eth-net-intelligence-api

There is no specific configuration in the Dockerfile, but we are copying a file with the name “app.json” into the image, which contains information about our nodes, the IP address and port of our frontend and a shared secret:

Configuration file for our net intelligence API

The shared secret is then passed into the front end application, which receives the information from the back end we set up earlier:

Dockerfile for the Ethereum status monitor

We are now ready to use the dashboard, which looks like this:

Note: Right now the Ethereum Network Intelligence API is not properly maintained. In some constellations, the process just ends and the Docker container automatically restarts. This behavior matches GitHub issue #250 which should be fixed with the PR #242 — which was issued in September 2016 but never merged.

Putting it all together

The following docker-compose file starts one bootnode, two sealing nodes, and a regular node to create a working blockchain that creates blocks in an energy-efficient way every 15 seconds. Additionally, two Docker containers for the ETH Status Monitor (Front- and Backend) are created. We need to assign fixed IP addresses to most of the nodes: The bootnode needs a static IP address, so it can be passed to the other nodes in the network for the initial connections. The full nodes need static IP addresses so we can add them to the status monitor. We map the 8545 port of the three full nodes to the ports 8545,8546,8547 of our hosts’ address, so we are able to access the RPC port of every node. For the nodes chain data we are defining Docker volumes, so we can store the chain data on the host. I am not happy that we have to define so many parameters which need to be related to each other (nodekeyhex to enodeId, private key to account, IP addresses), but this way all the configuration can be loaded from scratch.

Conclusion

We learned how to define our own blockchain with the genesis.json file, set up a network and connected the peers with help from a bootnode. Then we added a status monitor to our network to confirm that the blockchain operates normally.

Next steps

You can get all the sources from this example from my GitHub repository: https://github.com/javahippie/geth-dev

From the repositories root directory, execute the command docker-compose up to start the whole network (it may take a while to set up all the images for the first time). The status monitor will be accessible at http://localhost:3000. You can connect to your nodes with your local geth installation with the command geth attach http://localhost:8545 and start exploring the Ethereum network. You can deploy and call smart contracts, create transactions in the network and start developing your own ÐApps.

Your feedback

I am looking forward to hearing what you liked or disliked about this post, either here or on Twitter. If you want to know more or if you are having any questions, I will gladly try to help. If I made a mistake or something is not explained as clearly as I thought it to be, feel free to reach out and correct me or raise a Github issue.

--

--

Tim Zöller

IT professional in the field of digitalization, interested in Java, Clojure and Blockchains