Guides

Guides · Part 7 of 7

Run your own gateway

Verifying a Label 309 record needs nothing from anyone — no account, no server, no key. Publishing is the other half: it puts a transaction on Cardano, and that costs a fee. The gateway is the piece that pays it. It holds the funded Cardano wallet that anchors the record (and an Arweave wallet for sealed files), builds and submits the transaction, tracks it to confirmation, and serves the records back for standalone verification.

Most of the time you point your SDK or CLI at someone else's gateway. But the whole backend is open source — label-309-gateway, a single Rust binary plus Postgres — so you can run your own. Run one privately for your own apps, or operate one as a paid service for others: it is multi-tenant by design, with many accounts and API keys under one instance. This guide takes you from nothing to a first priced quote using the reference Docker Compose.

Before you start

You need Docker and Docker Compose, and a host with a few gigabytes of free disk. Everything below targets Cardano preprod — the safe posture for bringing a deployment up. Moving to mainnet is a deliberate switch you make later, once the deployment is funded and verified.

Get the code and write the config

Clone the repository and work from deploy/, where the reference docker-compose.yml lives. Copy the example config and edit it for your deployment — network, fee band, storage, and pricing:

git clone https://github.com/cardanowall/label-309-gateway
cd label-309-gateway/deploy
cp ../gateway.example.toml gateway.toml

The example is annotated and already targets preprod, so it boots as-is for a first run.

Create the keyring

Every signing key the gateway uses lives in one age-encrypted keyring file. Create it, then add a Cardano key (preprod), an Arweave key, and a webhook-wrap key. These keyring steps run the gateway binary directly — the keyring is meant to be created on a trusted machine and only its encrypted file copied into the container, so grab a release build from the repository for this one step.

mkdir -p secrets
printf '%s' 'a-strong-passphrase' > secrets/gateway-keyring-passphrase
export GATEWAY_KEYRING_PASSPHRASE="$(cat secrets/gateway-keyring-passphrase)"

gateway keyring init             --path secrets/gateway-keyring.age
gateway keyring add-cardano      --path secrets/gateway-keyring.age --network preprod
gateway keyring add-arweave      --path secrets/gateway-keyring.age
gateway keyring add-webhook-wrap --path secrets/gateway-keyring.age
chmod 600 secrets/*

Each add- step prints the address it just created. Fund the two printed addresses: the Cardano address pays anchoring fees, and the Arweave address funds storage credits for sealed files. Then set the database password the compose reads:

echo 'POSTGRES_PASSWORD=a-strong-db-password' > .env

Bring it up

Provision the operator first. This applies the migrations and prints the root secret exactly once — store it somewhere safe, it is the master control-plane credential:

docker compose run --rm gateway operator bootstrap --label acme

Then start Postgres and the gateway:

docker compose up -d

Register a wallet, an account, and a key

The compose publishes no host port on purpose: the data and control planes share one socket, so exposing it would expose the control plane too. Drive the control plane from inside the container. Open a shell:

docker compose exec gateway sh

Point it at the local control plane with the root secret bootstrap printed, then register the funded Cardano wallet (the address keyring add-cardano printed), create an account, credit it, and mint a data-plane API key scoped for publishing:

export GATEWAY_CONTROL_URL=http://127.0.0.1:8080/control/v1
export GATEWAY_CONTROL_TOKEN='ctl_…the root secret from bootstrap…'

gateway admin wallet register primary addr_test1… preprod
gateway admin account create
gateway admin account fund <account_id> 10000000 "starter credit"   # $10
gateway admin key create <account_id> poe:create,poe:read,account:read

key create prints the API key that drives the data plane. That, plus the base URL, is everything an app needs.

Take your first quote

To reach the data plane from the host during setup, uncomment the loopback ports mapping (127.0.0.1:8080:8080) in docker-compose.yml and run docker compose up -d again — loopback only, never 0.0.0.0. Then lock a price with the API key you just minted:

curl -sS -X POST http://127.0.0.1:8080/api/v1/poe/quote \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"record_bytes":256,"recipient_count":0,"file_bytes_total":0}'

The response carries a quote_id valid for 15 minutes plus a cost breakdown. A publish call consumes that quote_id atomically with the balance debit — the same two-step lock a price, then submit shape the SDK guides use. Your gateway is live.

Where to go next

The running binary serves its own interactive API reference — the data plane at /api/v1/docs and the control plane at /control/v1/docs, each rendered fully offline. Two documents in the repository go deeper: the operator runbook covers funding, credentials, money semantics, and day-2 operations, and the integrator guide covers building a product on top.

Your gateway's data plane is exactly what the SDK and CLI guides mean by a base URL. Swap https://your-gateway.example for the address you now control, hand out API keys, and publish your first PoE through a backend that is entirely yours.

Preprod first, mainnet when you're ready

Everything here targets Cardano preprod — the safe way to bring a deployment up. Moving to mainnet is a deliberate switch: set network = "mainnet" in the config, add a mainnet Cardano key to the keyring, and register the wallet as mainnet. All three have to agree before the gateway will anchor a real transaction.