> ## Documentation Index
> Fetch the complete documentation index at: https://optimism-373f39ad-soyboy-docs-deploy-a-contract.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Running a Node With Docker

> Run an OP Stack node (op-reth + op-node) using the official Docker images and docker-compose.

<Warning>
  **op-geth reaches end-of-support on 2026-05-31 and will not support the L1 Glamsterdam hardfork.** This tutorial runs op-reth (the primary supported execution client) via Docker. See the [op-geth deprecation notice](/notices/op-geth-deprecation) for the full migration plan.
</Warning>

This tutorial runs an OP Stack node using the **official op-reth and op-node Docker images** in a single `docker-compose.yml`. No source build required.

<Warning>
  **OP Mainnet requires a one-time pre-Bedrock state import.** OP Sepolia does not. If you point this tutorial at OP Mainnet on a fresh datadir, op-reth will fail at startup with `Op-mainnet has been launched without importing the pre-Bedrock state`. Before running OP Mainnet, follow the [op-reth sync-op-mainnet guide](https://reth.rs/run/sync-op-mainnet.html) to either restore from a [pre-synced snapshot](https://datadirs.optimism.io/) or run the minimal-bootstrap import. OP Sepolia bootstraps via snap sync with no extra steps — start there if you're just testing the setup.
</Warning>

## Dependencies

* [Docker](https://docs.docker.com/engine/install/)
* [Docker Compose](https://docs.docker.com/compose/install/) (v2)
* **An L1 execution RPC endpoint** (Ethereum mainnet for OP Mainnet, or Ethereum Sepolia for OP Sepolia).
* **An L1 Beacon API endpoint** for the same L1 chain. Needed by op-node to fetch blob data post-Ecotone.

## Quick start

<Steps>
  <Step title="Create a working directory">
    ```bash theme={null}
    mkdir op-stack-node && cd op-stack-node
    ```
  </Step>

  <Step title="Generate the JWT secret">
    Both containers share a JWT secret over a bind mount:

    ```bash theme={null}
    openssl rand -hex 32 > jwt.txt
    ```
  </Step>

  <Step title="Create the .env file">
    Configure your network and L1 endpoints. Pick **one** network block (Sepolia or Mainnet) and fill in your L1 RPC + Beacon URLs:

    ```bash theme={null}
    cat > .env <<'EOF'
    # --- Network: OP Sepolia (default) ---
    OP_RETH_CHAIN=optimism_sepolia
    OP_NODE_NETWORK=op-sepolia
    OP_RETH_SEQUENCER=https://sepolia-sequencer.optimism.io

    # --- Network: OP Mainnet (uncomment to use instead) ---
    # OP_RETH_CHAIN=optimism
    # OP_NODE_NETWORK=op-mainnet
    # OP_RETH_SEQUENCER=https://mainnet-sequencer.optimism.io

    # --- L1 endpoints (required) ---
    L1_RPC_URL=https://your-l1-rpc-endpoint
    L1_RPC_KIND=basic
    L1_BEACON_URL=https://your-l1-beacon-endpoint
    EOF
    ```

    `L1_RPC_KIND` valid values: `alchemy`, `quicknode`, `infura`, `parity`, `nethermind`, `debug_geth`, `erigon`, `basic`, `any`. Use `basic` if unsure.

    <Info>
      `OP_RETH_CHAIN` and `OP_NODE_NETWORK` accept any [superchain-registry](https://github.com/ethereum-optimism/superchain-registry) chain (e.g. `unichain` / `unichain-mainnet`, `soneium` / `soneium-mainnet`). For each chain, point `L1_RPC_URL` / `L1_BEACON_URL` at the corresponding L1 (Ethereum Mainnet or Sepolia) and update `OP_RETH_SEQUENCER` to that chain's sequencer endpoint.
    </Info>
  </Step>

  <Step title="Create docker-compose.yml">
    ```yaml theme={null}
    services:
      op-reth:
        image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-reth:v2.2.5
        container_name: op-reth
        ports:
          - "8545:8545"          # JSON-RPC HTTP
          - "8546:8546"          # JSON-RPC WebSocket
          - "9001:9001"          # Prometheus metrics
          - "30303:30303"        # P2P TCP
          - "30303:30303/udp"    # P2P UDP
        volumes:
          - ./reth-data:/data
          - ./jwt.txt:/jwt.txt:ro
        command:
          - node
          - --chain=${OP_RETH_CHAIN}
          - --datadir=/data
          - --http
          - --http.addr=0.0.0.0
          - --http.port=8545
          - --ws
          - --ws.addr=0.0.0.0
          - --ws.port=8546
          - --authrpc.addr=0.0.0.0
          - --authrpc.port=8551
          - --authrpc.jwtsecret=/jwt.txt
          - --rollup.sequencer=${OP_RETH_SEQUENCER}
          - --metrics=0.0.0.0:9001
        restart: unless-stopped

      op-node:
        image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.18.2
        container_name: op-node
        depends_on:
          - op-reth
        ports:
          - "9545:7000"          # JSON-RPC HTTP (host 9545 → container 7000; macOS uses 7000 for AirPlay)
          - "7300:7300"          # Prometheus metrics
          - "9222:9222"          # P2P TCP
          - "9222:9222/udp"      # P2P UDP
        volumes:
          - ./jwt.txt:/jwt.txt:ro
        command:
          - op-node
          - --l1=${L1_RPC_URL}
          - --l1.rpckind=${L1_RPC_KIND}
          - --l1.beacon=${L1_BEACON_URL}
          - --l2=ws://op-reth:8551
          - --l2.jwt-secret=/jwt.txt
          - --network=${OP_NODE_NETWORK}
          - --syncmode=execution-layer
          - --l2.enginekind=reth
          - --rpc.addr=0.0.0.0
          - --rpc.port=7000
          - --metrics.enabled
          - --metrics.addr=0.0.0.0
          - --metrics.port=7300
        restart: unless-stopped
    ```

    The op-reth datadir is bind-mounted from `./reth-data` on the host so you can inspect / restore from a snapshot directly (see [Bootstrap from a snapshot](#bootstrap-from-a-snapshot) below).

    Image tags shown are the latest as of writing. For the current tags, see the [op-reth releases](https://github.com/ethereum-optimism/optimism/releases?q=op-reth) and [op-node releases](https://github.com/ethereum-optimism/optimism/releases?q=op-node).
  </Step>

  <Step title="Start the node">
    ```bash theme={null}
    docker compose up -d
    ```

    Follow logs with:

    ```bash theme={null}
    docker compose logs -f
    ```
  </Step>
</Steps>

## Verification

Check the node is alive and advancing:

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

Expected: `{"jsonrpc":"2.0","id":1,"result":"0x..."}` with the current block in hex. Run again after a minute — the number should increase as sync progresses.

For op-node sync status (unsafe, safe, and finalized heads in one call):

```bash theme={null}
curl -s -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}' \
  http://localhost:9545 | jq .
```

<Info>
  During the initial EL-driven sync (`--syncmode=execution-layer`), `unsafe_l2` tracks the tip via libp2p gossip while `safe_l2` and `finalized_l2` stay at 0. This is expected — op-node defers L1 derivation until op-reth finishes its staged sync. Once the EL catches up, derivation begins and the safe/finalized heads start advancing.
</Info>

You can also tail op-node logs directly:

```bash theme={null}
docker compose logs op-node | grep -E "Sync progress|Finished EL sync"
```

## Bootstrap from a snapshot

Syncing from scratch is fine for OP Sepolia (\~30–50 GB, hours) but slow for OP Mainnet (\~700 GB, days). For Mainnet — or anytime you'd rather skip the initial sync — bootstrap op-reth from a pre-synced snapshot.

<Info>
  For OP Mainnet, a snapshot also handles the pre-Bedrock state requirement (see the warning at the top of this page) in one step.
</Info>

<Steps>
  <Step title="Stop the containers">
    ```bash theme={null}
    docker compose down
    rm -rf reth-data       # only if you previously synced and want to start clean
    ```
  </Step>

  <Step title="Download a snapshot">
    Browse [datadirs.optimism.io](https://datadirs.optimism.io/) for the snapshot matching your network and pick a recent file. Then download and verify the SHA256:

    ```bash theme={null}
    curl -fLO https://datadirs.optimism.io/<snapshot-file>.tar.zst

    # Verify checksum against the value on the index page
    sha256sum <snapshot-file>.tar.zst   # Linux
    shasum -a 256 <snapshot-file>.tar.zst   # macOS
    ```
  </Step>

  <Step title="Extract into the datadir">
    The bind-mounted directory is `./reth-data` (relative to your `docker-compose.yml`):

    ```bash theme={null}
    mkdir -p reth-data
    tar -I zstd -xvf <snapshot-file>.tar.zst -C reth-data --strip-components=1
    ```

    `--strip-components=1` removes the top-level wrapping directory inside the tarball. Run `tar -tf <snapshot-file>.tar.zst | head -3` first to confirm — if files are already at the archive root, omit `--strip-components`.
  </Step>

  <Step title="Start the node">
    ```bash theme={null}
    docker compose up -d
    docker compose logs -f op-reth
    ```

    op-reth will recognize the existing datadir on startup and pick up from the snapshot's tip — `latest_block` should be the snapshot's height, not 0. It will then catch up from that tip to current (minutes for Sepolia, hours for Mainnet, depending on snapshot age).
  </Step>
</Steps>

## Next steps

* [Node Metrics and Monitoring Guide](/node-operators/guides/monitoring/metrics) — wire up Prometheus/Grafana against the metrics port.
* [Node Troubleshooting Guide](/node-operators/guides/troubleshooting) — if you run into problems.
* [Building and running an OP Stack node from source](/node-operators/tutorials/run-node-from-source) — if you need a custom build or want to inspect the source.
