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(currently1.26, see workflow env). make,git,docker+docker compose.golangci-lint v2.11.4(CI version;make lintwill use it).mockery v3+(https://github.com/vektra/mockery) — used bymake generate-mocks.protoctoolchain only if regenerating proto stubs — see /architecture/services and the rootmake generate-proto.grpcurland/orgrpc_health_probefor poking the local server.
Clone and configure env
git clone git@github.com:dreamworld-research/beef.gitcd beefcp .env.example .envFill 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=developmentSIRLOIN_DATABASE_URL=postgres://sirloin:sirloin@localhost:8800/rump?sslmode=disableSIRLOIN_DATABASE_POOLED_URL=empty for localrump; optional for Neon runtime tests.- Inside compose:
SIRLOIN_BRAIN_HOST=brain:3000,SIRLOIN_ROUND_HOST=round:8080, andSIRLOIN_BRAIN_API_KEY=1. - On the host:
SIRLOIN_BRAIN_HOST=localhost:9970andSIRLOIN_BRAIN_API_KEY=1. For round, run it separately or add a temporary raw gRPC port mapping, then setSIRLOIN_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:
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-dmake 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
make dev-build # build all imagesmake dev-up-d # start full stackdocker compose logs -f sirloinCompose 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
cd apps/sirloinmake run-dev # SIRLOIN_STAGE=development go run cmd/app/main.go# or:make build && ./bin/sirloinBring up only the dependencies in compose:
docker compose up -d rump redis brain roundHot 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
cd apps/sirloinmake run-tests # unit tests with -racemake run-tests-all # unit + sandbox + integration (sequential, real Chargebee)make run-tests-coveragemake modernize # rewrite via golang.org/x/toolsmake ci-all # full CI gate locallymake run-tests-all requires SIRLOIN_CHARGEBEE_* env vars (real sandbox)
and uses -p 1 to avoid Chargebee rate limits. See
testing-strategy.
Single test:
go test -v -run TestPaymentProcessor_Recording ./internal/app/services/billing/payments/Mock and proto regeneration
make generate-mocks # mockery v3, config in .mockery.yml; mocks live next to ifacesmake generate-docs # Swagger docsProto regeneration is repo-level:
make generate-proto # at repo root; outputs to apps/sirloin/internal/pkg/pb/Common dev commands
# gRPC list services (sirloin must be running)grpcurl -plaintext localhost:50051 list
# call BillingService.ListProductsgrpcurl -plaintext -d '{}' localhost:50051 sirloin.v5.BillingService/ListProducts
# healthgrpc_health_probe -addr=localhost:50051
# DB shell (compose Postgres)docker compose exec rump psql -U sirloin -d rump
# CI mirror locallycd apps/sirloin && make ci-allLocal 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: servicesGotchas
- Postgres locally vs Neon: default dev uses the
rumpcompose Postgres. Schemas match Neon prod, but extensions / pg version drift is possible. For prod-like database behavior, pointSIRLOIN_DATABASE_URLat a direct Neon URL and leaveSIRLOIN_DATABASE_POOLED_URLempty unless explicitly testing pooled runtime behavior. - Primer test mode: environment is auto-detected from
SIRLOIN_PRIMER_API_KEYprefix (sandbox vs production). Don’t hand-pick a flag — re-issue the right key (config.go). - Chargebee sandbox:
make run-tests-allhits the sandbox and is rate-limited. Run tests sequentially (-p 1, already configured). Localmake run-testsdoes 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/mocksdir. - Email templates: imported from shank
(
internal/pkg/emails/templates). Editing emails goes through shank, not sirloin. make modernize: rewrites code viagolang.org/x/tools. Run before pushing if you’ve used older idioms.- Webhook auth: leaving
SIRLOIN_CHARGEBEE_WEBHOOK_*orSIRLOIN_PRIMER_WEBHOOK_SECRETempty 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-testsalready does).
Resolved
- Hot-reload tool:
air, configured inapps/sirloin/.air.linux.confand invoked viamake run-air(apps/sirloin/Makefile:44). - Round: compose services reach round at
round:8080. The host-publishedlocalhost:8821endpoint is the round gRPC UI helper, not raw round gRPC. - Clerk for local: only
SIRLOIN_CLERK_API_KEYis required — sirloin installs it viaclerk.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-allstrictly requires onlySIRLOIN_CHARGEBEE_*(-tags=sandbox,integration -p 1gate,apps/sirloin/Makefile:18-:19). Primer sandbox tests skip themselves whenSIRLOIN_PRIMER_API_KEYis unset (apps/sirloin/internal/app/services/billing/primersandbox_test.go:21-:24), so Primer creds are optional but recommended for full coverage.