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

# Agent setup

> Point a coding agent (Claude Code, Cursor, Windsurf) at this page and it will walk a human through an on-prem Definite install end to end.

This page is a runbook designed to be read by an AI agent (Claude Code, Cursor, Windsurf, or similar). The agent reads this page, asks the human user the intake questions below in order, and then executes the install against their cloud account.

If you are a human reading this directly, you probably want the cloud-specific guide: [AWS](/on-prem/aws), [GCP](/on-prem/gcp), or [Azure](/on-prem/azure). Come back here when you'd rather have an agent drive.

## Quick-start: paste this into your agent

Open a fresh session in Claude Code (or Cursor, Windsurf, etc.) in an empty working directory, and paste:

```
Help me set up Definite. Look at https://docs.definite.app/on-prem/agent-setup.md
```

That's it. The agent will fetch this page, ask the questions in the [Intake](#phase-1-intake) section, then drive Phases 2-5 against your cloud account.

<Note>
  **For agents:** every page on this site is also published as plain Markdown by appending `.md` to the URL. Use the `.md` URL when you fetch a doc because it is smaller, has no JSX components, and is easier to parse. So `/on-prem/aws` is at `https://docs.definite.app/on-prem/aws.md`, `/on-prem/gcp` at `https://docs.definite.app/on-prem/gcp.md`, and so on.
</Note>

<Note>
  If your agent supports MCP, also add the Definite docs as a source so it can pull in the per-cloud guides on demand: `claude mcp add --transport http definite-docs https://docs.definite.app/mcp`.
</Note>

***

## For the agent: how to use this page

You are an AI coding agent helping a human install on-prem Definite into their own cloud account. Read this entire page before asking your first question. Follow these rules:

1. **Ask one question at a time.** Do not batch the intake questions. Wait for the human's answer before moving on.
2. **Run real commands against the human's cloud account.** Do not just print them. Use your shell tool. Confirm before any destructive operation (delete, drop, force-destroy).
3. **Never invent values.** If the human hasn't given you a region, project ID, hostname, password, or credential, ask. Placeholders like `<your-region>` must be replaced before any command runs.
4. **Capture every value into a single `config.yaml`** as you go. Don't ask the human to write the file; you write it, show it to them, and confirm before `definite init`.
5. **Run `definite doctor` before `definite init`.** Do not pass `--skip-preflight` unless `doctor` failed only on the Postgres reachability check from a laptop outside the VPC (private RDS / Cloud SQL / Postgres Flexible Server) and the per-cloud guide says that is expected. Never use `--skip-preflight` to mask Kubernetes, object-store, license, or LLM failures.
6. **Hand off to the per-cloud guide for the long shell sequences.** Phase 2 below tells you which page to fetch.
7. **Stop and escalate to a human at Definite** (`hello@definite.app`) if: the cloud lacks a permission you need, an LLM provider rejects the model, `definite doctor` fails for a reason you can't fix in three tries, the broker rejects cloud-attested setup-token acquisition, or the human asks for production support beyond install.

<Warning>
  Agent note: Do not stop the install to ask the user to configure customer DNS unless they explicitly selected customer-owned DNS mode. In the default flow, use Definite-brokered DNS. After provisioning the load balancer, call the Definite setup broker, use the returned `*.onprem.definite.app` URL, wait for DNS/TLS readiness, and continue the install. Customer-owned DNS can be configured after the system is healthy.
</Warning>

<Note>
  Agent note: do not ask the user for a Definite setup token in the default flow. If `DEFINITE_ONPREM_SETUP_TOKEN` is not already present, the released `definite` CLI proves control of the user's cloud account to Definite and obtains a short-lived setup token automatically. On AWS this uses a presigned STS `GetCallerIdentity` request. On GCP this uses a `gcloud` identity token. The raw token is not printed and expires quickly.
</Note>

***

## Phase 0: Confirm the agent's environment

Before asking the human anything, verify your own tooling. Run, in parallel:

```bash theme={null}
which kubectl helm
kubectl version --client 2>/dev/null
helm version --short 2>/dev/null
```

