Fork me 🍴

Willian Antunes

How to set up an Ethereum Node with Light Mode using Docker

12 minute read

ethereum, web3, cryptocurrency, nodes

Table of contents
  1. Choosing a client
  2. Creating a Dockerfile
  3. Creating the Compose file
    1. Important thing
  4. Running the node and exploring it
    1. GraphQL API
    2. Folder structure
  5. Conclusion

I already use Bitcoin Core for studying purposes. You can check how I've been installing it. However, with the rise of Web3, one of the most famous buzzwords currently circulating on the internet, I thought it was time to take another step into something different, like understanding how Ethereum works so one can interact with it to create applications. As it always worked for me, I tend to go over practice while reading a thing's documentation. To avoid doing things blindly as the topic is totally different from the ones I'm used to, I read all the foundational topics about Ethereum Development before touching anything.

Choosing a client

So, on the Nodes and Clients page, I understood that I needed a client application to run a node on my own, pretty much like Bitcoin needs to, and there are many options. So which one should I pick? Just to list some of them:

Just because it's the original implementation of the Ethereum protocol, I chose Go Ethereum (Geth for short). Beyond that, it's the most widespread client, so it's a good idea to start with something solid and well-known.

Its documentation describes many container images that you can use, though lacking detailed explanations about how to execute it with an opinionated configuration through Compose. Let's create one!

Creating a Dockerfile

Before running the container to test things, first, I looked at its Dockerfile. Unfortunately, it creates no dedicated user and runs as root by default, which is a problem in terms of security. Run the command below and see it by yourself:

▶ docker run --entrypoint /bin/sh --rm -it ethereum/client-go:stable
/ # whoami
root

After testing in the container itself, I created the following Dockerfile:

ARG CUSTOM_USER=aladdin

FROM ethereum/client-go:stable

ARG CUSTOM_USER

RUN addgroup -S "$CUSTOM_USER"

RUN adduser \
    --disabled-password \
    --gecos "" \
    --ingroup "$CUSTOM_USER" \
    "$CUSTOM_USER"

WORKDIR /home/$CUSTOM_USER

USER "$CUSTOM_USER"

Creating the Compose file

To understand all the options available in Geth, we can execute the following command:

docker run --rm -it ethereum/client-go:stable --help

The idea is to run a node with synchronization mode as light and interact with the Ethereum network through some API; thus, I came up with the following Compose file:

version: "3.8"

services:
  ethereum-geth:
    build:
      context: .
    user: "$USER:$GROUP"
    volumes:
      - ./eth:/home/aladdin/.ethereum
      # https://github.com/compose-spec/compose-spec/blob/e8db8022c0b2e3d5eb007d629ff684cbe49a17a4/spec.md#short-syntax-4
      - '/etc/passwd:/etc/passwd:ro'
      - '/etc/group:/etc/group:ro'
    ports:
      # HTTP server / GraphQL API
      - 8545:8545
      # WebSocket
      - 8546:8546
      # Network listening port (P2P networking)
      - 30303:30303
    command:
      [
        # Blockchain sync mode ("snap", "full" or "light")
        "--syncmode=light",
        # Megabytes of memory allocated to internal caching
        "--cache=8192",
        # Enable the WS-RPC server
        "--ws",
        "--ws.addr=0.0.0.0",
        # Enable the HTTP-RPC server
        "--http",
        "--http.addr=0.0.0.0",
        "--http.vhosts=*",
        # Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.
        "--graphql",
        "--graphql.vhosts=*",
        # Enable metrics collection and reporting
        "--metrics",
        # Ethereum mainnet
        "--mainnet",
        # Maximum number of network peers (network disabled if set to 0) (default: 50)
        "--maxpeers=30",
        # Data directory for the databases and keystore (default: "/root/.ethereum")
        "--datadir=/home/aladdin/.ethereum"
      ]

If you look at the volume section, you'll see it will mount the directory responsible for storing the database and the keystore. If we don't do that, we won't be able to keep the state, so it's a must. By the way, the other two volumes are just a trick to execute the container using my current user.

Important thing

It's too open the configuration for Geth as we are just doing this for testing purposes. Nevertheless, if you're going to use it for real, like creating a wallet and storing your ether tokens, do not forget to harden your computer; for instance, use a firewall to avoid someone using the RPC server.

Running the node and exploring it

Finally, we're ready to go! Let's run it. You'll see something like this:

