Deploying an EVM archive

A DevOps playbook for standing up a self-hosted SQD archive for an EVM chain — using etherlink-archive-previewnet as the worked example.

What you get. An archive is a fast, queryable, compressed copy of an EVM chain's whole history — columnar and batch-queryable, so an indexer can backfill the full chain in one pass instead of paging it over RPC.

How to reuse this. The stack below is identical for any EVM runtime. To bring up a new chain you change a handful of values (node URL · hostnames · dataset · first block) and deploy — everything else stays the same.

1What an archive is

The stack is three small services sharing one data volume. You don't need to know their internals — just what each one is for:

blocks chunks reads pings state (~10s) GET height / worker plain-text int + URL POST query — straight to the worker, never through the router chain node external RPC ingest reads the node shared volume the archive on disk worker answers queries apps / DipDup external clients router reports height · hands back the worker URL
Green = query data, which flows between the app and the worker directly. Blue (dashed) = control only: the worker pings the router with its progress, and apps ask the router for the height and the worker's URL. The router returns those as plain text — it never proxies a query.
Portainer Services list showing the three running services of the etherlink-archive-previewnet stack
The deployed stack in Portainer — three services, all 1/1 replicas. Only router runs our image; ingest and worker are the upstream subsquid images.

2What you configure per chain

These are the values that change when you point the stack at a different EVM runtime. Everything else in the compose file is boilerplate you can leave untouched.

Each row is a variable the compose file substitutes, and the exact ingest/worker flag or traefik label it lands in — so you can trace it straight into docker-compose.yml.

VariableWhere it lands (real compose flag / label)Example (previewnet)
ETHERLINK_NODE_URL ingest -e — the EVM node to pull blocks from https://evm.previewnet.tezosx.nomadic-labs.com
FIRST_BLOCK ingest --first-block; also read by the router as the archive's start block 0
ENDPOINT_CAPACITY · RPS_LIMIT
BATCH_LIMIT · WRITE_CHUNK_SIZE
ingest -c · -r · --batch-limit · --write-chunk-size — throttle to the node's capacity 20 · 250
90 · 500
WORKER_DIR ingest --dest — where chunks are written. The dataset is the chain's id (the string consumers query); DATA_DIR + / + its base64url encoding:
printf shadownet | basenc --base64url | tr -d '=' → c2hhZG93bmV0
/data/worker/c2hhZG93bmV0
DATA_DIR the router scans this dir for every dataset folder under it — same shared volume as WORKER_DIR /data/worker
ROUTER_URL worker --router — internal Swarm DNS the worker pings http://…_router:5000
WORKER_URL worker --worker-url + router fallback — the worker's bare public base; the router appends /query itself https://…-worker.dipdup.net
ROUTER_EXTERNAL_HOST
WORKER_EXTERNAL_HOST
traefik Host() rule → the public URLs. Indexers point at the router host (see below) etherlink-archive-previewnet.dipdup.net
ROUTER_TRAEFIK_SERVICE
WORKER_TRAEFIK_SERVICE
names for the traefik service/router labels — must be unique per stack …-subsquid-router
NODE_HOSTNAME Swarm placement constraint — pins all three services to one node 25mm

In Portainer these live in the stack's Environment variables section (the same names the compose file substitutes). To stand up a new chain, copy the stack and change just the rows above:

Portainer stack editor showing the environment variables as key=value lines
The stack's environment variables in Portainer. Set these, then deploy/update the stack — Portainer pulls the (private) router image with its configured GHCR credentials, no extra setup per stack.

what consumers point at

An indexer (DipDup, an explorer, …) sets each index's archive URL to the router's external host plus the dataset namehttps://<ROUTER_EXTERNAL_HOST>/<dataset>, e.g. https://etherlink-archive-previewnet.dipdup.net/shadownet. That one URL is the whole public interface of the archive; everything else in the table above is internal plumbing.

3Verify & troubleshoot

After deploying, check the router over HTTP — no browser needed. A healthy archive reports a rising height:

curl https://<ROUTER_EXTERNAL_HOST>/height     # a number that grows; e.g. 800032
curl https://<ROUTER_EXTERNAL_HOST>/datasets   # the chain(s) served, e.g. ["shadownet"]

Height climbs as ingest writes more blocks. A brand-new archive starts low and catches up to the chain tip over time.

Common gotchas

Companion files in this folder: docker-compose.yml (the full stack), .env.example (all variables to fill in), and README.md (a short runbook for standing up a new chain). Source of truth lives in the deployments repo at stacks/etherlink-archive/.