Building an Ethereum Environment with Docker

1) Introduction

Docker is a powerful tool for managing containers and run-time environments and, besides its many advantages, Docker can also be handy to keep our development workstation tidy while playing with all kind of cool technologies. In addition, containerisation adds an important security aspect by providing a strong isolation from the host’s affairs.

In this writing, we are going to harness the power of Docker to build a first Ethereum work and development environment.

The Ethereum Go (language) team builds a Docker image of a “geth” node as part of their continuous build chain. The C++ team does the same and we can use these images to quickly run an Ethereum node in our local environment. Alternatively, we can script our own images, which brings some advantages as we will see later.

Before starting with Ethereum however, we need to make a very quick “Docker Basics in 120 Seconds” intermezzo. If you know Docker already, just skip this part…

2) Docker Basics – Part 1

Docker allows to run operating systems, applications and tools in so called Containers. A Container is an isolated environments that represents a autonoumous host on its own – a bit in the same way a Virtual Machine does. Yet, Docker Containers are much lighter. They do not start an entire full-blown operating system for each Container instance. Instead, Docker uses Linux kernel isolation mechanisms to run applications on the top of the host’s operating systems, yet keeping them isolated.

In order to start a Container, Docker uses Images. An image is basically the…image…of an environment before running it (kind like an installation ISO/disk) and they contain the strict minimum required to run this environment. Images represent the common and stable starting point for Containers. They can be created by various means and can have dependencies with other images. The most common ways to “get” an image is a) to pull it from an image repository, b) to make a snapshot of a Container and c) by executing a build script called a “Dockerfile”. Option a) and c) will be used in this tutorial.

Note: This article targets Docker on Linux. At the time of writing, the native Docker clients for Windows and OSX are in Beta state and the older implementations are based on a virtual machine, which is not handy beside of being obsolete soon.

Oh yes, if not yet the case, install Docker on your Linux host (which can be a VM) like this:

$ sudo apt-get install docker.io

3) A full Ethereum Node

The first test would be to download the current Ethereum go-client (“geth“) image and to start a client node that connects to the Ethereum production network.

:~$ sudo docker pull ethereum/client-go
latest: Pulling from ethereum/client-go
4fd7f6ecc65a: Pull complete 
:
9b791346678d: Pull complete 
Digest: sha256:6f281c10a190584931e9b04223743c28f00a10f910a7a7b80eb1c3ab557c48be
Status: Downloaded newer image for ethereum/client-go:latest

Now, we start a simple node, as it is described in the Ethereum documentation. Please stop the node with CTRL+C once the block synchronisation has started. We are not going to work with this container, so there is no need to download the entire chain data right now.

:~$ sudo docker run -it -p 30303:30303 ethereum/client-go

