Skip to content

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:

bash ftl-backend/scripts/gen-dev-keys.sh >> .env

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.

# First run — builds backend Docker images then starts all services
./ftl.sh start

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:

cd ftl-backend
make migrate
# Expands to: migrate -path migrations -database "$DATABASE_URL" up

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:

docker compose -f ftl-backend/deployments/docker-compose.yml up postgres redis

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

cd ftl-frontend
pnpm install
pnpm dev
# Vite starts at http://localhost:5173

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.