# base/node User Guide

Run a full node on the **Base Network** — Coinbase's Ethereum Layer 2 built on the [OP Stack](https://docs.optimism.io/). The `base/node` repository ships Docker builds, env templates, and entrypoint scripts that compose an execution client (**Reth**, **Geth**, or **Nethermind**) with a consensus client (**op-node** or **base-consensus**) into a reproducible Base node deployment.

This guide walks you through getting a Base node up and syncing — from the lowest-friction `docker compose up --build` path through configuration, day-to-day operations, troubleshooting, and FAQ.

---

## 1. Quick Start

Get a Base mainnet node syncing in four steps.

### 1.1 What You Need

- Docker Engine 20.10+ with the Compose v2 plugin (`docker compose`, two words)
- An Ethereum L1 RPC endpoint (your own node, Alchemy, Infura, QuickNode, etc.)
- An Ethereum L1 beacon endpoint and a beacon archiver (CL node serving historical blob sidecars)
- ~32 GB RAM, NVMe SSD with room for `2 × current chain size + snapshot + 20 %` buffer

### 1.2 Lowest-Friction Path

```bash
# 1. Clone the repo
git clone https://github.com/base/node.git
cd node

# 2. Edit .env.mainnet — set your L1 endpoints:
#      OP_NODE_L1_ETH_RPC=<your-l1-rpc>
#      OP_NODE_L1_BEACON=<your-l1-beacon>
#      OP_NODE_L1_BEACON_ARCHIVER=<your-l1-beacon-archiver>

# 3. (Optional) Pick a client — reth, geth, or nethermind.
#    The shipped .env defaults to USE_BASE_CONSENSUS=true which only works with reth.
export CLIENT=reth

# 4. Start the stack
docker compose up --build
```

```text
[+] Building 0.0s (0/0)
[+] Running 2/2
 Container node-execution-1  Created
 Container node-node-1       Created
Attaching to execution-1, node-1
execution-1  | Starting reth with additional args:
node-1       | Using Base Client
...
```

That's it. Two containers come up: `execution` (the chosen client) and `node` (the consensus client). Chain data lands in `./reth-data/` (or `./geth-data/`, `./nethermind-data/`) on the host.

