Skip to content

Local dev setup

This guide gets you from a clean laptop to a running local paperboard cluster. We use kind for the local Kubernetes cluster and Helm for service deployment. Estimated time: 30–45 minutes on a fast machine.

You need the following tools installed before starting. The versions listed are the minimum tested; newer versions generally work.

ToolVersionInstall
Go1.22+brew install go
Docker Desktop4.x+docker.com/products/docker-desktop
kind0.22+brew install kind
kubectl1.29+brew install kubectl
Helm3.14+brew install helm
gh CLI2.40+brew install gh
golangci-lint1.57+brew install golangci-lint
pre-commit3.x+brew install pre-commit
yq4.x+brew install yq

You also need:

  • A GitHub account with access to the paper-board org (request from your team lead).
  • An ANTHROPIC_API_KEY for the agents service LLM calls.

The sequence below brings up a local cluster with the shipped services (agents + identity + runtime + compute) running against a local Postgres.

sequenceDiagram
autonumber
participant Dev as Developer
participant Kind as kind cluster
participant PG as Postgres
participant Svc as Services
Dev->>Kind: kind create cluster --name paperboard
Dev->>Kind: kubectl apply -f infra/local/namespace.yaml
Dev->>PG: helm install postgres bitnami/postgresql
PG-->>Dev: Postgres ready on :5432 (direct) / :6432 (PgBouncer)
Dev->>Svc: helm install agents-migrator (pre-install Job)
Svc->>PG: run agents schema migrations
Dev->>Svc: helm install identity-migrator
Svc->>PG: run identity schema migrations
Dev->>Svc: helm install agents-server + identity-server
Svc-->>Dev: services healthy
Dev->>Dev: run smoke: curl localhost:8080/healthz

Authenticate with gh first:

Terminal window
gh auth login

Clone the repos you need. For a first-time setup, you need at minimum infra, agents, and identity:

Terminal window
gh repo clone paper-board/infra
gh repo clone paper-board/agents
gh repo clone paper-board/identity
gh repo clone paper-board/sdk
gh repo clone paper-board/proto

Set GOPATH-style access with GOPRIVATE so Go pulls private modules without prompting:

Terminal window
export GOPRIVATE=github.com/paper-board

Add this to your shell profile so it persists.

Terminal window
kind create cluster --name paperboard --config infra/local/kind-config.yaml

The kind config in infra/local/kind-config.yaml sets up a single-node cluster with port forwarding for the services we need locally.

We use Bitnami’s Postgres Helm chart for local development. The cluster runs two connection endpoints: port 5432 (direct, for migrations) and port 6432 (PgBouncer in transaction-pool mode, for service runtime). Never mix them — the migrator requires a session-mode connection for advisory locks, and the app runtime requires PgBouncer for connection multiplexing.

Terminal window
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install postgres bitnami/postgresql \
--namespace paper-board \
--create-namespace \
-f infra/local/postgres-values.yaml

Wait for Postgres to be ready:

Terminal window
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=postgresql \
-n paper-board --timeout=120s

Each service ships a migrator binary that runs as a Helm pre-install / pre-upgrade Job. In local dev you run them explicitly:

Terminal window
MIGRATION_DB_URL="postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"
helm install agents-migrator infra/helm/agents \
--namespace paper-board \
--set migrator.only=true \
--set migrator.env.MIGRATION_DB_URL="${MIGRATION_DB_URL}"
helm install identity-migrator infra/helm/identity \
--namespace paper-board \
--set migrator.only=true \
--set migrator.env.MIGRATION_DB_URL="${MIGRATION_DB_URL}"

The migrator creates the agents and identity Postgres schemas and applies all migrations. It uses an advisory lock (agents=3, identity=1) to prevent concurrent runs.

Terminal window
helm install agents-server infra/helm/agents \
--namespace paper-board \
-f infra/local/agents-values.yaml \
--set env.ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY}"
helm install identity-server infra/helm/identity \
--namespace paper-board \
-f infra/local/identity-values.yaml
Terminal window
kubectl get pods -n paper-board
curl http://localhost:8080/healthz

Expected output: {"status":"ok"}.

Each service reads config exclusively from environment variables (no config files in production). The critical split:

VariablePortPurpose
MIGRATION_DB_URL5432Direct Postgres — used only by the migrator binary
DATABASE_URL6432PgBouncer transaction pool — used by the service runtime
JWT_SIGNING_KEYHMAC key for JWT signing (identity service)
ANTHROPIC_API_KEYAnthropic API key (agents service, BYO model)

For the full env var list per service, see the service’s operations.md page.

Install the hooks once after cloning any paper-board service repo:

Terminal window
pre-commit install

The hooks run gofmt, golangci-lint, commitlint, and markdownlint on every commit. They will reject commits that fail lint. Never use --no-verify to bypass them.

If your local cluster gets into a bad state, the fastest reset is:

Terminal window
kind delete cluster --name paperboard

Then repeat Steps 2–6. Because we are pre-launch (Phase 1–4), there is no production data to preserve — breaking schema changes are fine and make drop && make migrate up is the canonical reset answer.

  • First PR — open your first pull request against a paper-board service repo.
  • Service map — port reference and schema ownership per service.