I0625 17:04:09.450267 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to /root/.ethereum/chaindata
I0625 17:04:09.455942 ethdb/database.go:169] closed db:/root/.ethereum/chaindata
I0625 17:04:09.456202 cmd/utils/flags.go:601] WARNING: No etherbase set and no accounts found as default
I0625 17:04:09.456505 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to /root/.ethereum/chaindata
I0625 17:04:09.460356 ethdb/database.go:82] Alloted 16MB cache and 16 file handles to /root/.ethereum/dapp
I0625 17:04:09.463419 eth/backend.go:172] Protocol Versions: [63 62 61], Network Id: 1
I0625 17:04:09.463470 eth/backend.go:201] Blockchain DB Version: 3
I0625 17:04:09.644915 eth/backend.go:247] WARNING: Wrote default ethereum genesis block
I0625 17:04:09.645520 core/blockchain.go:206] Last header: #0 [d4e56740…] TD=17179869184
I0625 17:04:09.645673 core/blockchain.go:207] Last block: #0 [d4e56740…] TD=17179869184
I0625 17:04:09.645786 core/blockchain.go:208] Fast block: #0 [d4e56740…] TD=17179869184
I0625 17:04:09.646432 p2p/server.go:313] Starting Server
I0625 17:04:11.555203 p2p/discover/udp.go:217] Listening, enode://8a8ea2dad08b3f15121c622ec6d72dc4505093ec53cc49a9a50594659318b30548cdcbbe2d4e3503cc62292506f50e017c1577abf1d5756ca874495d43eb1e70@[::]:30303
I0625 17:04:11.555540 p2p/server.go:556] Listening on [::]:30303
I0625 17:04:11.556186 node/node.go:296] IPC endpoint opened: /root/.ethereum/geth.ipc
I0625 17:04:31.560047 eth/downloader/downloader.go:320] Block synchronisation started
I0625 17:04:34.251195 core/blockchain.go:964] imported 2 block(s) (0 queued 0 ignored) including 0 txs in 647.186281ms. #2 [88e96d45 / b495a1d7]
I0625 17:04:34.267987 core/blockchain.go:964] imported 4 block(s) (0 queued 0 ignored) including 0 txs in 16.460388ms. #6 [3d612266 / 1f1aed8e]
I0625 17:05:33.760770 core/blockchain.go:964] imported 186 block(s) (0 queued 0 ignored) including 0 txs in 323.229106ms. #192 [e0c7c0b4 / 723899e8]
I0625 17:05:34.016536 core/blockchain.go:964] imported 5 block(s) (0 queued 0 ignored) including 0 txs in 7.847331ms. #197 [967642fd / 56f4551b]
I0625 17:05:35.850019 core/blockchain.go:964] imported 458 block(s) (0 queued 0 ignored) including 0 txs in 888.338897ms. #655 [1e1c730d / a8f9df73]
I0625 17:05:36.716939 core/blockchain.go:964] imported 505 block(s) (0 queued 0 ignored) including 0 txs in 863.672318ms. #1160 [3e066692 / 85d6e7b9]
I0625 17:05:37.756635 core/blockchain.go:964] imported 580 block(s) (0 queued 0 ignored) including 0 txs in 1.036141885s. #1740 [524a846b / 0bcc3bc1]
I0625 17:05:38.676489 core/blockchain.go:964] imported 637 block(s) (0 queued 0 ignored) including 0 txs in 916.068247ms. #2377 [2370141a / a1c97b7e]
I0625 17:05:39.793459 core/blockchain.go:964] imported 749 block(s) (0 queued 0 ignored) including 0 txs in 1.110722738s. #3126 [1f16ca3f / a6eec4da]

# Connection and download works… let’s stop here!
# HIT CTRL+C 

^CI0625 17:05:40.842769 cmd/utils/cmd.go:74] Got interrupt, shutting down...
I0625 17:05:40.842952 node/node.go:328] IPC endpoint closed: /root/.ethereum/geth.ipc
I0625 17:05:40.843021 core/blockchain.go:565] Chain manager stopped
I0625 17:05:40.843110 eth/handler.go:221] Stopping ethereum protocol handler...
I0625 17:05:40.843242 eth/downloader/downloader.go:299] Synchronisation failed: header processing canceled (requested)
I0625 17:05:40.843868 eth/handler.go:242] Ethereum protocol handler stopped
I0625 17:05:40.843945 core/tx_pool.go:163] Transaction pool stopped
I0625 17:05:40.843956 eth/backend.go:498] Automatic pregeneration of ethash DAG OFF (ethash dir: /root/.ethash)
I0625 17:05:40.844065 ethdb/database.go:169] closed db:/root/.ethereum/chaindata
I0625 17:05:40.844177 ethdb/database.go:169] closed db:/root/.ethereum/dapp
:~$ 

Above, we started the Docker container using the RUN command. RUN takes an image, which in this case is “ethereum/client-go“, creates a new container (!) and starts what the image defines as ENTRYPOINT. At this stage, the entry point is “/usr/bin/geth“.

Multiple things are important to understand:

1) RUN always creates a new container. This means, by using RUN each time we want to start a container, we will end up with a lot of useless containers. Once a container is created, the correct way to re-start it is the START command.

2) The command line parameters following the Docker command – RUN in this case – apply to this Docker command. “-it”, equivalent to “-i -t”, stands for “interactive” and “allocate a pseudo terminal (tty)”. Without these instructions, the container would run in the background with no feedback to our terminal. “-p 30303:30303” instructs Docker to expose the port 30303 from inside the container to the host and other containers, at port number 30303. As you remember, a container is an isolated environment and without implicitly exposing this port, the Ethereum client inside the container would not be able to contact the outside world and the blockchain. 30303 is the default Ethereum peer-to-peer network port. The other parameters following the image name are applied to the command that the image defines as start point when booted. In this case, there are none.