▶ mkdir -p eth && USER=$(id -u) GROUP=$(id -g) docker-compose up
Recreating how-to-set-up-ethereum-node-using-docker_ethereum-geth_1 ... done
Attaching to how-to-set-up-ethereum-node-using-docker_ethereum-geth_1
ethereum-geth_1  | INFO [12-26|19:55:22.225] Starting Geth on Ethereum mainnet... 
ethereum-geth_1  | INFO [12-26|19:55:22.225] Enabling metrics collection 
ethereum-geth_1  | INFO [12-26|19:55:22.227] Maximum peer count                       ETH=0 LES=10 total=30
ethereum-geth_1  | INFO [12-26|19:55:22.227] Smartcard socket not found, disabling    err="stat /run/pcscd/pcscd.comm: no such file or directory"
ethereum-geth_1  | INFO [12-26|19:55:22.227] Set global gas cap                       cap=50,000,000
ethereum-geth_1  | INFO [12-26|19:55:22.233] Allocated cache and file handles         database=/home/aladdin/.ethereum/geth/lightchaindata cache=4.00GiB handles=524,288
ethereum-geth_1  | INFO [12-26|19:55:22.246] Allocated cache and file handles         database=/home/aladdin/.ethereum/geth/les.client cache=16.00MiB handles=16
ethereum-geth_1  | INFO [12-26|19:55:22.249] Writing custom genesis block 
ethereum-geth_1  | INFO [12-26|19:55:22.372] Persisted trie from memory database      nodes=12356 size=1.78MiB time=28.150243ms gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
ethereum-geth_1  | INFO [12-26|19:55:22.373] Initialised chain configuration          config="{ChainID: 1 Homestead: 1150000 DAO: 1920000 DAOSupport: true EIP150: 2463000 EIP155: 2675000 EIP158: 2675000 Byzantium: 4370000 Constantinople: 7280000 Petersburg: 7280000 Istanbul: 9069000, Muir Glacier: 9200000, Berlin: 12244000, London: 12965000, Arrow Glacier: 13773000, MergeFork: <nil>, Engine: ethash}"
ethereum-geth_1  | INFO [12-26|19:55:22.373] Disk storage enabled for ethash caches   dir=/home/aladdin/.ethereum/geth/ethash count=3
ethereum-geth_1  | INFO [12-26|19:55:22.373] Disk storage enabled for ethash DAGs     dir=/home/willianantunes/.ethash        count=2
ethereum-geth_1  | INFO [12-26|19:55:22.373] Added trusted checkpoint                 block=13,565,951 hash=8aa8e6..200083
ethereum-geth_1  | INFO [12-26|19:55:22.373] Loaded most recent local header          number=0 hash=d4e567..cb8fa3 td=17,179,869,184 age=52y8mo3w
ethereum-geth_1  | INFO [12-26|19:55:22.373] Configured checkpoint oracle             address=0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a signers=5 threshold=2
ethereum-geth_1  | INFO [12-26|19:55:22.373] Gasprice oracle is ignoring threshold set threshold=2
ethereum-geth_1  | WARN [12-26|19:55:22.374] Error reading unclean shutdown markers   error="leveldb: not found"
ethereum-geth_1  | INFO [12-26|19:55:22.376] Starting peer-to-peer node               instance=Geth/v1.10.14-stable-11a3a350/linux-amd64/go1.17.5
ethereum-geth_1  | INFO [12-26|19:55:22.390] New local node record                    seq=1,640,548,522,390 id=b2923f7363ed1061 ip=127.0.0.1 udp=30303 tcp=30303
ethereum-geth_1  | INFO [12-26|19:55:22.391] Started P2P networking                   self=enode://43b021b78b55472cd2fc231591338ce58c88f07152ba8d9636fca7ce02131c382dc8d74e5653d16c70f38a99ee93836b18d9e150c06aada7d6429e9aad69933d@127.0.0.1:30303
ethereum-geth_1  | INFO [12-26|19:55:22.391] IPC endpoint opened                      url=/home/aladdin/.ethereum/geth.ipc
ethereum-geth_1  | INFO [12-26|19:55:22.392] HTTP server started                      endpoint=[::]:8545 prefix= cors= vhosts=*
ethereum-geth_1  | INFO [12-26|19:55:22.392] GraphQL enabled                          url=http://[::]:8545/graphql
ethereum-geth_1  | INFO [12-26|19:55:22.392] GraphQL UI enabled                       url=http://[::]:8545/graphql/ui
ethereum-geth_1  | INFO [12-26|19:55:22.392] WebSocket enabled                        url=ws://[::]:8546
ethereum-geth_1  | WARN [12-26|19:55:22.392] Light client mode is an experimental feature

While it is running, you can explore the client issuing many commands; for instance, you can create a new account (wallet):

