All three services ship as published container images:
ghcr.io/dobjlabs/digital-objects-network/synchronizerghcr.io/dobjlabs/digital-objects-network/relayerghcr.io/dobjlabs/digital-objects-network/archiver
Run the full stack, or just a synchronizer (with its archiver) to independently verify the network's canonical state from Ethereum chain data without trusting anyone else's instance.
| Service | CPU | RAM | Disk |
|---|---|---|---|
synchronizer |
2 cores | 2 GB | 20 GB (Postgres + RocksDB; grows with chain history) |
relayer |
1 core | 512 MB | 2 GB (Postgres relay job queue) |
archiver |
1 core | 512 MB | 50 GB (grows at ~128 KB per archived blob) |
These figures are for running each service. Running the full compose stack on a
single host requires their combined resources. The archiver's disk usage grows
proportionally with the number of blobs matching FILTER_ADDRESS; size the
volume accordingly and monitor it. The synchronizer's RocksDB store
(APP_STATE_DB_PATH) is a rebuildable cache, but mounting a volume for it
avoids a slow cold re-sync on restart.
cp deploy/.env.example deploy/.env # fill in your endpoints
docker compose -f deploy/compose.yaml up -dThis brings up Postgres plus all three services. Check them:
curl localhost:3000/healthz # synchronizer up
curl localhost:3000/sync-progress # is it following chain head?
curl localhost:3200/healthz # relayer up
curl localhost:3001/healthz # archiver up(Requires the published images. If they aren't published yet, build them locally first - see "Building the images yourself" below.)
A synchronizer needs an execution RPC, a beacon endpoint, and Postgres - no
wallet key. With just those it reads blobs from the beacon (recent blobs only).
To sync history older than the beacon's ~18-day retention, also run an
archiver and point ARCHIVER_URL at it. The bundled compose includes one:
docker compose -f deploy/compose.yaml up -d synchronizer postgresOr fully standalone against your own Postgres, no compose. Create the
synchronizer database first - the service creates its tables, but not the
database. ARCHIVER_URL is optional: unset, the synchronizer reads blobs from
BEACON_URL (recent blobs only); set it to an archiver for older history:
psql "postgres://user:pass@host:5432/postgres" -c 'CREATE DATABASE synchronizer'
docker run -d --name synchronizer -p 3000:3000 \
-e RPC_URL=https://your-execution-rpc \
-e BEACON_URL=https://your-beacon-api \
-e TO_ADDRESS=0x... \
-e SYNC_METADATA_DB_URL=postgres://user:pass@host:5432/synchronizer \
-v don_data:/var/lib/don \
ghcr.io/dobjlabs/digital-objects-network/synchronizer:latestTo run an archiver standalone, give it the same target address (as
FILTER_ADDRESS), a start slot, and a volume for its on-disk blob store:
docker run -d --name archiver -p 3001:3001 \
-e RPC_URL=https://your-execution-rpc \
-e BEACON_URL=https://your-beacon-api \
-e FILTER_ADDRESS=0x... \
-e INIT_START_SLOT=10413441 \
-v don_blobs:/var/lib/don \
ghcr.io/dobjlabs/digital-objects-network/archiver:latestSet via .env (compose) or -e flags (docker run). Image defaults:
HTTP_BIND=0.0.0.0:<port>, and the synchronizer's
APP_STATE_DB_PATH=/var/lib/don/synchronizer-db.
| Variable | Service | Required | Notes |
|---|---|---|---|
RPC_URL |
all | yes | Execution-layer RPC |
BEACON_URL |
synchronizer, archiver | yes | Beacon API with blob sidecars |
TO_ADDRESS |
all | yes | L1 target address; must match across services (archiver reads FILTER_ADDRESS) |
INIT_START_SLOT |
synchronizer, archiver | yes | Beacon slot both services start from on first run |
PRIVATE_KEY |
relayer | relayer only | Hot wallet that signs/pays for blob txs |
ARCHIVER_URL |
synchronizer | no | Blobs older than beacon retention; unset, falls back to BEACON_URL |
SYNC_METADATA_DB_URL |
synchronizer | no | Defaults to the bundled Postgres; set to point at an external one |
DB_URL |
relayer | no | Defaults to the bundled Postgres; set to point at an external one |
BLOBS_PATH |
archiver | no | On-disk blob store path; mount a volume here |
IMAGE_TAG |
compose | no | Image tag to run; pin to a release |
APP_STATE_DB_PATH |
synchronizer | no | RocksDB cache path; mount a volume here |
HTTP_BIND |
all | no | Defaults to 0.0.0.0:3000 / :3200 / :3001 |
RUST_LOG |
all | no | e.g. info |
The synchronizer's /var/lib/don holds RocksDB - a rebuildable cache (the
authoritative state lives in Postgres), but mounting a volume avoids a slow cold
re-sync on restart. The relayer keeps no local state. The archiver's
/var/lib/don holds its blob store on disk (no database); mount a volume so
the archive survives restarts instead of re-downloading from the chain.
- Each service is a singleton in a single deployment. Never run two against the same database or volume (the relayer collides on nonces, the synchronizer on slot commits, the archiver on its blob-store directory). The network can have many independent archivers; just never point two at one volume.
- Pin
IMAGE_TAGto a release (e.g.v0.1.0) for reproducible runs. - Hardened production: point
SYNC_METADATA_DB_URL/DB_URLat a managed Postgres (the synchronizer's state is authoritative - back it up) and injectPRIVATE_KEYfrom a secret store instead of a plaintext.env. The relayer has no runtime balance check, so monitor the wallet balance externally.
The compose pulls published images. To build from source instead, tag them with
the names the compose expects and point IMAGE_TAG at your tag:
docker build --target synchronizer -t ghcr.io/dobjlabs/digital-objects-network/synchronizer:dev .
docker build --target relayer -t ghcr.io/dobjlabs/digital-objects-network/relayer:dev .
docker build --target archiver -t ghcr.io/dobjlabs/digital-objects-network/archiver:dev .
# then set IMAGE_TAG=dev in deploy/.env