Skip to content

Cardano Relay Node

This example deploys a Cardano mainnet relay node on Compute Portal — bootstrapped via a Mithril snapshot so the chain DB restores in hours rather than days. The node uses a persistent volume for chain data, a ConfigMap for all mainnet config files, and (optionally) a public TCP endpoint so an external block producer can reach it.

ResourceRole
cardano-relaycardano-node 11.0.1, Cardano mainnet P2P relay
cardano-relay-configConfigMap holding all 6 mainnet config files
/data volume500 GB persistent chain DB (~221 GB today, grows ~2–5 GB/month)

Prerequisites

Server provisioning required

This guide uses a dedicated compute resource (server-type). If you have not yet purchased one, complete your order at computeportal.io/start. Server-type resources take up to 24 business hours to provision — you will receive an email when yours is ready. Return to this page after that email arrives.

New to the two-node model? Read the Cardano Stake Pool Setup guide first.

  • cpctl installed and authenticated (cpctl login --token <token>)
  • A CP account with a dedicated compute resource assigned — the relay needs 6 CPU cores and 20 GiB RAM. Contact support at computeportal.io if not assigned.
  • Verify quota before starting:
bash
cpctl quota
┌──────────────────┬──────────┬──────────┐
│ RESOURCE         │ USED     │ LIMIT    │
├──────────────────┼──────────┼──────────┤
│ CPU              │ 0.00     │ 8.00     │
│ Memory           │ 0.0 GiB  │ 64.0 GiB │
│ Services         │ 0        │ 20       │
└──────────────────┴──────────┴──────────┘

You need at least 6 CPU cores and 20 GiB RAM available.


Step 1 — Prepare config files

Download the six standard Cardano mainnet config files into a data/ directory:

data/
├── config.json          ← main node configuration
├── topology.json        ← P2P peer topology
├── byron-genesis.json
├── shelley-genesis.json
├── alonzo-genesis.json
└── conway-genesis.json

The latest versions are available from the IOHK Cardano configurations page.

topology.json

Use localRoots only with useLedgerAfterSlot: -1 to keep peer discovery IPv4-only and stable. Ledger-based peer discovery triggers DNS lookups for AAAA records, which fail silently if your compute node has no IPv6 routing.

