Skip to content

Sirloin Local Development

How to get sirloin running on a laptop, plus the gotchas. Test-execution conventions live in testing-strategy and testing; this doc focuses on the dev loop.

Prerequisites

  • Go matching apps/sirloin/go.mod (currently 1.26, see workflow env).
  • make, git, docker + docker compose.
  • golangci-lint v2.11.4 (CI version; make lint will use it).
  • mockery v3+ (https://github.com/vektra/mockery) — used by make generate-mocks.
  • protoc toolchain only if regenerating proto stubs — see /architecture/services and the root make generate-proto.
  • grpcurl and/or grpc_health_probe for poking the local server.

Clone and configure env

Terminal window
git clone git@github.com:dreamworld-research/beef.git
cd beef
cp .env.example .env

Fill the SIRLOIN_* block in .env. The exhaustive list and required-vs- optional breakdown is in sirloin-env. For local iteration the minimum viable set is:

  • SIRLOIN_STAGE=development
  • SIRLOIN_DATABASE_URL=postgres://sirloin:sirloin@localhost:8800/rump?sslmode=disable
  • SIRLOIN_DATABASE_POOLED_URL= empty for local rump; optional for Neon runtime tests.
  • Inside compose: SIRLOIN_BRAIN_HOST=brain:3000, SIRLOIN_ROUND_HOST=round:8080, and SIRLOIN_BRAIN_API_KEY=1.
  • On the host: SIRLOIN_BRAIN_HOST=localhost:9970 and SIRLOIN_BRAIN_API_KEY=1. For round, run it separately or add a temporary raw gRPC port mapping, then set SIRLOIN_ROUND_HOST=localhost:<port>.
  • SIRLOIN_CLERK_API_KEY=<dev key>
  • S3: any local MinIO or skip features that need it.

For Chargebee/Primer: leave keys empty for offline dev — sirloin logs warn and disables those code paths cleanly (config.go).

To run sirloin locally against Neon, set SIRLOIN_DATABASE_URL to a direct Neon URL with sslmode=require. Keep SIRLOIN_DATABASE_POOLED_URL empty unless you are explicitly testing the pooled runtime path; migrations, startup session setup, and advisory-lock flows depend on the direct URL.

For the compose path, generate the local Neon overrides instead of hand-editing all database variables:

Terminal window
neonctl auth
NEON_PROJECT_ID='<project-id>' NEON_CREATE_BRANCH=1 make neon-local-env
# Creates local/<git user.email> from staging by default.
# Later runs can omit NEON_CREATE_BRANCH and reuse local/<git user.email>.
make dev-up-neon-d

make neon-local-env uses the authenticated neonctl CLI session and defaults the Neon branch to a stable name derived from git config user.email when NEON_BRANCH is omitted; new branches fork from staging by default. make dev-up-neon-d skips the local rump service. For a smaller loop, pass NEON_COMPOSE_SERVICES="sirloin brain redis round".

Run

Full stack via docker compose

Terminal window
make dev-build # build all images
make dev-up-d # start full stack
docker compose logs -f sirloin

Compose maps 8086 (MCP), 8087 (webhooks), 50051 (gRPC). The local Postgres (rump) is at localhost:8800. gRPC UI helpers are exposed at localhost:8820 for sirloin and localhost:8821 for round (docker-compose.yml).

Sirloin only, on the host

Terminal window
cd apps/sirloin
make run-dev # SIRLOIN_STAGE=development go run cmd/app/main.go
# or:
make build && ./bin/sirloin

Bring up only the dependencies in compose:

Terminal window
docker compose up -d rump redis brain round

Hot reload

An air config ships at apps/sirloin/.air.linux.conf. Run make run-air (or make run-public-air for the host-bound variant) — both wired in apps/sirloin/Makefile:44-:51. make run-dev + manual restart is the no-tool fallback.

Tests

Terminal window
cd apps/sirloin
make run-tests # unit tests with -race
make run-tests-all # unit + sandbox + integration (sequential, real Chargebee)
make run-tests-coverage
make modernize # rewrite via golang.org/x/tools
make ci-all # full CI gate locally

make run-tests-all requires SIRLOIN_CHARGEBEE_* env vars (real sandbox) and uses -p 1 to avoid Chargebee rate limits. See testing-strategy.

Single test:

Terminal window
go test -v -run TestPaymentProcessor_Recording ./internal/app/services/billing/payments/

Mock and proto regeneration

Terminal window
make generate-mocks # mockery v3, config in .mockery.yml; mocks live next to ifaces
make generate-docs # Swagger docs

Proto regeneration is repo-level:

Terminal window
make generate-proto # at repo root; outputs to apps/sirloin/internal/pkg/pb/

Common dev commands

Terminal window
# gRPC list services (sirloin must be running)
grpcurl -plaintext localhost:50051 list
# call BillingService.ListProducts
grpcurl -plaintext -d '{}' localhost:50051 sirloin.v5.BillingService/ListProducts
# health
grpc_health_probe -addr=localhost:50051
# DB shell (compose Postgres)
docker compose exec rump psql -U sirloin -d rump
# CI mirror locally
cd apps/sirloin && make ci-all

Local dev sequence

sequenceDiagram
participant Dev
participant Sirloin as sirloin (host)
participant Rump as rump (compose)
participant Brain as brain (compose)
Dev->>Sirloin: make run-dev
Sirloin->>Rump: connect SIRLOIN_DATABASE_URL
Sirloin->>Brain: gRPC dial SIRLOIN_BRAIN_HOST
Dev->>Sirloin: grpcurl :50051 list
Sirloin-->>Dev: services

Gotchas

  • Postgres locally vs Neon: default dev uses the rump compose Postgres. Schemas match Neon prod, but extensions / pg version drift is possible. For prod-like database behavior, point SIRLOIN_DATABASE_URL at a direct Neon URL and leave SIRLOIN_DATABASE_POOLED_URL empty unless explicitly testing pooled runtime behavior.
  • Primer test mode: environment is auto-detected from SIRLOIN_PRIMER_API_KEY prefix (sandbox vs production). Don’t hand-pick a flag — re-issue the right key (config.go).
  • Chargebee sandbox: make run-tests-all hits the sandbox and is rate-limited. Run tests sequentially (-p 1, already configured). Local make run-tests does not call Chargebee; mocks may hide bugs that appear only against the live sandbox.
  • Mock locations: mocks live in the same directory as the interface, not under a mocks/ tree (.mockery.yml). Don’t re-add a /mocks dir.
  • Email templates: imported from shank (internal/pkg/emails/templates). Editing emails goes through shank, not sirloin.
  • make modernize: rewrites code via golang.org/x/tools. Run before pushing if you’ve used older idioms.
  • Webhook auth: leaving SIRLOIN_CHARGEBEE_WEBHOOK_* or SIRLOIN_PRIMER_WEBHOOK_SECRET empty is fine for local — webhooks are rejected with 401, no panic. See sirloin-errors.
  • Free checkouts (0 amount): still go through Chargebee recording. Don’t short-circuit the path locally.
  • Worker leader election: locks are in Redis. With multiple sirloin replicas in compose, only one runs each periodic worker. Single-replica dev avoids surprises.
  • Race detector required: CI runs all tests with -race. Run locally the same way (make run-tests already does).

Resolved

  • Hot-reload tool: air, configured in apps/sirloin/.air.linux.conf and invoked via make run-air (apps/sirloin/Makefile:44).
  • Round: compose services reach round at round:8080. The host-published localhost:8821 endpoint is the round gRPC UI helper, not raw round gRPC.
  • Clerk for local: only SIRLOIN_CLERK_API_KEY is required — sirloin installs it via clerk.SetKey(cfg.ClerkAPIKey) (apps/sirloin/cmd/app/main.go:114, apps/sirloin/internal/app/config/config.go:305-:308). No separate Clerk user-pool id is read by sirloin; the JWKS used for verification is derived from the secret. TODO(@zen): designate the canonical Clerk dev key/instance for local use.
  • make run-tests-all strictly requires only SIRLOIN_CHARGEBEE_* (-tags=sandbox,integration -p 1 gate, apps/sirloin/Makefile:18-:19). Primer sandbox tests skip themselves when SIRLOIN_PRIMER_API_KEY is unset (apps/sirloin/internal/app/services/billing/primersandbox_test.go:21-:24), so Primer creds are optional but recommended for full coverage.