3) Another important aspect is the place where the blockchain data is stored in this test. By default, “geth” uses “$userhome/.etherum” as data directory. This still is true when running inside of the container, if not specified otherwise as “root”: “/root/.ethereum“. This place is however “inside” of the container on its “virtual disk”. Again, having the data situated within the container keeps it isolated from host and other containers, which is not necessarily what we want.

In our case, the node needs to download the entire blockchain data. This takes a considerable amount of time, bandwidth and storage space and it could become very handy to share these files between various containers and host.

Sharing data/bases is obviously a classic issue when working with larger applications and Docker offers multiple options in this regard. In previous versions of Docker, people often used so called “Data Containers”. These are classic and dedicated containers, independent from the application run-time instance that are dedicated to store the data. The current Docker version replaces this idea by so called VOLUMES. In this article, we are however going for a different solution: we store the blockchain and account data on our host’s disk and mount the directory into the containers. This has some advantages in the case of Ethereum, as we will see later.

Before going further, let’s see what images we currently have in our installation:

:~$ sudo docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ethereum/client-go   latest              26d1f0e94420        28 hours ago        194.6 MB

Next, let’s list our container instances, running or not. The latter must be specified by adding the “-a” command:

:~$ sudo docker ps -a
CONTAINER ID        IMAGE                       COMMAND             CREATED             STATUS                      PORTS               NAMES
d8f940a14808        ethereum/client-go:latest   "/usr/bin/geth"     48 minutes ago      Exited (0) 46 minutes ago                       thirsty_kirch       

The INSPECT command can be very handy as it displays the entire configuration and situation of a container.

:~$ sudo docker inspect d8
[{
    "AppArmorProfile": "",
:
:

Our current container has previously been created using the RUN command and now we are going to restart the instance using the START command:

:~$ sudo docker start -i d8

-i” attaches the container output to our terminal and “d8” represents the container ID, which could also be the name and which does not need to be the complete ID as longs as it’s unambiguous.

Alternatively, we can start the container in background and attach a terminal later. This is Docker knowledge that will come handy later.

:~$ sudo docker start d8
d8
# Container is now running

:~$ sudo docker attach d8
I0625 17:58:57.970056 eth/downloader/downloader.go:320] Block synchronisation started
I0625 17:59:00.762328 core/blockchain.go:964] imported 9 block(s) (0 queued 0 ignored) including 0 txs in 659.774334ms. #3135 [fd5ca432 / 471daa6f]

CTRL+C stops the container!

^CI0625 17:59:02.352881 cmd/utils/cmd.go:74] Got interrupt, shutting down...
I0625 17:59:02.353163 node/node.go:328] IPC endpoint closed: /root/.ethereum/geth.ipc
I0625 17:59:02.353180 core/blockchain.go:565] Chain manager stopped
I0625 17:59:02.353186 eth/handler.go:221] Stopping ethereum protocol handler...

Alternatively:

:~$ sudo docker start d8
d8
:~$ sudo docker attach d8
I0625 17:59:30.708096 p2p/discover/udp.go:217] Listening, enode://

CTRL+P CTRL+Q detaches, but keeps the container running

:~$ sudo docker stop d8
d8

Having concluded this test and having the basic knowledge in our backpack, it’s time to get serious! The first thing we want is a “geth” node that connects to the Ethereum production network, that keeps our local blockchain in sync and that opens its service ports to proxy other tools – of course, also running in containers – towards the Ethereum network.

:~$ sudo docker run -it --name ethereum -v /opt/docker/ethereum:/root/.ethereum -p 30303:30303 -p 8545:8545 -p 8546:8546 ethereum/client-go --ws --rpc --rpcaddr "0.0.0.0" --wsaddr "0.0.0.0"

