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 asghcr.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: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
TheDockerfile 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
/appand the model to/app/models, runs as the unprivilegednodeuser, listens on port8080, and runsnode dist/main.js. AHEALTHCHECKcalls/healthevery 15 seconds.
/health comes from the RAIL402_VERSION build argument, dev by default.
Steps
1
Clone and build
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 The command prints the sponsor’s public key (The service refuses to report ready while the sponsor holds less than
.env in the repository root (it is
git-ignored):G…). Fund it with Friendbot, which gives a new testnet
account 10,000 XLM:MIN_SPONSOR_BALANCE_XLM
(25 XLM by default).3
Start Postgres and the service
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:
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 forstellar: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.