▶ USER=$(id -u) GROUP=$(id -g) docker-compose run ethereum-geth --datadir /home/aladdin/.ethereum account new
Creating how-to-set-up-ethereum-node-using-docker_ethereum-geth_run ... done
INFO [12-26|19:56:14.216] Maximum peer count                       ETH=50 LES=0 total=50
INFO [12-26|19:56:14.217] Smartcard socket not found, disabling    err="stat /run/pcscd/pcscd.comm: no such file or directory"
Your new account is locked with a password. Please give a password. Do not forget this password.
Password: 
Repeat password: 

Your new key was generated

Public address of the key:   0x5926553a65bACF63B0194b7Aeb2928CAbf43C4aB
Path of the secret key file: /home/aladdin/.ethereum/keystore/UTC--2021-12-26T19-56-35.276306583Z--5926553a65bacf63b0194b7aeb2928cabf43c4ab

- You can share your public address with anyone. Others need it to interact with you.
- You must NEVER share the secret key with anyone! The key controls access to your funds!
- You must BACKUP your key file! Without the key, it's impossible to access account funds!
- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!

You can start an interactive JavaScript environment connected to the light node using the following:

▶ USER=$(id -u) GROUP=$(id -g) docker-compose run ethereum-geth --datadir /home/aladdin/.ethereum attach
Creating how-to-set-up-ethereum-node-using-docker_ethereum-geth_run ... done
Welcome to the Geth JavaScript console!

instance: Geth/v1.10.14-stable-11a3a350/linux-amd64/go1.17.5
at block: 13590527 (Wed Nov 10 2021 20:06:06 GMT+0000 (UTC))
 datadir: /home/aladdin/.ethereum
 modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 les:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 vflux:1.0 web3:1.0

To exit, press ctrl-d or type exit
> 

Then you'll have a variety of commands available, such as (just copy one and execute it by yourself):

eth.accounts
personal.newAccount("this-is-the-password-that-encrypts-your-private-key")
personal.listWallets
eth.accounts
eth.getBlockByHash('0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3')
web3.currentProvider

You can easily find documentation for these commands, such as for web3.

GraphQL API

If you access http://127.0.0.1:8545/graphql/ui, you can execute a query like the following:

{
  block(number: 0) {
    hash
    miner {
      address
      transactionCount
      balance
    }
  }
}

Then the following is returned:

{
  "data": {
    "block": {
      "hash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",
      "miner": {
        "address": "0x0000000000000000000000000000000000000000",
        "transactionCount": "0x0",
        "balance": "0x0"
      }
    }
  }
}

Check out more example use cases for interacting with the GraphQL API.

Folder structure

Everything is being saved in eth folder. So how is its structure after the client runs for a while? Given we created some wallets previously, here's the output of the tree command on it:

▶ tree eth
eth
├── geth
│   ├── ethash
│   │   ├── cache-R23-1bccaca36bfde6e5
│   │   ├── cache-R23-70de525794a9c888
│   │   └── cache-R23-74efb201dab10dd5
│   ├── les.client
│   │   ├── 000001.log
│   │   ├── CURRENT
│   │   ├── LOCK
│   │   ├── LOG
│   │   └── MANIFEST-000000
│   ├── lightchaindata
│   │   ├── 000002.log
│   │   ├── 000004.ldb
│   │   ├── CURRENT
│   │   ├── LOCK
│   │   ├── LOG
│   │   └── MANIFEST-000000
│   ├── LOCK
│   ├── nodekey
│   └── nodes
│       ├── 000001.log
│       ├── CURRENT
│       ├── LOCK
│       ├── LOG
│       └── MANIFEST-000000
├── geth.ipc
├── history
└── keystore
    ├── UTC--2021-12-26T19-56-35.276306583Z--5926553a65bacf63b0194b7aeb2928cabf43c4ab
    └── UTC--2021-12-26T19-59-03.606317985Z--fa11874442f3e8681abc3b7ea95d5951393fefc7

Conclusion

Something important to say for newcomers is that what we did here is just a foundation for more complex things, such as setting up an entire private network on our own. By the way, what we did here can be used to set up a testnet. Then we can use Faucets and utilize them without worrying about losing money. This is quite interesting if you want to explore the technology itself before using it as a way to store real tokens. Maybe I'll create more posts showing how to accomplish such things.

See everything we did here on GitHub.

Posted listening to The Breeze of Petal Meadows, Paper Mario: The Thousand-Year Door 🎶.


Have you found any mistakes 👀? Feel free to submit a PR editing this blog entry 😄.