Skip to content

Latest commit

 

History

History
225 lines (167 loc) · 8.89 KB

docker-compose.md

File metadata and controls

225 lines (167 loc) · 8.89 KB

Docker Compose Setup for Vatix Backend

This guide explains how to use Docker Compose to run the Vatix backend — either just the data layer (for host-run development) or the fully containerized stack.

Prerequisites

Services

Service Profiles Container name Notes
postgres (default) vatix-postgres PostgreSQL 16
redis (default) vatix-redis Redis 7 — caching + job queues
api app, api vatix-backend Fastify HTTP API, port 3000
indexer app, indexer vatix-indexer Stellar event indexer
finalization-worker app, workers, finalization-worker vatix-finalization-worker Resolution finalization loop
oracle-worker app, workers, oracle-worker vatix-oracle-worker Oracle submission queue consumer
settlement-worker app, workers, settlement-worker vatix-settlement-worker Trade settlement queue consumer
migrate tools, migrate vatix-migrate One-off prisma migrate deploy job
load-test tools, load-test vatix-load-test One-off local order-placement load test (~100 rps)

Container names match the ones referenced in docs/runbooks/incident-runbook.md, so commands like docker logs vatix-indexer work as documented there.

postgres and redis have no profiles: entry, so they always start by default — this preserves the original host-run development workflow below. Every application process lives behind a profile so you opt in explicitly.

All application images (api, indexer, finalization-worker, oracle-worker, settlement-worker) run as the non-root vatix user (uid/gid 1001), set in the Dockerfile runtime stage. CI's docker-image-smoke job builds all worker target images and asserts id -u inside each container is non-zero, ensuring non-root execution is consistent across all processes. The api target additionally confirms postgres/redis healthchecks pass with the stack up.

Option A — Data layer only (host-run development)

This is the original workflow: run infra in containers, run the app processes on the host with tsx.

  1. Clone the repository and install dependencies:

    git clone https://github.com/vatix-protocol/vatix-backend.git
    cd vatix-backend
    pnpm install
  2. Copy environment variables:

    cp .env.example .env

    Edit .env if needed (see .env.example for details).

  3. Start the data layer:

    docker compose up -d

    This starts PostgreSQL (on port 5433) and Redis (on port 6379). No app profile is requested, so only postgres and redis come up.

  4. Initialize the database:

    pnpm prisma:generate
    pnpm prisma:migrate dev
  5. Run the backend processes on the host:

    pnpm dev                          # API
    pnpm indexer:dev                  # Indexer
    pnpm workers:finalization:dev     # Finalization worker
    pnpm workers:oracle:dev           # Oracle worker
    pnpm workers:settlement:dev       # Settlement consumer

Option B — Full containerized stack

Build and run every process as a container, using the Dockerfile at the repo root, which defines one build --target per process.

  1. Copy environment variables (same as above):

    cp .env.example .env
  2. Run database migrations before starting the app processes for the first time:

    docker compose --profile migrate up --build migrate
  3. Start everything:

    docker compose --profile app up -d --build

    This builds and starts postgres, redis, api, indexer, finalization-worker, oracle-worker, and settlement-worker.

    To run a subset, use the matching profile instead of app, e.g.:

    docker compose --profile api up -d --build              # postgres + redis + api only
    docker compose --profile workers up -d --build          # postgres + redis + all workers
    docker compose --profile settlement-worker up -d --build # settlement consumer only

Inside the compose network, app containers reach Postgres/Redis via the service DNS names postgres and redis (not the host-mapped localhost:5433 / localhost:6379 from .env.example) — docker-compose.yml overrides DATABASE_URL and REDIS_URL per service for this reason. Every other variable (API keys, Stellar config, log levels, etc.) is read from your local .env via env_file.

Stopping Services

docker compose --profile app down   # stop infra + app containers
docker compose down                 # stop infra only

Add -v to also remove the postgres_data / redis_data volumes.

Useful Commands

  • View running containers:
    docker compose ps
  • View logs for a specific process:
    docker compose logs -f api
    docker logs vatix-indexer --tail 100 --follow
  • Rebuild a single service after a code change:
    docker compose --profile app up -d --build api

Load Testing (local only)

scripts/load-test-orders.ts places signed synthetic orders against POST /v1/orders at a target rate (default ~100 rps) to exercise the API and matching engine under sustained write load.

⚠️ Local use only. This places real rows in whatever database the target API is backed by. It refuses to run against anything other than localhost / 127.0.0.1 / the compose api service unless you pass --allow-remote — never do that against a shared staging or production environment.

Run it via the tools/load-test compose profile once the API is up:

docker compose --profile api up -d --build      # start postgres + redis + api
docker compose --profile tools run --rm load-test

Or against a host-run pnpm dev API, without Docker:

pnpm load-test:orders                    # defaults: ~100 rps for 30s
pnpm load-test:orders -- --rps 50 --duration 10

The target API's default write rate limiter (10 req/60s per IP — see src/api/middleware/rateLimiter.ts) will throttle a single-IP load test almost immediately. To actually sustain the target rps for this local run, raise it just for that process:

RATE_LIMIT_WRITE_MAX=2000 RATE_LIMIT_WRITE_WINDOW_MS=1000 pnpm dev

See the header comment in scripts/load-test-orders.ts for the full option list (--url, --market-id, --traders, etc.) and prerequisites.

SLO gates & the CI nightly job

The run reports a capacity number — capacityRps, the sustained rate of accepted (201) orders — for tuning the admission-control watermarks (SETTLEMENT_LAG_SHED_THRESHOLD et al., see ADMISSION_CONTROL_CONFIG.md), plus successRate (201s excluding 429s) and p50/p95/p99 latency.

Two optional SLO gates make a regression fail loudly instead of silently:

Flag / env Effect
--max-p95-ms <n> / LOAD_TEST_MAX_P95_MS Exit non-zero if observed p95 latency exceeds nms
--min-success-rate <r> / LOAD_TEST_MIN_SUCCESS_RATE Exit non-zero if the 201 rate drops below r (0..1)

With neither set (a local ad-hoc run) the gates are a no-op and the script always exits 0. .github/workflows/nightly-load-test.yml runs this nightly (cron 0 3 * * *) and on workflow_dispatch: it boots Postgres + Redis, seeds an ACTIVE market, starts the API, and runs pnpm load-test:orders with both gates set (defaults: p95 ≤ 1500ms, success rate ≥ 0.95). It never runs on pull requests.

Graceful shutdown

Every process registers SIGINT/SIGTERM handlers (see Graceful Shutdown), and the Dockerfile sets STOPSIGNAL SIGTERM, so docker compose stop / docker stop <container> triggers the same clean shutdown path used for Ctrl+C in local development.


For more details, see the main README.md and Deployment Runbook.