You need `kubectl` 1.28+ and `helm` 3.12+. If either is missing, install them before continuing. The cloud CLI (`aws`, `gcloud`, or `az`) is checked in Phase 2 once you know which cloud.

***

## Phase 1: Intake

Ask the human these questions in order. Capture each answer into a working scratchpad you can refer back to in later phases.

### Q1. Which cloud?

> "Which cloud are you installing on: **AWS**, **GCP**, or **Azure**?"

Record as `CLOUD`. This drives every subsequent decision.

| If `CLOUD =` | Per-cloud guide to fetch in Phase 2 | Cluster         | Postgres                    | Object store | Default LLM  |
| ------------ | ----------------------------------- | --------------- | --------------------------- | ------------ | ------------ |
| AWS          | [/on-prem/aws](/on-prem/aws)        | EKS             | RDS Postgres 15             | S3           | Bedrock      |
| GCP          | [/on-prem/gcp](/on-prem/gcp)        | GKE (Autopilot) | Cloud SQL Postgres 15       | GCS (HMAC)   | Vertex AI    |
| Azure        | [/on-prem/azure](/on-prem/azure)    | AKS             | Postgres Flexible Server 15 | Blob Storage | Azure OpenAI |

<Warning>
  Azure is currently a **preliminary guide**. If the human picks Azure, tell them this is not yet validated end to end, and recommend they email `hello@definite.app` to be a design partner before proceeding.
</Warning>

### Q2. Environment

> "Is this a **demo / pilot** install (you'll throw it away in a few weeks), or a **production** install (real users, real data)?"

Record as `ENV ∈ {demo, prod}`. This changes a handful of defaults:

| Setting             | Demo                                                   | Production                                                                                                                              |
| ------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| HA databases        | single-AZ                                              | multi-AZ / regional                                                                                                                     |
| Deletion protection | off                                                    | on                                                                                                                                      |
| Hostname            | Definite-brokered `https://<slug>.onprem.definite.app` | Definite-brokered `https://<slug>.onprem.definite.app` by default; customer-owned DNS is an advanced option after the system is healthy |
| TLS                 | Definite-brokered TLS                                  | Definite-brokered TLS by default; private/internal production may need customer certs or a private CA                                   |
| Backups             | none                                                   | daily snapshots                                                                                                                         |
| Lakehouse PVC size  | 50Gi                                                   | size to expected data + 2x                                                                                                              |

Do not ask for DNS before first install unless the human explicitly chooses customer-owned DNS mode. The default first-run URL comes from Definite.

### Q3. DNS mode

> "Should I use the default Definite-brokered DNS for the first install? Definite will provide a URL like `https://<slug>.onprem.definite.app`. Choose customer-owned DNS only if you already need a private/internal hostname or custom certificate."

Record as `DNS_MODE ∈ {definite-brokered, customer-owned}`. Default to `definite-brokered`.

If `DNS_MODE = definite-brokered`, do not ask for a hostname yet. After the load balancer exists, call the Definite setup broker through the `definite` CLI, record the returned URL as `PUBLIC_URL`, and set `HOSTNAME` to the host portion, for example `<slug>.onprem.definite.app`.

If `DNS_MODE = customer-owned`, ask:

> "What customer-owned hostname should Definite be reachable at, like `definite.acme.com`?"

Record as `HOSTNAME`, then follow the per-cloud guide's customer-owned DNS notes. Use the released CLI's customer-owned mode when it is available; the expected flag shape is `--dns-mode customer-owned --hostname <HOSTNAME>`.

<Warning>
  Private/internal production deployments may require customer-owned DNS and a customer certificate or private CA. That is a custom production path, not the default first-run path.
</Warning>

### Q4. Authentication

> "How should users sign in: **local auth** (email + password, simplest), or **OIDC SSO** (Google Workspace, Okta, Entra ID, etc.)?"

Record as `AUTH ∈ {local, oidc}`.

If `AUTH = oidc`, also ask:

> "Which OIDC provider? I'll need the issuer URL, client ID, and client secret."