The command is “docker run” and the image to boot into the new container is “ethereum/client-go”. The RUN command has the following parameters:

  • -it” that starts the container in interactive mode and attaches the container’s standard output to our terminal. When re-starting the container later, we can choose to have run the process stay in background, but for now we would like to see what’s going on.
  • – -name” gives the container the logical name “ethereum” that we use later to access this instance.
  • -p 30303:30303 -p 8545:8545 -p 8546:8546” expose and map the three ports from inside the container to the outside, which is the hosts and possibly other containers (more about networking later).
  • -v /opt/docker/ethereum:/root/.ethereum“ mounts the host directory “/opt/docker/ethereum”, the place where we want to store the blockchain data, into the container at the position “/root/.ethereum”. The latter is the default location where “geth” stores all information when started using the root user account.

The image’s ENTRYPOINT command, “geth” in this case, this can by visualised by INSPECTing, is called in the same way that we would use the tool when running directly on the host. Please note that the container command line parameters cannot (easily) be changed later and that we would need to create a new container if a different command line is required. Yet “Containers are cheap” in Docker, so this constraint is not really an issue, especially as the data is not located within the container and a re-download is not required. One thing to remember though: only one “geth” node can access the blockchain data concurrently, so it’s not possible to run multiple main “geth” nodes simultaneous.

  • – -ws – -rpc” activate “geth“‘s web sockets and HTTP RPC interface respectively, and
  • – -rpcaddr “0.0.0.0” – -wsaddr “0.0.0.0” open these interfaces to all (!) adresses on the (containerised) network. Doing this would normally be a bit dangerous, but we are not running on the physical network of the host. More on this later.

Executing the above command should start a fresh container, invoke the “geth” tool which then starts downloading the blockchain data. (Note: you may use the “—fast” option).

:~$ sudo docker run -it --name ethereum -v /opt/docker/ethereum:/root/.ethereum -p 30303:30303 -p 8545:8545 -p 8546:8546 ethereum/client-go --ws --rpc --rpcaddr "0.0.0.0" --wsaddr "0.0.0.0"

I0625 18:22:03.697381 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to /root/.ethereum/chaindata
I0625 18:22:03.819930 ethdb/database.go:169] closed db:/root/.ethereum/chaindata
I0625 18:22:03.820202 cmd/utils/flags.go:601] WARNING: No etherbase set and no accounts found as default
I0625 18:22:03.820453 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to /root/.ethereum/chaindata
I0625 18:22:03.827300 ethdb/database.go:82] Alloted 16MB cache and 16 file handles to /root/.ethereum/dapp
I0625 18:22:03.830678 eth/backend.go:172] Protocol Versions: [63 62 61], Network Id: 1
I0625 18:22:03.830896 eth/backend.go:201] Blockchain DB Version: 3
I0625 18:22:03.831305 core/blockchain.go:206] Last header: #96284 [df42d941…] TD=155287189739820945
I0625 18:22:03.831335 core/blockchain.go:207] Last block: #96284 [df42d941…] TD=155287189739820945
I0625 18:22:03.831343 core/blockchain.go:208] Fast block: #96284 [df42d941…] TD=155287189739820945
I0625 18:22:03.832042 p2p/server.go:313] Starting Server
I0625 18:22:05.839885 p2p/discover/udp.go:217] Listening, enode://9229d7dd9ebf8a333d25dc84802364a2e3a86f94cb5de95dde5b9c80ff661a91fce25aeb8bee101e930d2b67dcd93a24f427e59c87c4d5d24bf73c11af38a303@[::]:30303
I0625 18:22:05.840755 node/node.go:366] HTTP endpoint opened: http://0.0.0.0:8545
I0625 18:22:05.840927 node/node.go:421] WebSocket endpoint opened: ws://0.0.0.0:8546
I0625 18:22:05.841028 node/node.go:296] IPC endpoint opened: /root/.ethereum/geth.ipc
I0625 18:22:05.841068 p2p/server.go:556] Listening on [::]:30303
I0625 18:22:35.841961 eth/downloader/downloader.go:320] Block synchronisation started
I0625 18:22:43.511606 core/blockchain.go:964] imported 3 block(s) (0 queued 0 ignored) including 0 txs in 652.77009ms. #96287 [8b26a131 / cf9b8d40]
I0625 18:22:50.214736 core/blockchain.go:964] imported 1540 block(s) (0 queued 0 ignored) including 422 txs in 3.132878621s. #97827 [ffda1eb0 / 6e766ed6]
I0625 18:22:51.742468 core/blockchain.go:964] imported 1075 block(s) (0 queued 0 ignored) including 262 txs in 1.515948524s. #98902 [d3b0fd8c / 8177e6dc]
:
:

