> ## Documentation Index
> Fetch the complete documentation index at: https://engineering.unkey.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Local development

> Set up, run, and test Unkey locally

## Prerequisites

<Warning>
  We do not support Windows as development environment. It might work, or it might not.
</Warning>

Unkey installs most tools and dependencies automatically. The only required preinstalled dependencies are:

* docker
* git

All other tools are managed via [mise](https://mise.en.dev/), which you'll install in the next step.

## Configure Depot

Unkey uses Depot as the build runner for local development.

To get your Depot token, sign in to <a href="https://depot.dev/orgs/25j3k7j1zw/settings" target="_blank">Depot</a>,
then create a token. Name it something like `<your-name>-local`.

The Depot org settings page is only accessible to Depot org owners. If you are
not an owner, Depot might redirect you away from the settings page or block
access. Ask Andreas for the appropriate token or access path.

During bootstrap, paste that token when prompted or manually add it to
`./dev/.env.depot`.

## Bootstrap

Clone the repository and install mise and other tools.

<Steps>
  <Step title="Clone the repository">
    ```bash theme={"theme":"kanagawa-wave"}
    git clone https://github.com/unkeyed/unkey
    cd unkey
    ```
  </Step>

  <Step title="Install and set up mise">
    You can set up mise manually or use the install script. It pins mise to a specific version and SHA.

    ```bash theme={"theme":"kanagawa-wave"}
    ./dev/install-mise
    ```
  </Step>

  <Step title="Bootstrap local configuration">
    Run the bootstrap task to install the pinned toolchain, create local environment files, write your Depot credentials, and configure the GitHub app.
    Use the token from [Configure Depot](#configure-depot) when prompted.

    ```bash theme={"theme":"kanagawa-wave"}
    mise run bootstrap
    ```

    If GitHub rate limits `mise install`, provide a `GH_TOKEN` when you rerun the task:

    ```bash theme={"theme":"kanagawa-wave"}
    GH_TOKEN=$(gh auth token) mise run bootstrap
    ```
  </Step>
</Steps>

If you only want to develop on the dashboard, run `mise run dashboard`.
Otherwise continue for a full dev setup.

## Run dev mode

Start the full development setup:

```bash theme={"theme":"kanagawa-wave"}
mise run dev
```

You get:

* Tilt UI at `http://localhost:10350`
* Various services port-forwarded
* Dashboard at `http://localhost:3000`

## Local HTTPS with Frontline (optional)

Set up local TLS for `*.unkey.local`:

1. Configure local DNS:

```bash theme={"theme":"kanagawa-wave"}
./dev/setup-wildcard-dns.sh
```

2. Start the minikube tunnel in another terminal:

```bash theme={"theme":"kanagawa-wave"}
mise run tunnel
```

3. Open the local domain:

```bash theme={"theme":"kanagawa-wave"}
open https://app.unkey.local
```

Tilt generates trusted TLS certificates using mkcert and Frontline terminates TLS on port 443.

## Stop the development environment

```bash theme={"theme":"kanagawa-wave"}
mise run down
```

## Environment configuration

Dashboard environment variables live in `web/apps/dashboard/.env`. The
bootstrap task creates the file from `web/apps/dashboard/.env.example`.

### Local authentication

Set local auth mode in `web/apps/dashboard/.env`:

```plaintext theme={"theme":"kanagawa-wave"}
AUTH_PROVIDER="local"
```

### Optional services

WorkOS authentication:

Add WorkOS credentials to `web/apps/dashboard/.env`:

```plaintext theme={"theme":"kanagawa-wave"}
AUTH_PROVIDER="workos"
WORKOS_CLIENT_ID=<your client ID>
WORKOS_API_KEY=<your API key>
WORKOS_COOKIE_PASSWORD=<your base64 password>
```

Stripe billing:

The dashboard subscription flow needs all four variables in
`web/apps/dashboard/.env`. If any is missing the dashboard treats Stripe as
unconfigured and billing calls fail.

The product ID lists come pre-filled in `.env.example` (the shared sandbox
catalog), so you only add two values:

* `STRIPE_SECRET_KEY` - a test-mode key (`sk_test_...`) from the shared sandbox.
* `STRIPE_WEBHOOK_SECRET` - the signing secret for forwarded webhook events.

If the stripe CLI is logged in (`stripe login`), `tilt up` handles webhook
forwarding for you: it runs `stripe listen` against both the dashboard
(`localhost:3000/api/webhooks/stripe`) and ctrl-api
(`localhost:7091/webhooks/stripe`), and writes the shared `STRIPE_WEBHOOK_SECRET`
into both `web/apps/dashboard/.env` and `dev/.env.stripe`. No manual step needed.

If the CLI is not logged in, forward events and copy the printed `whsec_...`
yourself:

```bash theme={"theme":"kanagawa-wave"}
stripe listen --forward-to http://localhost:3000/api/webhooks/stripe
```

To set up a fresh Stripe sandbox (products, meters, prices), follow the catalog
guide in the infra repo: <a href="https://github.com/unkeyed/infra/blob/main/docs/services/stripe-billing.md" target="_blank">Stripe Billing</a>.

Deploy (control plane) billing is separate from the dashboard flow above and is
configured through `dev/` env files that Tilt loads into Kubernetes secrets.
Each is optional: a missing file disables that piece and never breaks startup.

* `dev/.env.stripe` (the `stripe-credentials` secret, read by both ctrl-api and
  the worker). Copy `dev/.env.stripe.example` and set:
  * `STRIPE_SECRET_KEY` - test-mode key for the hourly usage push and the
    month-end invoice finalize.
  * `STRIPE_WEBHOOK_SECRET` - the close webhook's signing secret, written
    automatically by `tilt up` when the stripe CLI is logged in (as above).
  * `STRIPE_DEPLOY_*_LOOKUP_KEY` - the price lookup\_keys `CancelDeploy` uses to
    find a subscription's Deploy items. Same handles the dashboard uses; empty
    disables cancel.
* The spend-cap budget alert emails need `dev/.env.workos` (`WORKOS_API_KEY`, to
  resolve an org's admin recipients) and `dev/.env.resend` (`RESEND_API_KEY`, to
  send). Without them the alerts only log; the suspend/resume enforcement is
  unaffected.

See [Deploy Billing](/architecture/services/control-plane/worker/workflows/deploy-billing) and [Deploy Spend Cap](/architecture/services/control-plane/worker/workflows/deploy-spend-cap).

### Feature flags

You don't need Vercel Flags setup to run dashboard code that imports `@/lib/flags`. When `FLAGS` is missing, the dashboard uses the noop adapter and resolves each flag to its declared `defaultValue`.

If you're adding flags, testing remote targeting rules, or using Vercel Toolbar overrides, ask Andreas for the dev values of `FLAGS` and `FLAGS_SECRET`. Add them to `web/apps/dashboard/.env`. They're stable, so you set them once and forget. See [Feature flags](/contributing/tooling/feature-flags) for the rest of the workflow.

## Seed local data

```bash theme={"theme":"kanagawa-wave"}
mise run unkey -- dev seed local
```

## Test locally

Run Go tests with Rask:

```bash theme={"theme":"kanagawa-wave"}
mise run test
```

Run a single Go test:

```bash theme={"theme":"kanagawa-wave"}
mise exec -- go test -run TestCacheName ./pkg/cache
```

Run TypeScript tests with pnpm:

```bash theme={"theme":"kanagawa-wave"}
mise exec -- pnpm --dir=web test
```

## Code quality

```bash theme={"theme":"kanagawa-wave"}
mise run fmt
mise run build
```

## Troubleshooting

### Failure: resource\_exhausted: too many requests

If you receive an error message similar to the example below, authenticate your terminal with buf. You can sign up for a free account at [buf.build](https://buf.build/home).

```bash theme={"theme":"kanagawa-wave"}
Failure: resource_exhausted: too many requestssh

Please see https://buf.build/docs/bsr/rate-limits/ for details about BSR rate limiting.
svc/frontline/proto/generate.go:4: running "go": exit status 1
Failure: resource_exhausted: too many requests

Please see https://buf.build/docs/bsr/rate-limits/ for details about BSR rate limiting.
svc/vault/proto/generate.go:3: running "go": exit status 1
mise run generate: command failed
```