> [!TIP]
> A first-time sync from genesis takes days. To get to chain head in hours, drop a published snapshot into `HOST_DATA_DIR` before starting — see Section 5.5 (Snapshot/Restore) and the official snapshots index at [basechaindata.vercel.app](https://basechaindata.vercel.app).

> [!IMPORTANT]
> Without L1 endpoints filled into the env file, the consensus client will fail to start syncing. Set `OP_NODE_L1_ETH_RPC`, `OP_NODE_L1_BEACON`, and `OP_NODE_L1_BEACON_ARCHIVER` (and the `BASE_NODE_*` equivalents if you use `USE_BASE_CONSENSUS=true`) before launching.

### 1.3 Verify It Is Running

```bash
curl -s -X POST http://localhost:8545 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
```

```text
{"jsonrpc":"2.0","id":1,"result":"0x1a3b4c5"}
```

Anything other than `"0x0"` means the execution client is alive and at a real block number. See Section 4 for full verification including peer count and consensus sync.

### 1.4 What To Try Next

- Section 2 — Install all prerequisites (Docker, dependencies)
- Section 3 — Configure for mainnet vs Sepolia testnet, ports, logs, metrics
- Section 4 — Day-to-day commands (logs, restart, health checks)
- Section 5 — Recipe-driven tasks (switch network, restore snapshot, upgrade)
- Section 6 — Troubleshooting common errors

---

## 2. Installation

### 2.1 System Requirements

| Resource | Minimum | Recommended |
|---|---|---|
| CPU | Modern multicore | 8+ cores |
| RAM | 32 GB | 64 GB |
| Disk | NVMe SSD | NVMe SSD (RAID 0 across local drives) |
| Storage size | `2 × current chain size + snapshot size + 20 %` | same |
| Filesystem | ext4 | ext4 |
| Docker | Engine 20.10+ with Compose v2 plugin | same |
| Network | Public IP, ports 30303 + 9222 (TCP+UDP) reachable | same |

Live chain size is tracked at [base.org/stats](https://base.org/stats); snapshot sizes appear at [basechaindata.vercel.app](https://basechaindata.vercel.app).

> [!NOTE]
> Coinbase's production runs use AWS `i7i.12xlarge` instances with all local NVMe drives in RAID 0 on ext4, for both Reth archive and Geth full nodes.

### 2.2 Install Docker

=== "Linux (Ubuntu/Debian)"

    ```bash
    curl -fsSL https://get.docker.com | sh
    sudo usermod -aG docker $USER
    # log out and back in so your user picks up the docker group
    docker --version
    docker compose version
    ```

=== "macOS"

    ```bash
    brew install --cask docker
    open -a Docker
    docker --version
    docker compose version
    ```

=== "Windows"

    Install [Docker Desktop](https://www.docker.com/products/docker-desktop/). After installation, start Docker Desktop and verify in PowerShell:

    ```powershell
    docker --version
    docker compose version
    ```

> [!WARNING]
> The legacy `docker-compose` (v1, hyphenated) command is deprecated. Use `docker compose` (two words) — issue [#1012](https://github.com/base/node/issues/1012) tracked the same fix in this repo's `reth/README.md`.

### 2.3 Clone The Repository

```bash
git clone https://github.com/base/node.git
cd node
```

The repository ships these top-level components:

| Path | Purpose |
|---|---|
| `docker-compose.yml` | Two-container orchestration: `execution` + `node` |
| `.env` | Host-side defaults — `CLIENT`, `HOST_DATA_DIR`, `USE_BASE_CONSENSUS` |
| `.env.mainnet` | Mainnet env file (consumed by both containers via `env_file:`) |
| `.env.sepolia` | Sepolia testnet env file |
| `versions.env` / `versions.json` | Pinned upstream commit/tag for each binary |
| `geth/`, `nethermind/`, `reth/` | Per-client Dockerfile and entrypoint script |
| `consensus-entrypoint` | Dispatcher: chooses op-node vs. base-consensus |
| `op-node-entrypoint`, `base-consensus-entrypoint` | Consensus-client launchers |
| `supervisord.conf` | Process supervisor used inside containers |
| `dependency_updater/` | Go CI utility that bumps the pinned upstream versions |
| `SECURITY.md` | HackerOne reporting (Coinbase bug bounty program) |

### 2.4 Choose A Client

Base supports three execution clients and two consensus implementations.

#### 2.4.1 Execution Clients

| Client | `CLIENT=` | Language | Built from | Final image base |
|---|---|---|---|---|
| **Reth** | `reth` | Rust | `base/base` (pinned in `versions.json`) | `ubuntu:24.04` |
| **Geth** | `geth` | Go | `ethereum-optimism/op-geth` | `ubuntu:24.04` |
| **Nethermind** | `nethermind` | .NET | `NethermindEth/nethermind` | `mcr.microsoft.com/dotnet/aspnet:10.0-noble` |

Pick by exporting `CLIENT` before `docker compose up`:

```bash
CLIENT=reth docker compose up --build
CLIENT=geth docker compose up --build
CLIENT=nethermind docker compose up --build
```

> [!NOTE]
> If you don't export `CLIENT`, the compose file falls back to `geth` (`${CLIENT:-geth}`). The README mentions reth as the recommended client, but the compose default is geth — track issue [#1010](https://github.com/base/node/issues/1010). Be explicit about `CLIENT` in production.

#### 2.4.2 Consensus Clients

The `node` service runs one of two consensus binaries, picked by `USE_BASE_CONSENSUS`:

| Setting | Script invoked | Binary | When to use |
|---|---|---|---|
| `USE_BASE_CONSENSUS=true` | `base-consensus-entrypoint` | `base-consensus` (Rust, from `base/base`) | **Reth only.** Default in shipped `.env`. |
| `USE_BASE_CONSENSUS=false` (or unset) | `op-node-entrypoint` | `op-node` (Go, from `ethereum-optimism/optimism`) | Any client; standard OP Stack consensus. |

The dispatcher (`consensus-entrypoint`) selects the entrypoint:

```bash
if [ "${USE_BASE_CONSENSUS:-false}" = "true" ]; then
    if [ -f ./base-consensus-entrypoint ]; then
        echo "Using Base Client"
        exec ./base-consensus-entrypoint
    else
        echo "Base client is not supported for this node type"
        exit 1
    fi
else
    echo "Using OP Node"
    exec ./op-node-entrypoint
fi
```

> [!WARNING]
> `base-consensus` is shipped only in the **reth** image. Setting `USE_BASE_CONSENSUS=true` with `CLIENT=geth` or `CLIENT=nethermind` causes the consensus container to exit with `Base client is not supported for this node type`. Pair `USE_BASE_CONSENSUS=true` with `CLIENT=reth`, or set `USE_BASE_CONSENSUS=false` for geth/nethermind. See issue [#1016](https://github.com/base/node/issues/1016).

### 2.5 Post-Install Verification

```bash
docker --version
docker compose version
ls .env.mainnet .env.sepolia versions.env versions.json
```

```text
Docker version 27.x.x, build ...
Docker Compose version v2.x.x
.env.mainnet
.env.sepolia
versions.env
versions.json
```

If any of those files are missing, re-clone the repository — these are the source of truth for chain config and pinned dependencies.

---

## 3. Configuration

All runtime configuration is in **two** env files:

1. **`.env`** — host-side defaults consumed by `docker compose` itself. Controls `CLIENT`, `HOST_DATA_DIR`, and `USE_BASE_CONSENSUS`.
2. **`.env.mainnet`** or **`.env.sepolia`** — loaded into both containers via `env_file:`. Holds the network-specific and client-tuning variables.

### 3.1 Pick Your Network

```bash
# Mainnet (default)
docker compose up --build

# Sepolia testnet
NETWORK_ENV=.env.sepolia docker compose up --build
```

The compose file uses `${NETWORK_ENV:-.env.mainnet}` for the env_file selection.

### 3.2 Required L1 Endpoints

Edit the env file you are using and replace the placeholder values:

```bash
# In .env.mainnet (or .env.sepolia)
OP_NODE_L1_ETH_RPC=https://your-l1-execution-rpc
OP_NODE_L1_BEACON=https://your-l1-beacon
OP_NODE_L1_BEACON_ARCHIVER=https://your-l1-beacon-archiver
```

If you run the Base-consensus variant (reth + `USE_BASE_CONSENSUS=true`), also set:

```bash
BASE_NODE_L1_ETH_RPC=https://your-l1-execution-rpc
BASE_NODE_L1_BEACON=https://your-l1-beacon
```

### 3.3 L1 Provider Hint

`OP_NODE_L1_RPC_KIND` tells op-node how to fetch receipts efficiently:

| Value | Provider |
|---|---|
| `alchemy` | Alchemy |
| `quicknode` | QuickNode |
| `infura` | Infura |
| `parity` | OpenEthereum / Parity |
| `nethermind` | Nethermind |
| `debug_geth` | Geth with debug namespace (**default in shipped env files**) |
| `erigon` | Erigon |
| `basic` | Standard `eth_getBlockReceipts` only |
| `any` | Try any supported optimized method |
| `standard` | Standard methods including newer optimized ones |

Set it to match your L1 provider:

```bash
OP_NODE_L1_RPC_KIND="alchemy"
```

### 3.4 Data Directory

`HOST_DATA_DIR` in `.env` controls where chain data is persisted on the host (bind-mounted into `/data` inside the execution container):

```bash
HOST_DATA_DIR=./geth-data        # default when CLIENT=geth
HOST_DATA_DIR=./reth-data        # override when CLIENT=reth
HOST_DATA_DIR=./nethermind-data
```

These names match `.dockerignore` entries so they never bloat Docker build context.

### 3.5 Ports Exposed

The compose file publishes these ports from each container:

| Container | Container port | Host port | Purpose |
|---|---|---|---|
| `execution` | 8545 | **8545** | HTTP JSON-RPC |
| `execution` | 8546 | **8546** | WebSocket JSON-RPC |
| `execution` | 6060 | **7301** | Execution metrics (Prometheus) |
| `execution` | 30303 | **30303** (TCP+UDP) | Execution P2P |
| `node` | 8545 | **7545** | Consensus JSON-RPC |
| `node` | 9222 | **9222** (TCP+UDP) | Consensus P2P |
| `node` | 7300 | **7300** | Consensus metrics |
| `node` | 6060 | **6060** | Consensus pprof |

> [!IMPORTANT]
> Ports **30303/tcp+udp** and **9222/tcp+udp** must be reachable from the public internet for peer discovery to work. Open them in your router/firewall. The entrypoints auto-detect your public IP via `ifconfig.me`, `api.ipify.org`, `ipecho.net`, or `v4.ident.me` and advertise it to peers.

> [!CAUTION]
> All published RPC and metrics ports bind to `0.0.0.0` inside the container. If your host has a public IP without a firewall, your RPC is open to the internet. Restrict with iptables/ufw or rebind in `docker-compose.yml` (see the Admin Guide §7).

### 3.6 Geth Cache Tuning

Defaults in the shipped env files target a 64 GB+ host:

```bash
GETH_CACHE="20480"           # 20 GB total cache
GETH_CACHE_DATABASE="20"     # 20 % of total (~4 GB)
GETH_CACHE_GC="12"           # 12 %
GETH_CACHE_SNAPSHOT="24"     # 24 %
GETH_CACHE_TRIE="44"         # 44 %
```

Scale `GETH_CACHE` proportionally to your host RAM:

```bash
GETH_CACHE="8192"            # 8 GB cache for a 16 GB host
```

### 3.7 Logging

| Variable | Default | Values |
|---|---|---|
| `OP_NODE_LOG_LEVEL` | `info` | `trace`, `debug`, `info`, `warn`, `error`, `crit` |
| `OP_NODE_LOG_FORMAT` | `json` | `json`, `logfmt`, `terminal` |
| `BASE_NODE_LOG_VERBOSITY` | `3` | `0`–`5` |
| `BASE_NODE_LOG_FORMAT` | `json` | `json`, `logfmt`, `terminal` |
| `LOG_LEVEL` (reth entrypoint) | `info` | `error`, `warn`, `info`, `debug`, `trace` |
| `GETH_VERBOSITY` | `3` | `0`–`5` |
| `NETHERMIND_LOG_LEVEL` | `Info` | `Off`, `Error`, `Warn`, `Info`, `Debug`, `Trace` |

### 3.8 Metrics

Prometheus metrics are enabled by default. Scrape:

- Execution: `http://<host>:7301/metrics`
- Consensus: `http://<host>:7300/metrics`

`STATSD_ADDRESS="172.17.0.1"` is also set but only used by base-consensus (best-effort StatsD push to the Docker host's bridge IP).

### 3.9 Optional Features (Commented-Out By Default)

#### EthStats Monitoring

```bash
OP_GETH_ETH_STATS=nodename:secret@host:port
# or for nethermind:
OP_NETHERMIND_ETHSTATS_ENABLED=true
OP_NETHERMIND_ETHSTATS_NODE_NAME=NethermindNode
OP_NETHERMIND_ETHSTATS_ENDPOINT=ethstats_endpoint
```

#### Trusted L1 Mode

```bash
OP_NODE_L1_TRUST_RPC=true
```

> [!CAUTION]
> `OP_NODE_L1_TRUST_RPC=true` disables op-node's own validation of L1 responses. Only enable when pointing at an L1 RPC you fully control.

#### Snap Sync (Experimental)

```bash
OP_GETH_SYNCMODE=snap
OP_GETH_BOOTNODES=enode://...,enode://...
# or for nethermind:
OP_NETHERMIND_BOOTNODES=enode://...,enode://...
```

> [!WARNING]
> Snap sync on Base is experimental and may produce sync issues. Full sync is the default and recommended path.

#### Flashblocks (Reth Only)

```bash
# .env.mainnet
RETH_FB_WEBSOCKET_URL=wss://mainnet.flashblocks.base.org/ws

# .env.sepolia
RETH_FB_WEBSOCKET_URL=wss://sepolia.flashblocks.base.org/ws
```

Flashblocks is Base's sub-block pending-block preview feature — see Section 5.7 for usage.

#### Pruning (Reth Only)

```bash
RETH_PRUNING_ARGS="--prune.senderrecovery.distance=50000 --prune.transactionlookup.distance=50000 --prune.receipts.distance=50000 --prune.accounthistory.distance=50000 --prune.storagehistory.distance=50000 --prune.bodies.distance=50000"
```

> [!CAUTION]
> Pruning distance must be greater than `10064`. Published pruned snapshots use `1_339_200` (~31 days). Setting the distance below 10064 can break consensus participation. Node type (archive vs pruned) cannot be changed after initial sync.

---

## 4. Core Usage

### 4.1 Start The Node

```bash
docker compose up --build
```

`--build` rebuilds the client image when `versions.env` has changed.

### 4.2 Background Mode

```bash
docker compose up --build -d
docker compose logs -f
```

### 4.3 Stop

```bash
docker compose down
```

Chain data in `HOST_DATA_DIR` persists.

### 4.4 Stop And Wipe Data

```bash
docker compose down
rm -rf ./geth-data ./reth-data ./nethermind-data   # whichever you used
```

### 4.5 Verify Execution Client Sync

```bash
curl -s -X POST http://localhost:8545 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
```

```text
{"jsonrpc":"2.0","id":1,"result":"0x1a3b4c5"}
```

A non-`0x0` result means the execution client is syncing or synced.

### 4.6 Verify Consensus Client

```bash
curl -s -X POST http://localhost:7545 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}'
```

### 4.7 Check Peer Count

```bash
curl -s -X POST http://localhost:8545 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}'
```

> [!TIP]
> If peer count stays at `"0x0"`, your P2P ports are blocked. Verify `30303/tcp+udp` (execution) and `9222/tcp+udp` (consensus) are reachable from the internet using `nc -vuz <your-public-ip> 30303` from outside your network.

### 4.8 Follow Logs

```bash
docker compose logs -f                # both containers
docker compose logs -f execution      # execution client only
docker compose logs -f node           # consensus client only
```

### 4.9 Inspect Inside A Container

```bash
docker compose exec execution bash
docker compose exec node bash
```

### 4.10 Restart One Service

```bash
docker compose restart execution
docker compose restart node
```

### 4.11 Query Metrics

```bash
curl -s http://localhost:7301/metrics | head -n 20      # execution
curl -s http://localhost:7300/metrics | head -n 20      # consensus
```

### 4.12 Check Disk Usage

```bash
du -sh ./geth-data ./reth-data ./nethermind-data 2>/dev/null
df -h .
```

---

## 5. Common Tasks

### 5.1 Recipe — Start A Mainnet Reth Archive Node

**Goal:** Get the recommended Coinbase production configuration up and syncing.

**Prerequisites:**

- L1 endpoints configured in `.env.mainnet`
- Disk room for `2 × current chain size + snapshot size + 20 %`
- Ports `30303/tcp+udp` and `9222/tcp+udp` open in your firewall

**Steps:**

```bash
# Pick reth and use base-consensus (the default for the shipped .env)
export CLIENT=reth
export HOST_DATA_DIR=./reth-data

docker compose up --build -d
docker compose logs -f
```

**Verify:**

```bash
curl -s -X POST http://localhost:8545 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
```

A `result` field other than `"0x0"` confirms reth is producing block numbers.

**If it fails:** check `docker compose logs execution` for the reth boot output. The most common first-time failure is missing or wrong L1 endpoints — see Section 6.

### 5.2 Recipe — Sync To Current Head Quickly

**Goal:** Skip days of genesis sync by restoring from a published snapshot.

**Steps:**

```bash
# 1. Stop any running stack
docker compose down

# 2. Identify your snapshot:
#    Visit https://basechaindata.vercel.app and pick the URL that matches
#    your client (reth/geth/nethermind) and network (mainnet/sepolia).
SNAPSHOT_URL="https://example/path/to/snapshot.tar.zst"

# 3. Download and extract into HOST_DATA_DIR
mkdir -p ./reth-data
curl -L "$SNAPSHOT_URL" | tar --use-compress-program=zstd -xf - -C ./reth-data

# 4. Start the stack
CLIENT=reth docker compose up --build -d
```

**Verify:** `eth_blockNumber` should jump immediately to the snapshot's height.

### 5.3 Recipe — Expose RPC To A Specific Subnet

**Goal:** Make the JSON-RPC available to your VPC peers but not the public internet.

**Steps:** edit `docker-compose.yml` and rebind the published ports to a private interface:

```yaml
services:
  execution:
    ports:
      - "10.0.0.5:8545:8545"
      - "10.0.0.5:8546:8546"
      - "10.0.0.5:7301:6060"
      - "30303:30303"
      - "30303:30303/udp"
```

Then `docker compose up -d`.

> [!WARNING]
> Do **not** expose port 8551 (Engine API). It is JWT-authenticated, but the shipped JWT is a public sample value — anyone who reaches 8551 with the shipped key can drive your execution client.

### 5.4 Recipe — Switch From Mainnet To Sepolia

**Goal:** Run the Sepolia testnet variant on the same host (or move from mainnet to Sepolia).

**Prerequisites:** different `HOST_DATA_DIR` for Sepolia data.

**Steps:**

```bash
docker compose down

# Use a separate data directory for Sepolia
HOST_DATA_DIR=./reth-data-sepolia \
NETWORK_ENV=.env.sepolia \
CLIENT=reth \
docker compose up --build -d
```

> [!WARNING]
> Do not point the same `HOST_DATA_DIR` at both mainnet and Sepolia. The two chain databases are incompatible — you will corrupt the on-disk state.

### 5.5 Recipe — Snapshot And Restore

**Goal:** Take a backup and restore later (or move to another host).

**Snapshot:**

```bash
# Stop first — never back up while the node is running
docker compose down

# Compress the chain data
tar --use-compress-program='zstd -T0' -cf base-data.tar.zst ./reth-data

# Optional: ship to S3 or another backup target
aws s3 cp base-data.tar.zst s3://my-bucket/base/
```

**Restore:**

```bash
# Stop first
docker compose down

# Replace the data dir
rm -rf ./reth-data
mkdir ./reth-data
tar --use-compress-program=zstd -xf base-data.tar.zst -C .

# Start
docker compose up --build -d
```

> [!WARNING]
> Backups must be taken with the node stopped. Hot-backing-up an open MDBX (reth), LevelDB (geth), or Nethermind store will produce an inconsistent and unusable snapshot.

### 5.6 Recipe — Upgrade To A New Release

**Goal:** Pick up new pinned versions of op-node, op-geth, base-reth-node, or nethermind.

**Steps:**

```bash
# Pull the new pins
git pull

# Rebuild from scratch — the Dockerfiles re-clone the upstream repos at the new pinned commits
docker compose build --no-cache

# Start
docker compose up -d
```

**Verify:** `docker compose logs execution | head` shows the new client version. Each Dockerfile asserts that the cloned upstream's `HEAD` matches the pinned commit in `versions.env`; the build fails fast if a tag has been re-pointed upstream (supply-chain safety check).

### 5.7 Recipe — Enable Flashblocks (Reth)

**Goal:** Get sub-block pending-block previews on RPC.

**Steps:** uncomment in your env file and restart:

```bash
# .env.mainnet
RETH_FB_WEBSOCKET_URL=wss://mainnet.flashblocks.base.org/ws
```

```bash
docker compose down
CLIENT=reth docker compose up --build
```

You should see in the logs:

```text
Enabling Flashblocks support with endpoint: wss://mainnet.flashblocks.base.org/ws
```

Query a pending Flashblock:

```bash
curl -X POST http://localhost:8545 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["pending", false],"id":1}'
```

> [!NOTE]
> Flashblocks is reth-only. Setting `RETH_FB_WEBSOCKET_URL` with `CLIENT=geth` or `CLIENT=nethermind` has no effect. If `RETH_FB_WEBSOCKET_URL` is unset, reth runs in vanilla mode and prints `Running in vanilla node mode (no Flashblocks URL provided)`.

### 5.8 Recipe — Monitor Health From Prometheus

**Goal:** Get sync lag, peer counts, and disk pressure on a dashboard.

**Prometheus scrape config:**

```yaml
scrape_configs:
  - job_name: 'base-execution'
    static_configs:
      - targets: ['my-base-host:7301']
  - job_name: 'base-consensus'
    static_configs:
      - targets: ['my-base-host:7300']
```

**Suggested alerts:**

| Alert | Threshold |
|---|---|
| Sync lag | Node head trails sequencer head by > 30s for 5 min |
| Peer count drop | `< 5` execution peers or `< 3` consensus peers for 5 min |
| Engine API errors | Non-200 response rate from execution to consensus |
| Disk pressure | `node_filesystem_avail_bytes` falls below 20 % |
| Restart loops | Docker container restart count keeps increasing |

> [!TIP]
> The pprof endpoint on `localhost:6060` (consensus container) is great for ad-hoc Go profiling: `go tool pprof http://localhost:6060/debug/pprof/heap`. Restrict its host binding in production — the pprof endpoint is unauthenticated.

---

## 6. Troubleshooting

### Error: `expected OP_NODE_NETWORK to be set`

**Cause:** Compose is running without an env file, or the file doesn't define `OP_NODE_NETWORK`.

**Solution:** Ensure `.env.mainnet` (or `.env.sepolia`) exists and contains `OP_NODE_NETWORK=base-mainnet` (or `base-sepolia`). Launch with the right env file:

```bash
NETWORK_ENV=.env.mainnet docker compose up
```

**Prevention:** Don't delete lines from the shipped env files. Only fill in the `<your-preferred-*>` placeholders.

---

### Error: `expected BASE_NODE_NETWORK to be set`

**Cause:** `USE_BASE_CONSENSUS=true` is set but the env file doesn't define `BASE_NODE_NETWORK`.

**Solution:** The shipped env files already define this (`BASE_NODE_NETWORK=base` for mainnet, `base-sepolia` for testnet). Confirm you are loading the correct file via `NETWORK_ENV`.

---

### Error: `Base client is not supported for this node type`

**Cause:** `USE_BASE_CONSENSUS=true` with `CLIENT=geth` or `CLIENT=nethermind`. The `base-consensus` binary ships only in the reth image.

**Solution:** Either use `CLIENT=reth` or set `USE_BASE_CONSENSUS=false`:

```bash
CLIENT=reth USE_BASE_CONSENSUS=true  docker compose up --build
# or
CLIENT=geth USE_BASE_CONSENSUS=false docker compose up --build
```

**Prevention:** Treat the two variables as a pair. The shipped `.env` defaults `USE_BASE_CONSENSUS=true`, which only works with reth. See issue [#1016](https://github.com/base/node/issues/1016).

---

### Error: `Could not retrieve public IP.`

**Cause:** All four IP-echo providers (`ifconfig.me`, `api.ipify.org`, `ipecho.net`, `v4.ident.me`) failed from inside the container. The entrypoint exits with code **8**.

**Solution:** Ensure outbound HTTP on port 80 from the container. If you are behind a strict egress firewall, allowlist at least one of those four hosts. The entrypoints do not currently accept a manual public-IP override.

**Prevention:** Confirm outbound internet works from inside Docker before launching:

```bash
docker run --rm curlimages/curl https://ifconfig.me
```

---

### Error: `waiting for execution client to be ready` (repeats forever)

**Cause:** The consensus container is polling the execution container's Engine API port and not getting an HTTP `401` (which proves the execution client is up and rejecting unauthenticated calls). Either the execution container has crashed, or the engine RPC URL is wrong.

**Solution:** In another shell:

```bash
docker compose logs execution | tail -n 100
```

If the execution container has exited, check for startup errors (bad chain config, insufficient disk, corrupted database). The engine RPCs default to `http://execution:8551` (or `ws://execution:8551` on Sepolia) — only change them if you customized the compose file.

---

### Error: Peer count stuck at `0x0`

**Cause:** Inbound P2P traffic blocked. Without inbound connectivity, neither client can find peers.

**Solution:** Verify external reachability:

```bash
nc -vuz <your-public-ip> 30303
nc -vuz <your-public-ip> 9222
```

Open `30303/tcp+udp` and `9222/tcp+udp` in your router or cloud security group. Restart the stack.

**Prevention:** Document your port mappings before a long sync. Test with `nc` or `nmap` first.

---

### Error: `version downgrade detected: v0.3.0 -> v0.2.9`

**Cause:** The `dependency_updater` CLI rejected an upgrade because the new version sorts lower than the current pinned tag. This is a CI/internal error from `ValidateVersionUpgrade()` in `dependency_updater/version.go`.

**Solution:** This is not a node runtime error — see the Admin Guide §8 for the dependency updater workflow. If you hit it running the tool manually, double-check the GitHub tag actually represents a newer release.

---

### Error: `Commit hash verification failed`

**Cause:** During a Docker build, a clone step pinned an upstream tag, but the tag's `HEAD` commit no longer matches `OP_NODE_COMMIT` / `BASE_RETH_NODE_COMMIT` / `NETHERMIND_COMMIT` / `OP_GETH_COMMIT` in `versions.env`. Either the upstream tag was force-pushed, or your local `versions.env` is stale.

**Solution:**

```bash
git pull
docker compose build --no-cache
```

**Prevention:** Always run `docker compose up --build` after `git pull`. The pinned-commit assertion is a supply-chain safety net; don't disable it.

---

### Error: reth sync stuck or extremely slow

**Cause:** First-time archive sync depends heavily on L1 provider throughput, especially blob fetching.

**Solution:**

1. Use a snapshot — see Section 5.2.
2. Use a high-throughput L1 beacon archiver that returns all historical blob sidecars.
3. Confirm `OP_NODE_L1_BEACON_FETCH_ALL_SIDECARS="true"` (default in shipped env files).

---

### Error: `docker-compose: command not found`

**Cause:** You are using the legacy v1 CLI syntax (hyphenated). Base's compose file requires Compose v2 features.

**Solution:** Use `docker compose` (two words):

```bash
docker compose up --build
```

If your install is too old, upgrade Docker Engine to 20.10+ with the compose plugin or install Docker Desktop. See issue [#1012](https://github.com/base/node/issues/1012).

---

### Error: `unsupported architecture: <arch>` during reth build

**Cause:** The reth Dockerfile downloads the `mold` linker for `x86_64`, `aarch64`/`arm64`, and `armv7l`/`armv6l`. Other architectures abort the build.

**Solution:** Build on a supported architecture or modify `reth/Dockerfile` to add a case for your CPU.

---

### Error: `OP_NODE_P2P_ADVERTISE_IP` is wrong (NAT / CGNAT)

**Cause:** The public-IP auto-detect returns your NAT's address, which may not be externally reachable (typical with carrier-grade NAT).

**Solution:** Expose the node behind a routable IP or a port-forwarding gateway. The entrypoints do not currently accept a manual advertise-IP override; you'd need to patch `op-node-entrypoint` / `base-consensus-entrypoint` to `export OP_NODE_P2P_ADVERTISE_IP=<your-routable-ip>` before the `exec`.

---

### Error: Disk fills up rapidly

**Cause:** Base archive data grows continuously. Reth archive can exceed 3 TB; geth full archive is similar.

**Solution:**

- Watch disk: `df -h`.
- Plan storage as `2 × current chain size + snapshot size + 20 %`.
- Consider pruning (reth, distance > 10064) or `OP_GETH_GCMODE=full` + `OP_GETH_SYNCMODE=snap` for geth.

---

### Error: Ports 8545 / 8546 already in use

**Cause:** Another local Ethereum node, Anvil, or Hardhat is bound to those ports.

**Solution:** Stop the other process, or remap Base's ports in `docker-compose.yml`:

```yaml
ports:
  - "18545:8545"
  - "18546:8546"
```

---

### Error: `rollup.sequencerhttp` unreachable

**Cause:** Outbound HTTPS to `https://mainnet-sequencer.base.org` (or the Sepolia equivalent) is blocked.

**Solution:** Allow outbound HTTPS. In an air-gapped or egress-restricted environment, you would need to proxy or mirror the sequencer endpoint.

---

### Error: Nethermind fails with `Configuration file '<network>' not found`

**Cause:** Nethermind launches with `--config=$OP_NODE_NETWORK`. The pinned nethermind build must contain a chainspec for that network.

**Solution:** Confirm `versions.json` has a current nethermind tag that supports Base:

```bash
git pull
docker compose build --no-cache
```

---

### Error: `Enabling historical proofs preflight…` blocks for hours

**Cause:** When `RETH_HISTORICAL_PROOFS=true` and `RETH_HISTORICAL_PROOFS_STORAGE_PATH` is set, reth's preflight does a start-wait-stop-init dance that can take **up to 6 hours** on a fresh database.

**Solution:** Plan a long maintenance window the first time you enable historical proofs. After the initial init, subsequent boots are fast.

> [!WARNING]
> If `RETH_HISTORICAL_PROOFS=true` but `RETH_HISTORICAL_PROOFS_STORAGE_PATH` is empty, the preflight is silently skipped — historical proofs won't work, but the boot doesn't fail. Validate the path is set if you rely on this feature.

---

## 7. FAQ

**Q: Which client should I pick?**

Coinbase's production runs **reth archive** on mainnet — the best archive performance on modern NVMe hardware. Geth is the most battle-tested. Nethermind is the .NET option. Pick reth unless you have a specific operational reason to choose another.

---

**Q: Do I need to run my own L1 Ethereum node?**

No. Any reliable L1 RPC + beacon + beacon-archiver works (Alchemy, Infura, QuickNode, your own). Running your own L1 gives the best reliability and avoids rate limits, but is not required.

---

**Q: How long does initial sync take?**

From genesis: days to weeks, depending on client, hardware, and L1 throughput. From a snapshot: typically hours.

---

**Q: Can I run both mainnet and Sepolia on the same machine?**

Yes — use different `HOST_DATA_DIR` values and publish each stack on different host ports (edit `docker-compose.yml`). Run each stack from its own copy of the repo, or pass `-p <project-name>` to `docker compose` to keep container names separate.

---

**Q: Can I migrate synced data from geth to reth (or vice versa)?**

No. Each client uses a different on-disk format. To switch clients, fetch a fresh snapshot for the target client.

---

**Q: What is "Flashblocks"?**

Base's sub-block pending-block preview, delivered over a websocket. RPC clients can query the pending block before it's finalized. Supported only by reth in this repository — see Section 5.7.

---

**Q: What does `USE_BASE_CONSENSUS=true` do?**

It replaces the default Go-based op-node with Base's Rust-based `base-consensus` binary. It only works with `CLIENT=reth` because the base-consensus binary is built into the reth image only. It is the default in the shipped `.env`.

---

**Q: Can I run with less than 32 GB RAM?**

You can try, but the shipped Geth cache defaults assume 64 GB+. For smaller hosts, lower `GETH_CACHE` (MB total). Insufficient RAM usually shows up as OOM kills or extreme slowness.

---

**Q: Where does the JWT secret come from?**

From `BASE_NODE_L2_ENGINE_AUTH_RAW` in the env file (a hex string). The entrypoints write it to `/tmp/engine-auth-jwt` (`BASE_NODE_L2_ENGINE_AUTH`) at startup, and both the execution and consensus clients read from that file. **Important:** the shipped value is a public sample — generate your own with `openssl rand -hex 32` if you ever expose port 8551.

---

**Q: How do I upgrade to a new Base release?**

```bash
git pull
docker compose build --no-cache
docker compose up -d
```

The pinned versions live in `versions.env` / `versions.json` and are bumped upstream by CI (the `dependency_updater` Go tool — see the Admin Guide).

---

**Q: Is `base/node` the actual node software?**

No. This repository contains Docker builds, env templates, and entrypoint scripts. The execution binary (reth, geth, nethermind), op-node, and base-consensus are all built from their own upstream repos at the pinned commit hashes recorded in `versions.env`.

---

**Q: How do I enable Prometheus scraping?**

Metrics are already exposed on port `7301` (execution) and `7300` (consensus). Point your Prometheus scrape config at `http://<host>:7301/metrics` and `http://<host>:7300/metrics` — see Section 5.8.

---

**Q: Why does op-node show `OP_NODE_SYNCMODE=execution-layer (deprecated)`?**

The setting is being phased out upstream but is still accepted by the pinned op-node version. Leave it alone until Base publishes a release that removes the warning.

---

**Q: How do I report a bug?**

Open an issue at [github.com/base/node/issues](https://github.com/base/node/issues), or post in the `node-operators` channel of the [Base Discord](https://discord.gg/buildonbase).

---

**Q: Does Base offer a bug bounty?**

Yes — Coinbase's HackerOne program covers Base. Submit at [hackerone.com/coinbase](https://hackerone.com/coinbase). Security issues should never be filed on GitHub.

---

**Q: What pinned versions am I running?**

At commit `d4a32b2`:

| Component | Tag |
|---|---|
| `base_reth_node` (built from `base/base`) | `v0.7.6` |
| `nethermind` (`NethermindEth/nethermind`) | `1.36.2` |
| `op_geth` (`ethereum-optimism/op-geth`) | `v1.101702.0` |
| `op_node` (`ethereum-optimism/optimism`, prefix `op-node`) | `op-node/v1.16.11` |

Read `versions.json` for the canonical, version-controlled list including SHA-256 commits.

---

_Generated April 2026 from base/node at d4a32b2. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