Multiple lines are worth looking at more closely:

I0625 18:22:03.820202 cmd/utils/flags.go:601] WARNING: No etherbase set and no accounts found as default
I0625 18:22:03.820453 ethdb/database.go:82] Allowed 128MB cache and 1024 file handles to /root/.ethereum/chaindata
I0625 18:22:05.840755 node/node.go:366] HTTP endpoint opened: http://0.0.0.0:8545
I0625 18:22:05.840927 node/node.go:421] WebSocket endpoint opened: ws://0.0.0.0:8546
I0625 18:22:05.841028 node/node.go:296] IPC endpoint opened: /root/.ethereum/geth.ipc

1) First, “geth” complains that there is no “etherbase” defined. The “etherbase” is the “default ethereum address” that receives the Ether reward when successfully mining blocks, executing smart contracts and including the result in the blockchain. The account, which we wet-up just below, comes also handy when developing contracts later.

2) Next, we see the blockchain data being written to “/root/.ethereum/chaindata” and since we have mounted this directory from our host, we should be able to see the data appearing on our local disk:

:~$ ls -l /opt/docker/ethereum
total 24
drwxr-xr-x 2 root root 12288 jun 25 20:22 chaindata
drwxr-xr-x 2 root root  4096 jun 25 20:22 dapp
srw------- 1 root root     0 jun 25 20:22 geth.ipc
-rw------- 1 root root    64 jun 25 13:04 nodekey
drwxr-xr-x 2 root root  4096 jun 25 20:22 nodes

3) Finally, the HTTP and the web socket endpoints have been open, and the default IPC (Interprocess Communication) file handle “/root/.ethereum/geth.ipc” has been created. This would normally be hidden inside the container, yet we have mounted the outside directory so the file can be used to communicate with this “geth” node.

Remains to define a default Ethereum account. Making use of a Docker command allowing to execute commands inside a running container, this is a piece of cake. Note that this does not open a different container, it connects to the existing that must obvisouly be running.

tasha@ETHEREUM:~$ sudo docker exec -i ethereum geth account new

Your new account is locked with a password. Please give a password. Do not forget this password.
!! Unsupported terminal, password will be echoed.
Passphrase: xxx
Repeat passphrase: xxx
Address: {071a6ac5af1997482f0e5a48630c234fd49f46ac}
tasha@ETHEREUM:~$ 

Don’t forget to note down the Ethereum address and more important, the password. In order to be recognised by the “geth” node, its container must be restarted. The new account can be found in our mounted data directory at:

:~$ sudo ls -l /opt/docker/ethereum/keystore/
-rw------- 1 root root 491 jun 25 20:34 UTC--2016-06-25T18-34-18.768040094Z--071a6ac5af1997482f0e5a48630c234fd49f46ac

The container instance can be stopped using CTRL+C when in interactive mode. Otherwise, invoking the docker STOP command is the right thing to do. Remember, once the container is created it has to be started with:

:~$ sudo docker start -i ethereum

Some final notes on this:

♦ In order to be able to synchronise with the Ethereum network, the host time must precisely match the Ethereum network time. Thus, it might become necessary to perform a synchronisation with the “world time” using the NTP (P)rotocol:

:~$ sudo docker exec ethereum apt-get install -y ntpdate
:~$ sudo docker exec ethereum ntpdate -s ntp.ubuntu.com

♦ While working on my LUBUNTU VM, I experienced a strange behaviour related to the network routing table. As far as I can tell, this is not related to Ethereum. A quick (and very dirty) workaround when this happens, meaning when all containers lost network connectivity, is to re-install Docker. This will not delete images and containers. For follow-up, have a look on this issue #2635.

sudo apt-get remove docker.io
sudo apt-get autoremove --purge
sudo apt-get install docker.io

4) Docker Basics – Part 2

In the current configuration, we have an Ethereum data directory that can be mounted into our containers. This is interesting not because of the blockchain data, which can be accessed by one process only in any case, but rather to get access to the IPC file descriptor that can be used by Ethereum nodes for inter-process communication. So, we could actually continue here without explicitly accessing the network.

