Building an Ethereum Devnet with Reth and Lighthouse

We are a 24/7 globally distributed team

Introduction

Ethereum Devnets are fully customisable private networks designed for development purposes, allowing complete control over configurations, validators, and network parameters. This guide walks through the process of setting up an Ethereum L1 Devnet using Reth as the execution client and Lighthouse as the consensus client.

Why do we need it

Having a safe, customisable environment for testing is essential. That’s where a devnet comes in. A development network is a private Ethereum network that gives developers full control over how it runs. It’s an ideal playground for building, experimenting, and debugging; without the risks or costs of deploying on a live chain.

We created our devnet using Reth and Lighthouse to serve as a reliable and flexible testing ground for L1 infrastructure. With this setup, we can test new features, simulate validator behavior, and tweak chain parameters in a completely isolated environment. This allows us to catch issues early, optimize configurations, and ensure our changes won’t introduce instability when rolled out to production.

One of the main problems the devnet solves is visibility and control. Public testnets are often shared by many users and can be unpredictable or unavailable. With our own devnet, we have a consistent and fully observable environment where we can monitor performance metrics, fine-tune validator behavior, and rapidly iterate on upgrades or custom logic.

Our devnet acts as a powerful internal tool for research and development. Whether we’re onboarding new validators, testing smart contract deployments, or experimenting with protocol-level changes, it gives us the confidence to move fast without breaking things.

Setting Up the L1 Node

Step 1: Install Dependencies

  • curl
  • docker-ce
  • docker-ce-cli
  • containerd.io
  • docker-buildx-plugin
  • docker-compose-plugin
  • build-essential
  • foundry (optional if you want to use cool tool like cast)

Step 2: Generate the Genesis File Copy the value file to the current directory for future use. And then replace the GENESIS_TIMESTAMP with the future time because you need to get the node running before the genesis time. the following is an ansible instruction

    - name: Get future timestamp
      shell: date +%s -d "+10 minutes"
      register: genesis_timestamp

    - name: Update GENESIS_TIMESTAMP in values.env
      lineinfile:
        path: /home/ubuntu/config/values.env
        regexp: '^export GENESIS_TIMESTAMP='
        line: "export GENESIS_TIMESTAMP=\"{{ genesis_timestamp.stdout }}\""
        create: yes

Then use the ethereum-genesis-generator from ethpandaops to generate the genesis file.

    - name: Generate genesis
      command: >
        docker run --rm -it \
        -u 0 \
        -v /home/ubuntu/output:/data \
        -v /home/ubuntu/config/values.env:/config/values.env \
        ethpandaops/ethereum-genesis-generator:latest all

Step 3: Create a Validator keystores In order to get the network working we need a validator as well. Use the following to generate the validator keystores

    - name: Create validator keystores
      command: >
        docker run --rm -t --entrypoint eth2-val-tools \
        --platform linux/amd64 \
        -v /home/ubuntu/output/validator_keys:/output \
        ethpandaops/ethereum-genesis-generator:{{ genesis_generator_version }} \
        keystores --insecure \
        --out-loc="/output/lighthouse-reth-1" \
        --source-min="0" \
        --source-max="200" \
        --source-mnemonic=$SOURCE-MNEMONIC

Step 4: Generate the JWT for communication between EC and BC

    - name: Generate JWT secret
      shell: |
        openssl rand -hex 32 | tr -d "\n" > jwt.hex
      args:
        executable: /bin/bash
        chdir: /home/ubuntu/network-config

Step 5: Configure Docker Compose

docker compose -f docker-compose.yml up -d