If they want Google Workspace SSO specifically, point them at [/on-prem/sso](/on-prem/sso) for the OAuth client setup, and continue.

### Q5. LLM provider

> "Which LLM provider should Fi use?"

| `CLOUD` | Default                                        | Alternatives                            |
| ------- | ---------------------------------------------- | --------------------------------------- |
| AWS     | Bedrock (IRSA, no static creds)                | Anthropic direct, Azure OpenAI, Vertex  |
| GCP     | Vertex AI (Workload Identity, no static creds) | Anthropic direct, Bedrock, Azure OpenAI |
| Azure   | Azure OpenAI                                   | Anthropic direct, Bedrock, Vertex       |

Record as `LLM_PROVIDER`. For Bedrock or Vertex, confirm the human has model access enabled in their account for the Claude model they want.

### Q6. Region

> "Which region? Pick whatever is closest to your users / source data. If you're not sure, \[recommend their cloud's most-popular region]."

Record as `REGION`. Cross-check that the chosen LLM model is available in this region (Bedrock and Vertex availability varies).

### Q7. Existing infrastructure to reuse

> "Are you starting fresh, or do you have existing infrastructure you want me to reuse: an existing Kubernetes cluster, an existing Postgres database, or an existing object-store bucket?"

Most installs are greenfield. If the human says they want to reuse something, capture the connection details and note them for Phase 2: you'll skip provisioning that resource but still need it in `config.yaml`.

### Q8. Naming

> "What name prefix should I use for the resources I create? Default is `definite`."

Record as `NAME_PREFIX`. This becomes part of cluster name, bucket name, DB name, etc.

### Q9. Optional customer-owned license

Do not ask for a manual license key in the default Definite-brokered flow. `definite init` requests a broker-issued license during setup and injects it for the install.

Only ask this question if the human explicitly selected customer-owned/manual mode:

> "On-prem Definite needs a license key, issued to you by Definite. Do you have it? It looks like `onprem_` followed by a long hex string."

Record as `LICENSE_KEY` only for that manual path. If the human does not have a key for manual mode, stop and have them email `hello@definite.app`.

***

## Phase 2: Provision cloud infrastructure

You now have enough information to provision. Fetch the per-cloud guide for `CLOUD` and execute Phase 1 ("Provision …") of that guide, substituting the answers from Intake.