Yet, in order to make good use of our full-fledged nicely containerised Ethereum node, understanding how Docker works with networks can greatly help us to find the best solution for our future use cases. As usual, networking can be rather complex, so we stick with the things we need to know in the context of this article.

By default, Docker containers have no access to the host’s network. What would be the point of containerisation if this would be the case? In lieu of, Docker, by default, creates a separate virtual network to which all containers and the host have access: “docker0“. This can be made visible by displaying the host’s network configuration (some lines omitted):

:~$ sudo ifconfig
docker0   Link encap:Ethernet  HWaddr a2:b7:79:14:3f:58  
          inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::f862:e3ff:fe7b:6495/64 Scope:Link

eth0   Link encap:Ethernet  HWaddr 08:00:27:df:91:13  
          inet addr:192.168.1.21  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fedf:9113/64 Scope:Link

Beside our local network “eth0” (or similar, NB. eth for Ethernet, not Ethereum ;), we see the network named “docker0”. It is a different sub-net and 172.17.42.1 is our host IP address on this network. To keep things simple, we are going to use this default “docker0” network that is shared by all containers. Yet know that Docker also permits creating separate logical networks and assign them to specific containers!

Getting the IP address of a container is less straight forward. “ifconfig” is not installed by default in the lightweight Linux images. We could install everything using a command à la “sudo docker exec apt-get -y install tralala”, but we would need to do that in each container again and again. There is an easier solution:

:~$ sudo docker inspect ethereum | grep "IPAddress"
"IPAddress": "172.17.0.9",

Demonstration (Faux Contact;):

:~$ ping 172.17.0.9
PING 172.17.0.9 (172.17.0.9) 56(84) bytes of data.
64 bytes from 172.17.0.9: icmp_seq=1 ttl=64 time=0.026 ms

One important thing to know about this IP is that it changes when a container is restarted. This can be an issue when the IP is passed as command line parameter at creation of another container, as we will see beneath.

By the way, the INSPECT command allows to view anything about a container, system and IO configuration, start-up command line, file paths and mounts, and much more.

5) Connecting a JavaScript console

Next, we would like to interact with the Ethereum main node with the usual “geth” JavaScript console. Easy peasy…

The tricky part is the inter-process communication between the two containerised “geth” nodes. The first option would be to use the IPC file that is present in the mounted data directory. This is the classic way “geth” nodes communicate when running on the same host. All we need is to mount the data directory into the second node, at the same place, so “geth” “sees” the other node as if it would run just one console window away. Both containers use the same IPC descriptor to interconnect:

:~$ sudo docker run -it -v /opt/docker/ethereum:/root/.ethereum ethereum/client-go attach 

Welcome to the Geth JavaScript console!
instance: Geth/v1.4.7-stable/linux/go1.5.1
coinbase: 0x071a6ac5af1997482f0e5a48630c234fd49f46ac
at block: 1725899 (Sat, 18 Jun 2016 10:12:09 UTC)
 datadir: /root/.ethereum
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> web3.net.peerCount
13

The alternatives are the web socket or HTTP interface. This requires to know the IP address of the main node, but that we know how to find out…

:~$ sudo docker run -it --name “ethconsole" ethereum/client-go attach http://172.17.0.9:8545

Welcome to the Geth JavaScript console!
instance: Geth/v1.4.7-stable/linux/go1.5.1
coinbase: 0x071a6ac5af1997482f0e5a48630c234fd49f46ac
at block: 1727805 (Sat, 18 Jun 2016 17:58:07 UTC)
 modules: eth:1.0 net:1.0 rpc:1.0 web3:1.0
> web3.version.network 
"1" 
> web3.net.peerCount 
11 
> web3.fromWei(eth.getBalance(eth.coinbase),"ether") 
0 
> web3.net.peerCount 
11 
> web3.eth.gasPrice 
20000000000 
> web3.eth.mining 
false 
> web3.version.api 
"0.15.3" 
> web3.version.network 
"1" 
> web3.version.ethereum 
"0x3f" 
> exit 

This method has one important handicap: we have to specify the IP address of the main “geth” node as part of the second container’s command line. Once created, this IP declaration cannot be changed anymore (except by playing in the config files, but na!), so this container remains useful only till the target container is restarted and its IP has changed.

