> For the complete documentation index, see [llms.txt](https://docs.gravity.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gravity.xyz/gravity-networks/run-a-mainnet-pfn-with-docker.md).

# Run a Gravity Mainnet Public Full Node With Docker

This guide walks you through running a Gravity Mainnet Public Full Node (PFN) with the `gravity_node` Docker image. A PFN syncs from an upstream public-network seed and can serve EVM JSON-RPC for applications, indexers, or internal services.

A PFN does **not** participate in consensus, does **not** hold validator stake, and does **not** require validator invite access.

> **Placeholders.** Commands below use `<YOUR_...>` placeholders. Replace each value before running. Never commit generated identities, private keys, `.env` files, rendered node config, logs, or node databases to version control.

## Prerequisites

1. **Docker and Docker Compose v2.** Production nodes should run on Linux. Docker Desktop on macOS is useful for local validation, but it handles host networking differently from Linux.
2. **Local command-line tools.** Install `just`, `envsubst` from GNU gettext, `jq`, `curl`, `nc`, and `lsof`.
3. **The Gravity SDK repository.** The Docker image and config templates live in [`gravity-sdk`](https://github.com/Galxe/gravity-sdk).

   ```bash
   git clone https://github.com/Galxe/gravity-sdk.git
   cd gravity-sdk
   ```
4. **Mainnet `genesis.json` and `waypoint.txt`.** These artifacts pin the exact chain you are joining. Download the canonical files from [`gravity-sdk/genesis/mainnet`](https://github.com/Galxe/gravity-sdk/tree/main/genesis/mainnet).
5. **An upstream public-network seed.** The current public PFN seed is shown below.
6. **SSD/NVMe storage.** Chain data grows with uptime. Budget well above the current chain size.

## 1. Build or Load the Docker Image

From the `gravity-sdk` repository root:

```bash
TAG=$(git rev-parse --short HEAD)

DOCKER_BUILDKIT=1 docker build --network=host \
  -f docker/gravity_node/Dockerfile \
  -t gravity_node:${TAG} \
  --build-arg CARGO_PROFILE=release \
  .
```

If your operator package provides a prebuilt image, load or pull that image instead and set `IMAGE_TAG` to its tag in later commands.

## 2. Prepare the Docker Workspace

```bash
cd docker/gravity_node
cp .env.example .env
mkdir -p config
```

Edit `.env`:

```dotenv
GRAVITY_IMAGE=gravity_node
IMAGE_TAG=<YOUR_IMAGE_TAG>
RUST_LOG=info
```

The container reads node config from `./config` and stores chain data in Docker volumes.

## 3. Prepare Mainnet Artifacts

From `docker/gravity_node`:

```bash
mkdir -p ../../cluster/output
curl -fsSL https://raw.githubusercontent.com/Galxe/gravity-sdk/main/genesis/mainnet/genesis.json \
  -o ../../cluster/output/genesis.json
curl -fsSL https://raw.githubusercontent.com/Galxe/gravity-sdk/main/genesis/mainnet/waypoint.txt \
  -o ../../cluster/output/waypoint.txt
```

Copy these artifacts into the Docker config directory:

```bash
cp ../../cluster/output/genesis.json config/genesis.json
cp ../../cluster/output/waypoint.txt config/waypoint.txt
```

## 4. Gather Seed Information

At the moment, public internet access is intended for the mainnet RPC/PFN fleet. Other validator and VFN hosts should be treated as private/internal unless the Gravity team explicitly shares invite-only seed information.

| Node    | Host                            | Public-network seed port | Peer ID                                                            | Network public key                                                 |
| ------- | ------------------------------- | ------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| `rpc-1` | `mainnet-rpc-p2p-1.gravity.xyz` | `6180`                   | `38013b46c21388c3fd08ab32b86b478b3109125566d63c2da8fdc941dc474077` | `234aee14677a3d2198208ea72ca5e95ed75520df27f01f0c36220303ff78642f` |

Before using the seed, verify from your node host that the public-network port is reachable:

```bash
nc -vz mainnet-rpc-p2p-1.gravity.xyz 6180
```

Use the current public PFN seed when rendering `PFN_SEEDS_BLOCK` in the Docker config section below:

```toml
seeds = [
  { peer_id = "38013b46c21388c3fd08ab32b86b478b3109125566d63c2da8fdc941dc474077", role = "PreferredUpstream", address = "/dns/mainnet-rpc-p2p-1.gravity.xyz/tcp/6180/noise-ik/234aee14677a3d2198208ea72ca5e95ed75520df27f01f0c36220303ff78642f/handshake/0" },
]
```

## 5. Generate a PFN Identity

Create a minimal cluster config file for identity generation, for example `cluster/local-pfn-docker.toml`:

```toml
[cluster]
name = "<YOUR_PFN_CLUSTER_NAME>"

[[nodes]]
id = "local-pfn-docker"
role = "pfn"
identity = { source = "file" }
```

The Docker flow uses this TOML only to generate node identity material. It does not read ports, seeds, `base_dir`, or `data_dir` from the TOML during container startup. Docker runtime storage is controlled by the volume or bind mount in the start command, and by the container paths rendered in the next section.

Generate the PFN identity:

```bash
cd ../../cluster
just init local-pfn-docker.toml
cd ../docker/gravity_node
cp ../../cluster/output/local-pfn-docker/config/identity.yaml config/identity.yaml
```

The generated `identity.yaml` contains private node identity material. Keep it on the runtime host only.

## 6. Render Container Config

The container sees config at `/gravity/config` and data at `/gravity/data`, so the rendered files must use container paths, not host paths.

`DATA_DIR`, `STORAGE_DIR`, and `LOG_DIR` are intentionally separate:

```
/gravity/data/              # Docker data volume root
└── data/                   # STORAGE_DIR, chain databases
    ├── consensus_db/
    ├── quorumstoreDB/
    └── reth/

/gravity/logs/              # Docker logs volume root
├── consensus_log/
└── execution_logs/
```

To place data on a specific host disk, mount that host path to `/gravity/data`. To place logs on a specific host disk, mount that host path to `/gravity/logs`. Do not change the TOML `data_dir` for Docker runtime storage.

From `docker/gravity_node`:

```bash
export NODE_ID=local-pfn-docker
export HOST=127.0.0.1
export PUBLIC_PORT=26180
export RPC_PORT=28545
export METRICS_PORT=29001
export INSPECTION_PORT=30001
export AUTHRPC_PORT=28661
export P2P_PORT_RETH=32024
export TXPOOL_MAX_ACCOUNT_SLOTS=64
export PRUNE_TRANSACTIONLOOKUP_DISTANCE=10064

export CONFIG_DIR=/gravity/config
export DATA_DIR=/gravity/data
export STORAGE_DIR=/gravity/data/data
export LOG_DIR=/gravity/logs
export GENESIS_PATH=/gravity/config/genesis.json

export RPC_HTTP_CORSDOMAIN='*'
export RPC_HTTP_API='debug,eth,net,trace,txpool,web3,rpc'

export NETWORK_IDENTITY_TYPE=from_file
export NETWORK_IDENTITY_FIELD=path
export NETWORK_IDENTITY_VALUE=/gravity/config/identity.yaml
export SAFETY_RULES_IDENTITY_VARIANT=from_file
export SAFETY_RULES_IDENTITY_KEY=identity_blob_path
export SAFETY_RULES_IDENTITY_VALUE=/gravity/config/identity.yaml
export DISCOVERY_METHOD_FULLNODE_BLOCK=''
export PFN_SEEDS_BLOCK='    seeds:
      "0x38013b46c21388c3fd08ab32b86b478b3109125566d63c2da8fdc941dc474077":
        addresses:
          - "/dns/mainnet-rpc-p2p-1.gravity.xyz/tcp/6180/noise-ik/234aee14677a3d2198208ea72ca5e95ed75520df27f01f0c36220303ff78642f/handshake/0"
        role: PreferredUpstream'

envsubst < ../../cluster/templates/public_full_node.yaml.tpl \
  > config/public_full_node.yaml

envsubst < ../../cluster/templates/reth_config_pfn_prune.json.tpl \
  > config/reth_config.json
```

If you do not want transaction-lookup pruning, do not set `PRUNE_TRANSACTIONLOOKUP_DISTANCE`, and render `reth_config.json` from the non-prune template instead:

```bash
envsubst < ../../cluster/templates/reth_config_pfn.json.tpl \
  > config/reth_config.json
```

Verify the rendered config:

```bash
jq . config/reth_config.json >/dev/null
grep -n 'public_full_node.yaml\|prune.transactionlookup.distance\|gravity/config\|gravity/data' \
  config/reth_config.json
grep -n 'PreferredUpstream\|mainnet-rpc-p2p-1.gravity.xyz\|/gravity/config/identity.yaml' \
  config/public_full_node.yaml
```

Expected `reth_config.json` behavior:

* `chain` points to `/gravity/config/genesis.json`;
* `gravity_node_config` points to `/gravity/config/public_full_node.yaml`;
* `datadir` points to `/gravity/data/data/reth`;
* `log.file.directory` points to `/gravity/logs/execution_logs/`;
* when pruning is enabled, `prune.transactionlookup.distance` is `10064`.

## 7. Pruning Mode

`prune_transactionlookup_distance = 10064` starts reth with transaction-lookup pruning enabled. This keeps recent transaction-hash lookup data while allowing older lookup indexes to be pruned.

Use a value **greater than or equal to `10064`** for production PFN/RPC nodes. Treat smaller values as test-only and reject them during config review.

Use this pruning mode when you want lower execution database growth and do not need arbitrary old `eth_getTransactionByHash` lookups from this node.

If you omit `prune_transactionlookup_distance`, the generated PFN reth config uses archive-style transaction lookup behavior and keeps historical lookup indexes instead of pruning them. That is simpler for indexers or debugging flows that need old transaction hashes, but it uses more disk over time.

Do not set `prune_transactionlookup_distance = 0` to disable pruning. A distance of `0` is still a pruning mode and can prune transaction lookup data aggressively. To run without transaction-lookup pruning, leave the field out.

## 8. Optional: Warm Start From a Snapshot or Existing Node

A new Docker PFN can sync from genesis, but that may take a long time on an established chain. To warm start, load the snapshot into the Docker data volume **before the first node start**.

If the Docker PFN has already started once, stop the container and remove the old data volume before importing a snapshot. Do not merge snapshot files into an already-initialized database.

The Docker container mounts its data volume at `/gravity/data`. The chain databases must live under `/gravity/data/data`, so extract snapshot contents into that `data` subdirectory.

### Option A: Download the Public Mainnet Data Snapshot

Gravity publishes daily mainnet PFN data snapshots in the public bucket. Pick the latest available date and download it to your node host:

```bash
curl -s 'https://storage.googleapis.com/storage/v1/b/gravity-public-bucket/o?prefix=gravity-mainnet-data/&fields=items(name,size,updated)' \
  | jq -r '.items[] | "\(.name) \(.size) \(.updated)"'

SNAPSHOT_DATE=<YYYY-MM-DD>
curl -L --fail --continue-at - \
  "https://storage.googleapis.com/gravity-public-bucket/gravity-mainnet-data/${SNAPSHOT_DATE}.tar" \
  -o "/tmp/${SNAPSHOT_DATE}.tar"
```

The snapshot tar contains database directories such as `consensus_db/`, `quorumstoreDB/`, and `reth/` at the archive root. It does not contain an outer `data/` directory.

For the Linux compose flow, load the snapshot into the compose data volume:

```bash
docker compose run --rm --no-deps \
  --user root \
  --entrypoint sh \
  -e SNAPSHOT_DATE \
  -v /tmp:/snapshot:ro \
  gravity_node \
  -lc 'mkdir -p /gravity/data/data && \
       tar -xf "/snapshot/${SNAPSHOT_DATE}.tar" -C /gravity/data/data && \
       chown -R 10001:10001 /gravity/data /gravity/logs'
```

For the macOS `docker run` flow, load the snapshot into the explicit named volume used by the start command below:

```bash
set -a
. ./.env
set +a

docker volume create gravity_node_local_pfn_data
docker run --rm \
  --user root \
  --entrypoint sh \
  -e SNAPSHOT_DATE \
  -v gravity_node_local_pfn_data:/gravity/data \
  -v gravity_node_local_pfn_logs:/gravity/logs \
  -v /tmp:/snapshot:ro \
  ${GRAVITY_IMAGE}:${IMAGE_TAG} \
  -lc 'mkdir -p /gravity/data/data && \
       tar -xf "/snapshot/${SNAPSHOT_DATE}.tar" -C /gravity/data/data && \
       chown -R 10001:10001 /gravity/data /gravity/logs'
```

After extraction, the Docker data volume should contain:

```
/gravity/data/data/consensus_db/
/gravity/data/data/quorumstoreDB/
/gravity/data/data/reth/
```

### Option B: Copy From an Existing Node You Control

If you already run a trusted RPC/PFN, you can copy only the chain databases from that node:

```bash
cd <YOUR_EXISTING_NODE_DATA_DIR>
tar -cf /tmp/pfn-data.tar consensus_db quorumstoreDB reth
```

Then load `/tmp/pfn-data.tar` into the Docker data volume with the same compose or `docker run` extraction pattern above. Mount `/tmp` as `/snapshot` and replace the extraction command with:

```bash
tar -xf /snapshot/pfn-data.tar -C /gravity/data/data
```

Do **not** copy `rand_db` or `secure_storage.json` from another node. Those files carry node-specific randomness or safety state and should not be reused.

## 9. Start the PFN

Make sure the selected host ports are free:

```bash
lsof -nP -iTCP:26180 -iTCP:28545 -iTCP:29001 -iTCP:30001 -iTCP:28661 -iTCP:32024
```

On Linux, use Docker Compose:

```bash
docker compose up -d
docker ps --filter name=gravity_node
```

The compose file uses `network_mode: host` because p2p ports need predictable advertised addresses.

On Docker Desktop for macOS, host networking behaves differently. For local validation from the macOS host, use explicit port publishing:

```bash
docker rm -f gravity_node 2>/dev/null || true
set -a
. ./.env
set +a

docker run -d --name gravity_node --restart unless-stopped \
  -p 26180:26180 \
  -p 28545:28545 \
  -p 29001:29001 \
  -p 30001:30001 \
  -p 28661:28661 \
  -p 32024:32024 \
  -v "$(pwd)/config:/gravity/config:ro" \
  -v gravity_node_local_pfn_data:/gravity/data \
  -v gravity_node_local_pfn_logs:/gravity/logs \
  -e RUST_BACKTRACE=1 \
  -e RUST_LOG=info \
  --ulimit nofile=1048576:1048576 \
  ${GRAVITY_IMAGE}:${IMAGE_TAG}
```

## 10. Verify Sync

Query the local Docker PFN:

```bash
curl -s --noproxy '*' http://127.0.0.1:28545 \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' | jq -r .result
```

Compare with the public RPC:

```bash
curl -s --noproxy '*' https://mainnet-rpc.gravity.xyz \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' | jq -r .result
```

Follow logs:

```bash
docker logs gravity_node -f
docker exec gravity_node sh -lc \
  'tail -n 200 /gravity/logs/execution_logs/*/reth.log'
docker exec gravity_node sh -lc \
  'tail -n 200 /gravity/logs/consensus_log/vfn.log'
```

Confirm the prune config was accepted:

```bash
docker exec gravity_node sh -lc \
  'grep -R "Pruner initialized\\|prune_config\\|transaction_lookup" /gravity/logs/execution_logs || true'
```

## 11. Stop, Restart, or Reset

For the Linux compose flow, stop the container but keep chain data:

```bash
docker compose down
```

Restart:

```bash
docker compose up -d
```

Delete the local PFN database and start from zero again:

```bash
docker compose down -v
```

For the macOS `docker run` flow, stop the container but keep chain data:

```bash
docker rm -f gravity_node
```

Delete the local PFN database and start from zero again:

```bash
docker rm -f gravity_node
docker volume rm gravity_node_local_pfn_data gravity_node_local_pfn_logs
```

Only remove Docker volumes when you intentionally want to delete the node database.

## Files to Keep Out of Version Control

Do not commit generated runtime files:

* `docker/gravity_node/.env`;
* `docker/gravity_node/config/*`;
* `cluster/output/<YOUR_NODE_ID>/config/identity.yaml`;
* Docker volumes such as `gravity-data`, `gravity-logs`, `gravity_node_local_pfn_data`, and `gravity_node_local_pfn_logs`;
* node databases and logs.

The reusable materials are the public guide and SDK templates. Each operator should generate their own identity and runtime config.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.gravity.xyz/gravity-networks/run-a-mainnet-pfn-with-docker.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