json
{
  "bootstrapPeers": [
    { "address": "backbone.cardano.iog.io", "port": 3001 },
    { "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 }
  ],
  "localRoots": [
    {
      "accessPoints": [
        { "address": "relays-new.cardano-mainnet.iohk.io", "port": 3001 },
        { "address": "relay1.mainnet.cardanofoundation.org", "port": 3001 },
        { "address": "relay2.mainnet.cardanofoundation.org", "port": 3001 },
        { "address": "north-america.relays-new.cardano-mainnet.iohk.io", "port": 3001 },
        { "address": "europe.relays-new.cardano-mainnet.iohk.io", "port": 3001 },
        { "address": "asia-pacific.relays-new.cardano-mainnet.iohk.io", "port": 3001 },
        { "address": "relay.emurgo.io", "port": 3001 }
      ],
      "advertise": false,
      "trustable": true,
      "valency": 6
    }
  ],
  "publicRoots": [],
  "useLedgerAfterSlot": -1
}

Step 2 — Upload config files as a ConfigMap

Upload all six files in one command. The key for each file is its filename; all keys are mounted into the container at /data/config/.

bash
cpctl configmap create cardano-relay-config \
  --from-file data/config.json \
  --from-file data/topology.json \
  --from-file data/byron-genesis.json \
  --from-file data/shelley-genesis.json \
  --from-file data/alonzo-genesis.json \
  --from-file data/conway-genesis.json
Uploading config.json (12 KB)...
Uploading topology.json (1 KB)...
Uploading byron-genesis.json (4 KB)...
Uploading shelley-genesis.json (42 KB)...
Uploading alonzo-genesis.json (1 KB)...
Uploading conway-genesis.json (140 KB)...
✓ Config map "cardano-relay-config" created

name   cardano-relay-config
keys   config.json, topology.json, byron-genesis.json, shelley-genesis.json, alonzo-genesis.json, conway-genesis.json

Step 3 — Deploy with Mithril bootstrap

A single cpctl deploy command starts the node. An init container runs Mithril first, downloads and verifies a recent mainnet snapshot (~221 GB) into the persistent volume, then exits — cardano-node starts only after the snapshot is in place.

bash
cpctl deploy \
  --image      ghcr.io/intersectmbo/cardano-node:11.0.1 \
  --name       cardano-relay \
  --port       3001 \
  --no-probe \
  --init-image ghcr.io/intersectmbo/mithril-client:latest \
  --init-command 'if [ -f /data/db/protocolMagicId ]; then
    echo "DB already bootstrapped, skipping Mithril download"; exit 0
  fi
  echo "Starting Mithril snapshot download..."
  /app/bin/mithril-client cardano-db download latest \
    --download-dir /data --include-ancillary' \
  --init-env CARDANO_NETWORK=mainnet \
  --init-env AGGREGATOR_ENDPOINT=https://aggregator.release-mainnet.api.mithril.network/aggregator \
  --init-env GENESIS_VERIFICATION_KEY=5b3139312c36362c3134302c3138352c3133382c31312c3233372c3230372c3235302c3134342c32372c322c3138382c33302c31322c38312c3135352c3230342c31302c3137392c37352c32332c3133382c3139362c3231372c352c31342c32302c35372c37392c33392c3137365d \
  --init-env ANCILLARY_VERIFICATION_KEY=5b32332c37312c39362c3133332c34372c3235332c3232362c3133362c3233352c35372c3136342c3130362c3138362c322c32312c32392c3132302c3136332c38392c3132312c3137372c3133382c3230382c3133382c3231342c39392c35382c32322c302c35382c332c36395d \
  --mount-configmap cardano-relay-config:/data/config \
  -y

Flag notes:

FlagWhy
--no-probecardano-node doesn't serve HTTP — skip the TCP readiness probe
--init-imageMithril client runs before cardano-node starts
--init-commandIdempotent: skips download if the DB is already present (restart-safe)
--init-envMainnet Mithril aggregator URL and public verification keys
--mount-configmapAll 6 config files mounted at /data/config/ inside the container

Bootstrap timeline

PhaseWhat happensApprox. time
Mithril download~221 GB snapshot streamed to /data/db20–40 min
Mithril verificationAll immutable chunks verified via SPO threshold signatures2–8 hours
cardano-node startupLedger state replayed from snapshot15–30 min
Block syncRemaining blocks synced from peers1–3 hours

Step 4 — Scale resources and attach storage

bash
# 6 CPU cores and 20 GiB RAM — tuned for the RTS flags below
cpctl scale cardano-relay --cpu 6 --ram 20g

# 500 GB persistent volume — gives ~2 years of headroom at current chain growth
cpctl volume add cardano-relay /data --size 500

Set runtime environment variables (Cardano-specific paths and GHC RTS tuning):

bash
cpctl env set cardano-relay \
  CARDANO_PORT=3001 \
  CARDANO_DB=/data/db \
  CARDANO_CONFIG=/data/config/config.json \
  CARDANO_TOPOLOGY=/data/config/topology.json \
  CARDANO_NODE_SOCKET_PATH=/ipc/node.socket \
  NETWORK=mainnet \
  "CARDANO_RTS_FLAGS=+RTS -N4 -A16m -qg1 -qb1 -RTS"

-N4 runs 4 GHC threads — matched to the 6-core allocation (4 worker threads

  • OS and I/O overhead). Adjust CARDANO_RTS_FLAGS via cpctl env set if your CPU allocation differs.

Step 5 — Monitor sync progress

Stream logs while Mithril downloads and cardano-node syncs:

bash
cpctl logs cardano-relay --follow

Once cardano-node is running, check sync progress:

bash
cpctl exec cardano-relay -- cardano-cli query tip --mainnet
json
{
  "block": 13909082,
  "epoch": 654,
  "era": "Conway",
  "hash": "7f3b...",
  "slot": 137842910,
  "slotInEpoch": 125150,
  "slotsToEpochEnd": 307250,
  "syncProgress": "1.00"
}

syncProgress: "1.00" means the relay is fully synced and participating in the Cardano P2P network.


Step 6 — Expose to an external block producer (optional)

If your block producer runs outside Compute Portal (bare metal, VPS, or home machine), it needs to reach the relay over raw TCP on port 3001. Standard HTTPS ingress does not work for Cardano's binary P2P protocol — use cpctl expose to open a direct TCP path.

bash
cpctl expose cardano-relay --port 3001
✓ Exposed cardano-relay at tcp.cpctl.app:3001
  Point your client at: tcp.cpctl.app:3001

Confirm the endpoint:

bash
cpctl expose list cardano-relay
HOST             PORT   NODE PORT   CREATED AT
tcp.cpctl.app    3001   30283       2026-09-05T08:00:00Z

Point your block producer's topology.json at tcp.cpctl.app:3001:

json
{
  "bootstrapPeers": [],
  "localRoots": [
    {
      "accessPoints": [
        { "address": "tcp.cpctl.app", "port": 3001 }
      ],
      "advertise": false,
      "valency": 1,
      "warmValency": 1
    }
  ],
  "publicRoots": [],
  "useLedgerAfterSlot": -1
}

Traffic path: block producer → tcp.cpctl.app:3001 → nginx stream → NodePort → cardano-relay pod

To remove the public endpoint:

bash
cpctl expose delete cardano-relay 3001

Updating config files

To push a topology or config change after initial deployment:

bash
cpctl configmap delete cardano-relay-config
cpctl configmap create cardano-relay-config \
  --from-file data/config.json \
  --from-file data/topology.json \
  --from-file data/byron-genesis.json \
  --from-file data/shelley-genesis.json \
  --from-file data/alonzo-genesis.json \
  --from-file data/conway-genesis.json
cpctl machine restart cardano-relay

The restart picks up the new ConfigMap contents immediately — no redeployment needed.


Useful commands

bash
# Sync progress
cpctl exec cardano-relay -- cardano-cli query tip --mainnet

# Live logs
cpctl logs cardano-relay --follow

# Shell access
cpctl exec cardano-relay -- bash

# Resource usage
cpctl status cardano-relay

# Storage
cpctl volume list cardano-relay

# Metrics
cpctl metrics cardano-relay

Resource sizing reference

ResourceValueNotes
CPU6 cores-N4 RTS threads + OS overhead
RAM20 GiBcardano-node ledger state is memory-resident
Storage500 GB~221 GB today; grows ~2–5 GB/month
Port3001Cardano P2P (binary protocol, not HTTP)

GPU Compute Platform