The easiest solution is to delete this container and start a new one each time we need the console. Remember – “containers are cheap” with Docker and we can automatise this with a script:

# Get the Ethereum main node container IP
export NODEIP=$(sudo docker inspect ethereum | grep "IPAddress" | cut -d "\"" -f 4)

# Remove the current console container and create a new one pointing to the current target IP
sudo docker rm ethconsole
sudo docker run -it --name ethconsole ethereum/client-go attach http://$NODEIP:8545

Excellent!

6) Run the MIX IDE

Now it’s about to get really interesting. So far, we used pure command line “geth” instances running in separate containers and we made them communicating. Running the Ethereum Mix IDE adds a new challenge: using a graphical user interface.

Docker is not really designed to run UI inside a container, but there are various tricks that we can use to work around. There are three ways that I’m aware of (right now):

1) Installing an entire X11 server into the container and doing some magic as described here. This is heavy, but totally “The Docker Way” as the container remains isolated.

2) Simply installing a VNC server into the container and open the UI remotely. Actually smart, but VNC is not really fun to work with due to the performance. If you like this idea, just duckduck it or have a look at this place.

3) Mounting the Linux host’s X11 IPC (Inter Process Communication) socket into the container at the right place and don’t tell him that’s not its own :) This is elegant, but breaks the separation between containers thus may be a security and stability issue. In our case, no risk, no fun – we got for it! (I got this idea from here and here).

This problem solved, we need to get the Mix IDE into a container. Since no predefined image is provided yet by the Ethereum team, we bravely build our own using a “Dockerfile”.

The first move is to create a directory to store the Dockerfile, let’s say “ethereum-mix-ide”. Next, the file named “Dockerfile” (filename is mandatory) is created inside with the content as given hereunder:

(For the impatient: the Dockerfile on Github)

:~/Docker/ethereum-mix-ide$ nano Dockerfile

# Docker image of the Ethereum Mix IDE.
#
# Version 1
# https://github.com/imifos/ethereum-workspace
# 
# Build image: 
#     sudo docker build -t "imifosmkdir " .
# Create container: 
#     sudo docker run -it --name ethereumix -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY imifos/ethereum-mix-ide
# (Re)Start container (-i optional): 
#     sudo docker start -i ethereumix
#
#
 
FROM ubuntu 
MAINTAINER Tasha CARL <imifos@mm.st>

# 'software-properties-common' is required for add-apt-repository, 
# which we want to use to make us independent from the UBUNTU version 
# when adding external repositories. And while at it, we add sudo and
# firefox so we can fully develop inside our container.
RUN apt-get update 
RUN apt-get -y install software-properties-common \
    && apt-get install -y sudo \
    && apt-get install -y firefox

# Install Mix IDE
RUN add-apt-repository ppa:ethereum/ethereum-qt \
    && add-apt-repository ppa:ethereum/ethereum 
RUN apt-get update
RUN apt-get -y install mix-ide

# Creates an user account under which we can start an X11 session  
# via the mounted X11 IPC channel from the host. This is required 
# since it's not possible (easy) to run an X session as root. 
#
# Making the user sudoer and part of the root group will allow us
# to customise containers on the fly later: 
# $> sudo docker exec -ti ethereumix /bin/sh

ENV HOME /home/mix
RUN useradd --create-home --home-dir $HOME mix \
    && chown -R mix:mix $HOME \
    && usermod -a -G audio,video mix \
    && adduser mix root \
    && echo "mix ALL=(ALL) NOPASSWD: ALL \n" >> /etc/sudoers

WORKDIR $HOME 
USER mix 

ENTRYPOINT mix-ide 

Finally, while inside the directory, we invoke the Docker command that executes this script step by step and saves the final result into our new image. Please do not forget the “.” at the end as it is part of the command line.

:~/Docker/ethereum-mix-ide$ sudo docker build -t "imifos/ethereum-mix-ide" .
:
# (this takes a while)
:

The “Dockerfile” script is quite self-explaining (hey, I wrote comments just for you!!). Our image is based on the latest official UBUNTU image. To start, it installs various tools and the Mix IDE. In order to be able to connect to an X server, the running process cannot be “root”. Thus, the script creates a user named “mix” and gives him sudo rights. These grants will be needful when installing additional packages into the container at a later time. Finally, the “mix-ide” is set as autostart point (this can be overwritten at command line).