| `CLOUD` | Fetch (use the `.md` URL) and follow                                                                                       |
| ------- | -------------------------------------------------------------------------------------------------------------------------- |
| AWS     | [`/on-prem/aws.md`](https://docs.definite.app/on-prem/aws.md) - Phase 1 (first supported black-box path, Terraform module) |
| GCP     | [`/on-prem/gcp.md`](https://docs.definite.app/on-prem/gcp.md) - Phase 1 (`gcloud` sequence)                                |
| Azure   | [`/on-prem/azure.md`](https://docs.definite.app/on-prem/azure.md) - Phase 1 (`az` sequence; preliminary)                   |

A few rules that apply across all three:

* **Confirm the cloud CLI is authenticated** before running anything. `aws sts get-caller-identity` / `gcloud config list account` / `az account show`. If the human isn't logged in, ask them to authenticate; do not try to authenticate as them.
* **Capture every output you'll need for `config.yaml`** as you go (cluster name, DB connection string, bucket name, access keys, service-account emails). Don't make the human re-run commands to find them.
* **Get explicit confirmation** before any command that costs money (cluster create, RDS create, Cloud SQL create) or that writes IAM bindings.
* **Do not create customer DNS in the default flow.** For `DNS_MODE = definite-brokered`, continue until the load balancer exists, then use the `definite` CLI to call the setup broker. The backend contract is `POST /onprem/v1/setup-sessions`, but the agent should not ask the human to run a direct `curl` by default.
* **Provisioning takes 20-40 minutes**, mostly the managed Kubernetes cluster. Tell the human that up front. Run independent commands in parallel where possible.

When Phase 1 of the per-cloud guide is done, return here for Phase 3.

***

## Phase 3: Assemble `config.yaml`

Open a new file `config.yaml` in the human's working directory and fill it in from your scratchpad. The shape is the same across clouds; only `object_store`, `lakehouse.storage.storage_class_name`, and `llm` change.

A canonical template (replace every `<...>` with a real value from Intake or Phase 2):

```yaml theme={null}
deployment:
  name: <NAME_PREFIX>
  namespace: <NAME_PREFIX>
  dns_mode: definite-brokered          # default first-run mode

broker:
  # Optional. Omit in the default path: definite init will acquire a short-lived
  # setup token by proving control of the active AWS/GCP credentials.
  # setupToken:
  #   env: DEFINITE_ONPREM_SETUP_TOKEN

# Omit the license block in the default brokered flow. definite init injects
# the broker-issued license for this install. Add license.key only for
# customer-owned/manual mode.

postgres:
  url: postgres://<user>:${POSTGRES_PASSWORD}@<host>:5432/<db>

object_store:
  # type: s3 | gcs | azure
  # See the per-cloud guide for the exact credentials shape.

lakehouse:
  prefix: lake/
  storage:
    size: 50Gi
    storage_class_name: <gp3 | premium-rwo | managed-csi-premium>

auth:
  mode: <local | oidc>
  initial_admin_email: <admin@your-domain>
  # If oidc: also set issuer, client_id, client_secret env ref.

llm:
  provider: <bedrock | vertex | anthropic | azure_openai>
  # Provider-specific fields per the per-cloud guide.

resources:
  api:        { replicas: 2, cpu: "1",  memory: 2Gi }
  lakehouse:  { replicas: 1, cpu: "4",  memory: 16Gi }
  frontend:   { replicas: 2, cpu: 500m, memory: 512Mi }
  job_runner: { replicas: 1, cpu: 500m, memory: 1Gi }
```

Then **show the filled-in `config.yaml` to the human and ask them to confirm before you proceed**. Diff it against their answers. Flag anything you had to guess.

Export the secrets the file references:

```bash theme={null}
export POSTGRES_PASSWORD="..."
# Object-store credentials (S3 access key, GCS HMAC pair, or Azure storage key)
# OIDC_CLIENT_SECRET if auth.mode: oidc
```

Pull these values from Phase 2's outputs. Never echo secrets back into the chat transcript; reference them by env var name only. In the default brokered flow there is no `LICENSE_KEY` env var and no `DEFINITE_ONPREM_SETUP_TOKEN` env var; the CLI acquires both broker authorization and license during `definite init`.

<Warning>
  Do not commit `config.yaml` to a public repo while the secret env vars are exported in your shell. For prod installs, recommend sourcing secrets from a secret manager (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, 1Password).
</Warning>

***

## Phase 4: Install Definite

The CLI install is identical across clouds. Run, in order:

```bash theme={null}
# 1. Install the CLI
curl -fsSL https://storage.googleapis.com/definite-public/definite-onprem/install.sh | sh
definite version

# 2. Bootstrap cluster-level prereqs (ingress, cert-manager, agent-sandbox CRDs)
definite bootstrap

# 3. Preflight
definite doctor --config config.yaml

# 4. Deploy. In the default mode, init discovers the ingress load balancer,
# calls the Definite setup broker, waits for brokered DNS readiness, injects
# the returned hostname/license into this install, and prints the final URL.
# If no setup token is configured, the CLI acquires one automatically from
# the active AWS/GCP credentials.
definite init --config config.yaml --dns-mode definite-brokered
```

If `DNS_MODE = customer-owned`, use `definite init --config config.yaml --dns-mode customer-owned --hostname <HOSTNAME>` instead. Do this only when the human explicitly selected customer-owned DNS; otherwise use Definite-brokered DNS.

If `definite doctor` fails, **fix the failure before running `definite init`**. The most common failures and fixes are in the Troubleshooting section of each per-cloud guide.

The one documented exception is the Postgres reachability check against a private DB endpoint from a laptop (RDS, Cloud SQL with private IP, Postgres Flexible Server). The in-cluster API pods may reach the DB fine while the laptop has no route. If `doctor` fails only on Postgres for this reason, follow the per-cloud guide's private-network workaround. Do not use `--skip-preflight` to mask any other failure.

<Warning>
  If `definite init` prints an `UNLICENSED` warning (or the `doctor` `license` check warns), `config.yaml` has no `license` block or the key is wrong. The deployment still installs, but every product API route returns HTTP 403 until you add a valid `license` block and re-run. Do not hand a deployment to the human in this state.
</Warning>

### Bootstrap the initial admin

`definite init` does not auto-create an admin. After the pods are `Ready`:

```bash theme={null}
ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24)
kubectl set env deploy/definite-api -n <NAME_PREFIX> \
  INITIAL_ADMIN_EMAIL=<human's email> \
  INITIAL_ADMIN_PASSWORD="$ADMIN_PASSWORD"
kubectl rollout status deploy/definite-api -n <NAME_PREFIX>
```

**Print the admin password back to the human and tell them to save it in a password manager.** It is not recoverable.

As of the v0.0.11 install path, this API rollout is expected to complete without a manual lakehouse restart. Do not restart the lakehouse unless logs show an unrelated lakehouse-specific failure.

### AWS Bedrock follow-up

If `CLOUD = AWS` and `LLM_PROVIDER = bedrock`, confirm the `definite` ServiceAccount has the Bedrock IRSA role ARN. If it is not already present, annotate it after install:

```bash theme={null}
BEDROCK_ROLE_ARN=$(terraform output -raw bedrock_irsa_role_arn)
kubectl annotate serviceaccount definite -n <NAME_PREFIX> \
  eks.amazonaws.com/role-arn="$BEDROCK_ROLE_ARN" --overwrite
kubectl rollout restart deploy/definite-api -n <NAME_PREFIX>
kubectl rollout status  deploy/definite-api -n <NAME_PREFIX>
```

Without this annotation Fi can't call Bedrock and every Fi conversation fails. See [AWS guide → Bedrock IRSA](/on-prem/aws#bedrock-irsa).

***

## Phase 5: Verify

1. `definite status --config config.yaml` — all pods `Running`, ingress has an address.
2. Open `https://<HOSTNAME>` in a browser. In the default flow this is the Definite-brokered `https://<slug>.onprem.definite.app` URL; wait until the broker reports DNS/TLS ready before debugging the app.
3. Log in with the admin email + password from Phase 4.
4. Confirm core auth routes return 200 after login (`/login`, `/auth/me`, and the main app shell).
5. Run a simple query smoke, for example `SELECT 1 AS ok`.
6. If Fi is configured, ask Fi a question against a test connection.

If any of these fail, capture logs (`definite logs api --tail 200`) before debugging.

***

## Hand-off to the human

When Phase 5 is green, summarize for the human:

* The URL they sign in at
* The admin email + a reminder where the password is saved
* The path to `config.yaml` (they'll need it for `definite upgrade`)
* The per-cloud guide's Day-2 section (`/on-prem/<cloud>#day-2-operations`)
* Next steps: [Connect a data source](/connect-your-database), [set up the MCP server](/mcp/mcp), [add SSO](/on-prem/sso) if they started with local auth
* For teardown later: the per-cloud guide's Phase 3. On AWS in particular, the ingress-nginx NLB must be deleted before `terraform destroy` or the destroy hangs on subnet `DependencyViolation` (tracking: [definite-onprem#405](https://github.com/definite-app/definite-onprem/issues/405)).

***

## When to escalate

Stop and tell the human to email `hello@definite.app` if:

* The cloud account is missing a permission you can't grant yourself, and the human can't grant it either.
* The LLM provider rejects model access and the human's admin doesn't respond within an hour.
* `definite doctor` fails three times for distinct reasons.
* `definite init` succeeds but pods crash-loop with errors that aren't in the per-cloud Troubleshooting table.
* The human asks for anything beyond install (capacity planning, custom registry mirrors, air-gapped install, FIPS compliance, custom sandbox network policies).

Email [hello@definite.app](mailto:hello@definite.app) with reproducible bugs.
