Espresso Mainnet 1 Node Setup Guide
This guide covers deploying an Espresso Mainnet 1 Regular (Non-DA) Node using Docker Compose. For validator registration and key generation, see the Validator Registration guide first.
Prerequisites
System Requirements
| Component | Regular Node | DA Node |
|---|---|---|
| CPU | 1+ Core | 4+ Core (node) + 2 Core (DB) |
| RAM | 8 GB | 8 GB (node) + 4 GB (DB) |
| Storage | Negligible (kilobytes) | 1.2 TB SSD (archive) / 100 GB (pruning) |
| OS | Ubuntu 20.04/22.04 LTS | Ubuntu 20.04/22.04 LTS |
| Network | Stable internet, public IP | Stable internet, public IP |
Software Requirements
- Docker Engine 24+ and Docker Compose V2
- Ethereum L1 RPC endpoints (HTTP and WebSocket)
Before You Begin
Ensure you have completed:
- ✅ Generated consensus keys (BLS + Schnorr)
- ✅ Registered your validator in the Stake Table
- ✅ Delegated stake to your node
Network Ports
| Port | Protocol | Purpose | Required |
|---|---|---|---|
30000 | UDP + TCP | Libp2p P2P | ✅ Must be open and reachable |
26000 | TCP | API / Metrics / Healthcheck | Recommended for monitoring |
The Libp2p port (default 30000) must be publicly reachable on both UDP and TCP. Without this, your node cannot communicate with other validators.
Step 1: Prepare Project Directory
sudo -i
mkdir -p /data/espresso/{data,keys}
cd /data/espresso
Step 2: Create Environment File
###############################################################################
# Espresso Mainnet 1 Node - Environment Configuration
###############################################################################
#--- Ethereum L1 RPC -----------------------------------------------------------
# HTTP JSON-RPC endpoint (required)
ESPRESSO_SEQUENCER_L1_PROVIDER=https://mainnet.infura.io/v3/<YOUR-API-KEY>
# WebSocket endpoint (required for Mainnet 1 operations)
ESPRESSO_SEQUENCER_L1_WS_PROVIDER=wss://mainnet.infura.io/v3/<YOUR-API-KEY>
#--- Networking ----------------------------------------------------------------
# Your server's public IP address (required, other nodes need to reach you)
ESPRESSO_PUBLIC_IP=YOUR_SERVER_PUBLIC_IP
# Libp2p P2P port (UDP + TCP, must be open in firewall)
ESPRESSO_LIBP2P_PORT=30000
# Host port for the API (metrics, healthcheck)
ESPRESSO_API_PORT=26000
Replace <YOUR-API-KEY> with your actual Infura/Alchemy API key and YOUR_SERVER_PUBLIC_IP with your server's public IP address.
Step 3: Place Your Key File
Copy your generated key file into the keys/ directory:
# If you generated keys with the keygen command:
cp /path/to/your/generated/keys/0.env /data/espresso/keys/0.env
chmod -R 0700 /data/espresso/keys
The key file should contain:
ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=BLS_SIGNING_KEY~<your-bls-private-key>
ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=SCHNORR_SIGNING_KEY~<your-schnorr-private-key>
Protect this file — it contains your consensus private keys. Set strict permissions (chmod 600) and never commit it to version control.
Step 4: Create Docker Compose File
###############################################################################
# Espresso Mainnet 1 - Regular (Non-DA) Node
# Docs: https://docs.espressosys.com/network/guides/node-operators/running-a-mainnet-1-node
###############################################################################
services:
espresso-node:
image: ghcr.io/espressosystems/espresso-sequencer/sequencer:20260407
container_name: espresso-sequencer
restart: unless-stopped
command: sequencer -- http -- catchup -- status
environment:
#---------------------------------------------------------------------------
# Fixed / Network-wide (DO NOT CHANGE)
#---------------------------------------------------------------------------
ESPRESSO_SEQUENCER_CDN_ENDPOINT: cdn.main.net.espresso.network:1737
ESPRESSO_STATE_RELAY_SERVER_URL: https://state-relay.main.net.espresso.network
ESPRESSO_SEQUENCER_GENESIS_FILE: /genesis/mainnet.toml
ESPRESSO_SEQUENCER_EMBEDDED_DB: "true"
RUST_LOG: "warn,libp2p=off"
RUST_LOG_FORMAT: json
# State peers (at least one required)
ESPRESSO_SEQUENCER_STATE_PEERS: https://query.main.net.espresso.network
# Config peers (required for first-time startup)
ESPRESSO_SEQUENCER_CONFIG_PEERS: https://cache.main.net.espresso.network
#---------------------------------------------------------------------------
# Operator-specific (FILL THESE IN via .env file)
#---------------------------------------------------------------------------
ESPRESSO_SEQUENCER_L1_PROVIDER: ${ESPRESSO_SEQUENCER_L1_PROVIDER:?set ESPRESSO_SEQUENCER_L1_PROVIDER in .env}
ESPRESSO_SEQUENCER_L1_WS_PROVIDER: ${ESPRESSO_SEQUENCER_L1_WS_PROVIDER:?set ESPRESSO_SEQUENCER_L1_WS_PROVIDER in .env}
ESPRESSO_SEQUENCER_API_PORT: 80
ESPRESSO_SEQUENCER_STORAGE_PATH: /data/store/
ESPRESSO_SEQUENCER_KEY_FILE: /keys/0.env
ESPRESSO_SEQUENCER_LIBP2P_BIND_ADDRESS: 0.0.0.0:${ESPRESSO_LIBP2P_PORT:-30000}
ESPRESSO_SEQUENCER_LIBP2P_ADVERTISE_ADDRESS: ${ESPRESSO_PUBLIC_IP}:${ESPRESSO_LIBP2P_PORT:-30000}
ports:
# API port (metrics, healthchecks)
- "${ESPRESSO_API_PORT:-26000}:80"
# Libp2p P2P port (UDP)
- "${ESPRESSO_LIBP2P_PORT:-30000}:${ESPRESSO_LIBP2P_PORT:-30000}/udp"
# Libp2p P2P port (TCP)
- "${ESPRESSO_LIBP2P_PORT:-30000}:${ESPRESSO_LIBP2P_PORT:-30000}/tcp"
volumes:
# Persistent storage for embedded SQLite DB
- ./data:/data/store
# Key file (mounted read-only)
- ./keys:/keys:ro
logging:
driver: json-file
options:
max-size: "100m"
max-file: "5"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
Environment Variables Reference
Fixed (Network-wide)
| Variable | Value | Description |
|---|---|---|
ESPRESSO_SEQUENCER_CDN_ENDPOINT | cdn.main.net.espresso.network:1737 | CDN for block delivery |
ESPRESSO_STATE_RELAY_SERVER_URL | https://state-relay.main.net.espresso.network | State relay server |
ESPRESSO_SEQUENCER_GENESIS_FILE | /genesis/mainnet.toml | Genesis config (built into image) |
ESPRESSO_SEQUENCER_EMBEDDED_DB | true | Use embedded SQLite for Regular Node |
ESPRESSO_SEQUENCER_STATE_PEERS | https://query.main.net.espresso.network | State sync peers |
ESPRESSO_SEQUENCER_CONFIG_PEERS | https://cache.main.net.espresso.network | Config bootstrap peers (first startup only) |
Operator-specific (.env file)
| Variable | Example | Description |
|---|---|---|
ESPRESSO_SEQUENCER_L1_PROVIDER | https://mainnet.infura.io/v3/<KEY> | HTTP JSON-RPC for Ethereum mainnet (required) |
ESPRESSO_SEQUENCER_L1_WS_PROVIDER | wss://mainnet.infura.io/v3/<KEY> | WebSocket for Ethereum Mainnet |
ESPRESSO_PUBLIC_IP | 203.0.113.1 | Server public IP address |
ESPRESSO_LIBP2P_PORT | 30000 | Libp2p P2P port (UDP + TCP) |
ESPRESSO_API_PORT | 26000 | Host port for API / metrics / healthcheck |
Alternative to key file: Instead of ESPRESSO_SEQUENCER_KEY_FILE, you can set the two key environment variables directly:
ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=BLS_SIGNING_KEY~...ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=SCHNORR_SIGNING_KEY~...
Step 5: Open Firewall Ports
# UFW (Ubuntu)
ufw allow 30000/udp
ufw allow 30000/tcp
ufw allow 26000/tcp # API/metrics/healthcheck for monitoring
# Or iptables
iptables -A INPUT -p udp --dport 30000 -j ACCEPT
iptables -A INPUT -p tcp --dport 30000 -j ACCEPT
Step 6: Launch the Node
Ensure the data and keys directories exist in your current directory (/data/espresso) before starting the node. If Docker creates them automatically, it may assign incorrect permissions. (You should have created these in Step 1).
cd /data/espresso
# Start the node in detached mode
docker compose up -d
# Verify the container is running
docker compose ps
Expected output:
NAME IMAGE STATUS PORTS
espresso-sequencer ghcr.io/espressosystems/espresso-sequencer/sequencer:20260407 Up 10 seconds 0.0.0.0:26000->80/tcp, 0.0.0.0:30000->30000/udp+tcp
Step 7: Monitor the Node
Check Logs
# Follow real-time logs
docker compose logs -f
# Check last 100 lines
docker compose logs --tail 100
Healthcheck
curl -f http://localhost:26000/healthcheck
curl -s http://localhost:26000/status | jq .
Key Log Messages
On first startup, look for these indicators:
| Log Message | Meaning |
|---|---|
Fetching config from peers | Node is bootstrapping network config |
Connected to CDN | Successfully joined the CDN network |
Joined consensus | Node is participating in consensus |
Received proposal | Node is receiving and processing blocks |
First startup may take a few minutes as the node fetches network configuration from ESPRESSO_SEQUENCER_CONFIG_PEERS. After the first successful join, this config is cached locally and the config peers parameter is no longer needed on restarts.
Directory Structure
/data/espresso/
├── .env # Environment variables
├── docker-compose.yml # Service definition
├── data/ # Persistent SQLite DB storage
└── keys/
└── 0.env # Consensus private keys (BLS + Schnorr)
Keep /data/espresso/data persistent across restarts. Keep /data/espresso/keys/0.env backed up offline and excluded from Git; it contains the BLS and Schnorr private keys used by the node.
Operations
Restart the Node
docker compose restart
Stop the Node
docker compose down
Upgrade the Node
When a new release is published:
- Update the image tag in
docker-compose.yml - Pull and restart:
docker compose pull
docker compose up -d
View Resource Usage
docker stats espresso-sequencer --no-stream
Backup
The key file is the most critical piece to back up:
# Back up keys (store securely, encrypted)
cp /data/espresso/keys/0.env /secure-backup/espresso-keys-backup.env
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
Connection refused on P2P | Firewall blocking UDP/TCP 30000 | Open port 30000 on both protocols |
Failed to fetch config | First-time config peers unreachable | Verify internet connectivity and DNS resolution |
| Node not in active set | Insufficient delegated stake | Need to be in top 100 by delegated ESP tokens |
L1 provider error | Invalid or rate-limited RPC | Check your Infura/Alchemy API key and plan limits |
| Healthcheck failing | Node still syncing | Wait for initial sync to complete (~2-5 min) |
Debug Logging
To enable verbose logging temporarily:
docker compose down
# Edit docker-compose.yml: change RUST_LOG to "info,libp2p=off"
docker compose up -d
docker compose logs -f
Mainnet 1 Key Changes from Mainnet 0
If migrating from Mainnet 0:
| Change | Details |
|---|---|
| Container image | Updated to 20260407 |
| Peer URLs | Use v1 APIs for all peer URLs |
| stateAPI module | Now auto-enabled — do NOT specify state on command line |
| L1 WS Provider | ESPRESSO_SEQUENCER_L1_WS_PROVIDER should be configured alongside ESPRESSO_SEQUENCER_L1_PROVIDER |