Let’s verify the result:

:~/Docker/ethereum-mix$ sudo docker images

REPOSITORY                TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
imifos/ethereum-mix-ide   latest              0eecb790e2dd        3 minutes ago       656.2 MB
ethereum/client-go        latest              26d1f0e94420        45 hours ago        194.6 MB

The image ready and steady, we are going to create a new container that we proudly name “ethereumix“:

:~$ sudo docker run -it --name ethereumix -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY -p 18545:8545 imifos/ethereum-mix-ide

WARNING: This project is using the experimental QML API extensions for QtWebEngine and is therefore tied to a specific QtWebEngine release.
WARNING: The experimental API will change from version to version, or even be removed. You have been warned!

Layout must be attached to Item elements
Layout must be attached to Item elements
Both point size and pixel size set. Using pixel size.
Both point size and pixel size set. Using pixel size.
Both point size and pixel size set. Using pixel size.
qml: [{"id":0,"jsonrpc":"2.0","method":"eth_accounts","params":[]}]
qml: [{"id":1,"jsonrpc":"2.0","method":"eth_gasPrice","params":[]}]
<Unknown File>: QML QQuickLayoutAttached: Binding loop detected for property "preferredWidth"
<Unknown File>: QML QQuickLayoutAttached: Binding loop detected for property "preferredWidth"
<Unknown File>: QML QQuickLayoutAttached: Binding loop detected for property "preferredWidth"
Both point size and pixel size set. Using pixel size.
Both point size and pixel size set. Using pixel size.
Both point size and pixel size set. Using pixel size.
Both point size and pixel size set. Using pixel si…
:
:
# The MIX UI should pop-up right now!

Once built, the container can be restarted anytime with:

:~$ sudo docker start ethereumix

or, in interactive mode to see all the beta version warning messages:

:~$ sudo docker start -i ethereumix

You may have certainly perceived this variation: “-p 18545:8545”. It’s indeed a variation and no typo :) The “geth” main node container binds already it’s port 8545 to the host port 8545, so we need to choose another place to bind to. Given that the Mix IDE container initiates the connection to the “geth” main node, the bind location is of no importance.

-v /tmp/.X11-unix:/tmp/.X11-unix” mounts our local X11 server socket into the container, this time using Docker’s VOLUME functionality, and “-e DISPLAY=$DISPLAY” sets the $DISPLAY environment variable inside the container to the same value that we have on the host. This variable specifies the display to address by the X client and this is mandatory to be set.

Mix IDE does not need access to the Blockchain data, but we need to contact the main Ethereum “geth” server node at the moment we want to deploy Smart Contracts on the blockchain. Again, we need the IP address of the server node:

:~$ sudo docker inspect ethereum | grep "IPAddress"
[sudo] password for tasha: 
        "IPAddress": "172.17.0.1",

This time however, the target address is specified inside the UI and the container can be reused without hassle when the server IP changes.

Ethereum Mix IDE running in a Docker container

The image creation script installs what is require to work with Mix IDE, but we might need to install other tools or tune the container at a later time. As we have already seen above, we can execute commands inside a container and this can even be an interactive shell:

                                              
:~$ sudo docker exec -ti ethereumix /bin/sh

$ id
uid=1000(mix) gid=1000(mix) groups=1000(mix),0(root),29(audio),44(video)

$ sudo apt-cache search firefox
firefox - Safe and easy web browser from Mozilla
firefox-dbg - Safe and easy web browser from Mozilla - debug symbols
firefox-dev - Safe and easy web browser from Mozilla - development files
:
$ exit

Success! :)

The next article will discuss how to build a local test blockchain infrastructure using Docker, which is perfect for such things. It allows to run multiple test chains, as well as to start, stop and to move them around as we please… Et zou!

3 thoughts on “Building an Ethereum Environment with Docker

  1. Hi Natasha, thanks for you perfect post! Where is the next article you plan to discuss how to build a local test blockchain infrastructure using Docker?

    1. Hey :) Thank you. It’s planned to write it, but I haven’t started yet. You can maybe get around with taking the article about making a test blockchain (in general, older article here) and do this in Docker? Best,T.

Leave a Reply

Your email address will not be published. Required fields are marked *