# docker-compose.yml
services:
  reth:
    image: ghcr.io/paradigmxyz/reth:latest
    container_name: reth
    command: >
      node
      --chain /config/genesis.json
      --datadir /root/.reth
      --http
      --http.addr 0.0.0.0
      --http.api all
      --ws
      --ws.api all
      --disable-discovery
      --authrpc.jwtsecret /secrets/jwt.hex
      --authrpc.addr 0.0.0.0
      --authrpc.port 8551
      --metrics 0.0.0.0:9003
    volumes:
      - ./output/metadata/genesis.json:/config/genesis.json
      - ./network-config/jwt.hex:/secrets/jwt.hex
      - reth_data:/root/.reth
    ports:
      - "8545:8545"   # HTTP-RPC API
      - "30303:30303" # P2P
      - "8551:8551"   # Engine
      - "9003:9003"   # Metrics

  lighthouse:
    image: sigp/lighthouse:latest
    container_name: lighthouse
    command: >
      lighthouse beacon_node
      --datadir /root/.lighthouse
      --listen-address 0.0.0.0
      --port 9000
      --http
      --http-address 0.0.0.0
      --http-port 5052
      --slots-per-restore-point 8192
      --disable-packet-filter
      --execution-endpoint http://reth:8551
      --execution-jwt /secrets/jwt.hex
      --testnet-dir /config
      --metrics
      --metrics-address 0.0.0.0
      --metrics-allow-origin=*
      --metrics-port 5054
      --enable-private-discovery
      --debug-level debug
      --allow-insecure-genesis-sync
    volumes:
      - ./output/metadata:/config
      - ./output/metadata/genesis.ssz:/config/genesis.ssz
      - ./network-config/jwt.hex:/secrets/jwt.hex
      - lighthouse_data:/root/.lighthouse
    depends_on:
      - reth
    ports:
      - "9000:9000"  # P2P
      - "5052:5052"  # HTTP API
      - "5054:5054"  # Metrics

  lighthouse_validator:
    image: sigp/lighthouse:latest
    container_name: lighthouse_validator
    command: >
      lighthouse validator_client
      --testnet-dir /config
      --validators-dir /root/.lighthouse/validator_keys/keys
      --secrets-dir /root/.lighthouse/validator_keys/secrets
      --suggested-fee-recipient=$suggested-fee-recipient
      --init-slashing-protection
      --beacon-nodes http://lighthouse:5052
      --metrics
      --metrics-address=0.0.0.0
      --metrics-allow-origin=*
      --metrics-port 5064
      --graffiti $graffiti

    volumes:
      - ./output/metadata:/config
      - ./output/validator_keys/lighthouse-geth-1/keys:/root/.lighthouse/validator_keys/keys
      - ./output/validator_keys/lighthouse-geth-1/secrets:/root/.lighthouse/validator_keys/secrets
      - ./validators:/root/.lighthouse/validators
      - lighthouse_data:/root/.lighthouse
    depends_on:
      - lighthouse
    ports:
      - "5062:5062"  # HTTP API
      - "5064:5064"  # Metrics

volumes:
  reth_data:
  lighthouse_data:

Step 6: Test the Network

Use cast to send transactions and verify that the network is producing blocks

cast send 0xAF48eDe4D7a1692fFc1e16033C94762fBdE6823B \
    --value 1ether \
    --rpc-url http://localhost:8545 \
    --mnemonic "$(cat ./mnemonic.txt)" \
    --mnemonic-derivation-path "m/44'/60'/0'/0/1"

The transaction will go through if the devnet is set up properly.

Wrapping it up

You've now got your own fully customised Ethereum L1 Devnet running with Reth and Lighthouse. Whether you're testing new features, tweaking validator settings, or experimenting with chain configurations, this setup gives you full control over your network.

But we didn’t stop there. We also set up real-time monitoring and dashboards to keep track of network performance, making debugging and performance tuning much easier. If you’re interested in building your own Devnet, need help with monitoring, or just want to help over blockchain infra, feel free to contact us!

Subscribe to get
the latest updates

Our HQ Locations

Copenhagen Denmark

Melbourne Australia

Tallinn Estonia

Privacy policy||

Copyright © 2025. All rights reserved