Skip to main content
The repository’s Dockerfile builds the service image and docker-compose.yml runs it next to Postgres. This page takes a fresh clone to a ready testnet facilitator on localhost:8080.
You need Docker with the Compose v2 plugin and git. Node.js and pnpm are not needed on the host: the image builds with its own toolchain.

Published image

Every release is published to the GitHub Container Registry as ghcr.io/tolgayayci/rail402:<version> and :latest, with /health reporting that version. Without a clone, a testnet facilitator that keeps its state in memory is three commands:
STORE=memory suits trying it out: settlements are not durable and only one replica is safe. For a durable setup, use Postgres as below; in the compose file or your own, the published image can replace rail402:local.

One command

From a clone, with Docker (Compose v2), curl and bash:
The script builds the image, creates a testnet fee sponsor inside it and funds the sponsor with Friendbot, writes TESTNET_RPC_URL, TESTNET_SPONSOR_SECRET and SEARCH_CURSOR_SECRET to .env (git-ignored, mode 600), starts Postgres and the service with the service profile, and waits until http://127.0.0.1:8080/ready passes. It never overwrites an existing .env: run it again and it reuses the file and only starts the stack. The steps below do the same by hand.

The image

The Dockerfile has two stages on node:24.19.0-trixie-slim (Debian, because the embedding runtime needs glibc):
  • build installs pnpm 11.22.0, installs dependencies offline from the lockfile, compiles the service, downloads the pinned embedding model and checks its SHA-256 hashes, and deploys the service with production dependencies only.
  • runtime copies the service to /app and the model to /app/models, runs as the unprivileged node user, listens on port 8080, and runs node dist/main.js. A HEALTHCHECK calls /health every 15 seconds.
The version reported by /health comes from the RAIL402_VERSION build argument, dev by default.

Steps

1

Clone and build

This tags the image rail402:local. To stamp a version, build it directly: docker build --build-arg RAIL402_VERSION=$(git rev-parse --short HEAD) -t rail402:local .
2

Create a sponsor account

The sponsor pays settlement fees and the reserves of the channel accounts. Generate a fresh key with the image you just built, and write the service’s secrets to .env in the repository root (it is git-ignored):
The command prints the sponsor’s public key (G…). Fund it with Friendbot, which gives a new testnet account 10,000 XLM:
The service refuses to report ready while the sponsor holds less than MIN_SPONSOR_BALANCE_XLM (25 XLM by default).
3

Start Postgres and the service

Compose starts Postgres, waits for it to be healthy, then starts the service with DATABASE_URL=postgres://rail402:rail402@postgres:5432/rail402 and the variables in .env. On first start the service applies its database migrations and creates its 8 channel accounts on testnet, paid by the sponsor.
4

Check readiness

/ready answers 503 with the same body until every check passes. /supported lists the sponsor and the channel accounts as signers.
5

Pay through it

From a source checkout, the canonical client run pays a stock seller through your instance with fresh Friendbot-funded accounts and checks the balances:
It prints the settlement transaction and the buyer’s and seller’s USDC balances before and after.

Configuration for a working self-host

Every other setting has a default. The full list is on the Configuration page. The service validates its configuration at startup and exits with code 78 and a message naming the variable when something is missing or malformed:

Compose profiles

Every published port (5432, 8000 and 8080) is bound to 127.0.0.1. To expose the service, put a TLS-terminating reverse proxy in front of it and set TRUSTED_PROXY_HOPS. The bundled Postgres keeps its data in the postgres-data volume; for anything beyond a trial, use a managed Postgres with point-in-time recovery (see Operations).

Operate

channels status prints one JSON line per network:
On SIGTERM the service stops accepting requests, lets in-flight settlements finish for up to SHUTDOWN_GRACE_MS (30 s), reconciles once more and exits. Compose allows 40 seconds (stop_grace_period).

Pubnet

The image can be configured for stellar:pubnet, but pubnet has not been exercised end to end. Its configuration has stricter rules, checked at startup: an https RPC URL, an explicit PUBNET_REQUIRE_API_KEY, and channel accounts that are not created at startup but provisioned by an operator. See Configuration and Operations.