Local Setup¶
Prerequisites¶
| Tool | Version | Purpose |
|---|---|---|
| Go | 1.22+ | Build and run the three backend binaries |
| Node.js | 20+ | Required by pnpm and Vite |
| pnpm | 8+ | Frontend package manager (npm install -g pnpm) |
| Docker | 24+ | Runs Postgres and Redis locally |
| psql | Any | Optional — useful for manual DB inspection |
| golang-migrate | v4 | Runs ftl-backend/migrations/ (go install github.com/golang-migrate/migrate/v4/cmd/migrate@latest) |
1. Clone the repos¶
The four repos are independent. Clone them into the same parent directory.
git clone git@github.com:JetaFutures/ftl-backend.git
git clone git@github.com:JetaFutures/ftl-frontend.git
git clone git@github.com:JetaFutures/ftl-docs.git
git clone git@github.com:JetaFutures/ftl-support.git
Put ftl.sh in the parent directory (workspace root). It expects ftl-backend/ and ftl-frontend/ as siblings.
2. Create the workspace .env file¶
The workspace root .env is read by ftl.sh and passed to Docker containers via --env-file. It must not be committed.
Generate fresh RS256 key material:
Then add the remaining variables:
# Minimum required for local dev
GOOGLE_CLIENT_ID=<your-google-client-id>
SPORTMONKS_API_KEY=<your-sportmonks-key>
SPORTMONKS_BASE_URL=https://api.sportmonks.com
SPORTMONKS_SEASON_ID=25536
REG_TICKET_SECRET=<any-random-hex-32-bytes>
DATABASE_URL=postgres://ftl_admin:localdev@localhost:5432/ftl2026?sslmode=disable
REDIS_URL=redis://localhost:6379
The JWT_PRIVATE_KEY_B64 and JWT_PUBLIC_KEY_B64 variables come from gen-dev-keys.sh. The legacy JWT_SECRET is retired — do not use it.
Warning
Never commit .env. The ftl-backend/.gitignore covers *.env and .env, but verify the workspace root .gitignore also excludes .env before your first push.
3. Start the full stack with ftl.sh¶
ftl.sh is the recommended way to start everything locally. It manages Docker containers for Postgres and Redis, builds and starts the Go service containers, and starts the Vite dev server.
After a successful start:
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| API server | http://localhost:8080 |
| WebSocket server | ws://localhost:8081 |
| PostgreSQL | localhost:5432, db ftl2026, user ftl_admin, password localdev |
| Redis | localhost:6379 |
Other ftl.sh commands:
./ftl.sh stop # Stop all containers and the frontend dev server
./ftl.sh restart # stop then start
./ftl.sh rebuild # Rebuild backend Docker images then restart
./ftl.sh status # Show container states and ports
./ftl.sh logs # Tail api-server + ws-server logs
4. Run migrations¶
Migrations are not run automatically by ftl.sh. Run them once against the local database:
This applies all files under ftl-backend/migrations/ in sequence.
5. Alternative: run services outside Docker¶
If you want to iterate on Go code without rebuilding images, start only the infra containers and run the binaries with go run.
Start Postgres and Redis only:
In separate terminals:
# Terminal 1 — API server (port 8080)
cd ftl-backend
make run-api
# Equivalent: go run ./cmd/api-server
# Terminal 2 — WebSocket server (port 8081)
cd ftl-backend
make run-ws
# Equivalent: go run ./cmd/ws-server
# Terminal 3 — Flusher (no port)
cd ftl-backend
make run-flusher
# Equivalent: go run ./cmd/flusher
Each binary reads DATABASE_URL and REDIS_URL from environment. Export them first or prefix each command:
export DATABASE_URL=postgres://ftl_admin:localdev@localhost:5432/ftl2026?sslmode=disable
export REDIS_URL=redis://localhost:6379
6. Run the frontend¶
The Vite dev server proxies /api to http://localhost:8080 and /ws to ws://localhost:8081 (configured in vite.config.ts). No .env.local is needed for local API connectivity. The only frontend env var required locally is VITE_GOOGLE_CLIENT_ID in .env for Google OAuth.
7. Useful dev flags¶
Set these as environment variables before starting api-server:
| Variable | Effect |
|---|---|
FLUSHER_EMBEDDED=1 |
Runs the flusher goroutine inside api-server. Skip the separate flusher binary during development. |
DISABLE_RATE_LIMIT=1 |
Disables rate limiting. Used by ftl.sh start and load tests. Never set this in production. |
BOT_ENABLED=1 |
Enables the bot trading goroutine inside api-server. |
BOT_WALLET=500 |
Starting virtual wallet balance for bot accounts (default used by ftl.sh). |
ftl.sh start sets FLUSHER_EMBEDDED=1, BOT_ENABLED=1, BOT_WALLET=500, and DISABLE_RATE_LIMIT=1 automatically.
8. Dev auth shortcut¶
The api-server exposes POST /api/auth/dev when ENV is not production. This endpoint accepts a JSON body {"displayName": "Test Coach", "districtId": "EKM"} and returns a JWT without requiring a real Google ID token. Use it from curl or Postman during backend development:
curl -X POST http://localhost:8080/api/auth/dev \
-H 'Content-Type: application/json' \
-d '{"displayName": "dev-user", "districtId": "EKM"}'
Warning
The /api/auth/dev endpoint is disabled in production. The ENV=production check is enforced in ftl-backend/cmd/api-server/main.go. Do not expose it in staging either unless you understand the risk.
9. Running tests¶
# Backend unit + integration tests
cd ftl-backend
make test
# Equivalent: go test ./... -v -race -count=1
# Frontend Playwright E2E
cd ftl-frontend
pnpm exec playwright test
10. Staging cost reminder¶
If you have access to the Azure staging environment, always scale down overnight to avoid burning credit:
# Before bed — scale all apps to 0, stop Postgres
AZURE_CONFIG_DIR=~/.azure-ftl bash ftl-infra/scripts/scale-down.sh
# Morning — start Postgres, wait 60s, bring apps to min=1
AZURE_CONFIG_DIR=~/.azure-ftl bash ftl-infra/scripts/scale-up.sh
See ftl-docs/reports/azure-cost-audit-2026-05-02.md for the full cost incident report and why this matters.