Skip to main content
This guide walks you through an on-prem install of Definite on Google Cloud. You’ll provision the infrastructure with gcloud, install the platform with the definite CLI, and tear it down cleanly when you’re done. Unlike AWS, this is currently a scripted/manual provisioning path rather than a packaged Terraform module. By default, the install uses Definite-brokered DNS and TLS. Definite provides a URL like https://<slug>.onprem.definite.app during setup, so you do not need to configure Cloud DNS or any other customer DNS before the first successful install. End to end: about 30 to 40 minutes, most of which is GKE and Cloud SQL spinning up.
Unlike the AWS guide, GCP does not yet ship a Terraform module. The steps below use gcloud directly. A GCP Terraform module (matching the AWS Terraform module) is on the roadmap. If you’d prefer to wait or want early access, contact hello@definite.app.

What gets created

ResourceNotes
VPC + subnetRegional VPC with a subnet for the GKE nodes; private cluster recommended
GKE clusterKubernetes 1.30 (configurable). Autopilot works; Standard is also supported
Cloud SQL Postgres 15Private IP, reachable only from the VPC
GCS bucketLakehouse data; uniform bucket-level access, versioning recommended
GCS HMAC keyService-account-bound HMAC key the lakehouse uses to read/write the bucket (see HMAC vs Workload Identity)
Artifact Registry repo (optional)Only needed if you mirror Definite images into your project
premium-rwo StorageClassGKE shorthand for PD-SSD; the lakehouse PVC binds to this by default
Every name, region, machine type, and CIDR below is something you choose. Set them once at the top of your shell session as env vars (PROJECT_ID, REGION, etc.) and reuse them.

Prerequisites

1

GCP project access

A GCP project where you have permission to create GKE clusters, Cloud SQL instances, GCS buckets, service accounts, and IAM bindings. The gcloud CLI must be authenticated (gcloud auth login) and the project set (gcloud config set project <your-project-id>).
2

Local tooling

Install the following on the machine you’ll run gcloud and definite from:
ToolVersionCheck
gcloudrecentgcloud version
kubectl1.28+kubectl version --client
helm3.12+helm version
gke-gcloud-auth-pluginrecentgke-gcloud-auth-plugin --version
3

Enable APIs

Turn on the APIs the install touches:
gcloud services enable \
  container.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com \
  servicenetworking.googleapis.com \
  iamcredentials.googleapis.com \
  aiplatform.googleapis.com
The last one (aiplatform.googleapis.com) is only needed if Fi will use Vertex AI as its LLM provider.
4

LLM access

Decide which LLM provider Fi will use. Vertex AI is the most common choice for GKE deployments because it lives in the same project and authenticates via the cluster’s identity. Anthropic, Bedrock, and Azure OpenAI are also supported. If you go with Vertex, make sure the Claude model you want is enabled for your project (Vertex Model Garden -> Anthropic models).
5

Definite broker access

In the default brokered flow, you do not need a manual Definite setup token or license key. definite init uses gcloud auth print-identity-token to prove the active GCP identity to Definite, exchanges that proof for a short-lived setup token, then receives the brokered DNS name and license. For production, prefer running the agent as a setup service account rather than a personal user account. If your organization requires a manual/customer-owned mode, contact hello@definite.app.

Phase 1: Provision GCP infrastructure

The fastest path right now is to provision with gcloud directly. The shape mirrors what the AWS Terraform module produces. Set up shared env vars first:
export PROJECT_ID="<your-project-id>"
export REGION="<your-region>"                # e.g. us-central1
export CLUSTER_NAME="<your-cluster-name>"    # e.g. definite
export SQL_INSTANCE="<your-sql-instance>"    # e.g. definite-db
export BUCKET="<your-bucket-name>"           # e.g. <project>-definite-lake
export SA_NAME="<your-sa-name>"              # e.g. definite-lakehouse

gcloud config set project "$PROJECT_ID"

1. Create a GKE cluster

Autopilot is the simplest option (Google manages nodes):
gcloud container clusters create-auto "$CLUSTER_NAME" \
  --region "$REGION" \
  --release-channel regular
If you prefer Standard mode (more control over node pools, GPU, sole-tenant nodes), use gcloud container clusters create with a --machine-type like e2-standard-4 and at least 3 nodes. Wire up kubectl:
gcloud container clusters get-credentials "$CLUSTER_NAME" --region "$REGION"
kubectl get nodes

2. Create Cloud SQL Postgres

gcloud sql instances create "$SQL_INSTANCE" \
  --database-version=POSTGRES_15 \
  --region="$REGION" \
  --tier=db-custom-2-8192 \
  --availability-type=REGIONAL

gcloud sql databases create definite --instance="$SQL_INSTANCE"

gcloud sql users create definite \
  --instance="$SQL_INSTANCE" \
  --password="<your-postgres-password>"
Stash the connection name (used by the Cloud SQL Auth Proxy and by postgres.url):
gcloud sql instances describe "$SQL_INSTANCE" --format='value(connectionName)'
For production, give the instance a private IP in your VPC (see Private IP setup) so the cluster can reach it without going through the auth proxy. If the instance is on the same VPC as the cluster, postgres.url looks like:
postgres://definite:${POSTGRES_PASSWORD}@<private-ip>:5432/definite

3. Create a GCS bucket for the lakehouse

gcloud storage buckets create "gs://${BUCKET}" \
  --location="$REGION" \
  --uniform-bucket-level-access \
  --public-access-prevention
Versioning is recommended for production:
gcloud storage buckets update "gs://${BUCKET}" --versioning

4. Create a service account and HMAC key for the bucket

The lakehouse reads and writes GCS through DuckDB’s httpfs extension. httpfs speaks the S3 interop API, not native GCS, so it needs an HMAC key pair bound to a service account that has object-admin on the bucket. Workload Identity alone is not enough.
# Create a dedicated service account for the lakehouse.
gcloud iam service-accounts create "$SA_NAME" \
  --display-name="Definite lakehouse"

SA_EMAIL="${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"

# Grant it object-admin on the lakehouse bucket only (least privilege).
gcloud storage buckets add-iam-policy-binding "gs://${BUCKET}" \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/storage.objectAdmin"

# Create the HMAC pair. The key + secret print once; capture them now.
gcloud storage hmac create "$SA_EMAIL"
The output gives you accessId (the HMAC key ID) and secret (the HMAC secret). These are what you’ll set as GCS_HMAC_KEY_ID and GCS_HMAC_SECRET before running definite init.

5. (If using Vertex AI for Fi) Grant Vertex access

Bind the GKE workload identity for the API pod’s service account to Vertex User on the project. The simplest way is to grant roles/aiplatform.user to the node service account the cluster runs as:
NODE_SA=$(gcloud container clusters describe "$CLUSTER_NAME" \
  --region "$REGION" \
  --format='value(nodeConfig.serviceAccount)')

# `default` resolves to the Compute Engine default SA; replace if you set one.
[ "$NODE_SA" = "default" ] && \
  NODE_SA="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')-compute@developer.gserviceaccount.com"

gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member="serviceAccount:${NODE_SA}" \
  --role="roles/aiplatform.user"
For tighter scoping, set up Workload Identity on the deployment’s API service account and bind roles/aiplatform.user to that specifically.

HMAC vs Workload Identity

OptionWhen to use
HMAC key (accessId, secret)Use this today for lakehouse bucket access. The lakehouse uses DuckDB’s httpfs extension, which speaks the S3 interop API with static credentials. Workload Identity can’t be used for the bucket.
Workload IdentityUse this for Vertex AI access (and any other GCP API the deployment’s pods call). The API pod runs as a Kubernetes ServiceAccount that’s bound to a Google service account with roles/aiplatform.user. No static credentials.

Phase 2: Install Definite with the definite CLI

1. Install the CLI

curl -fsSL https://storage.googleapis.com/definite-public/definite-onprem/install.sh | sh
The install script detects your OS and architecture, downloads the matching prebuilt binary from a public Google Cloud Storage bucket, verifies its SHA256 checksum, and places definite on your PATH (default: $HOME/.local/bin). Binaries are published for macOS and Linux (arm64 and x86_64), and are uploaded by the release workflow using short-lived Workload Identity Federation credentials (no long-lived keys). To pin a version, set DEFINITE_VERSION before piping to sh:
curl -fsSL https://storage.googleapis.com/definite-public/definite-onprem/install.sh \
  | DEFINITE_VERSION=v0.0.17 sh
Verify:
definite version

2. Bootstrap cluster prerequisites

definite bootstrap installs the cluster-level pieces that definite init assumes are already present:
PrerequisiteWhat it provides
Ingress controllerHTTP/S routing for the deployment’s Ingress resource
Definite-brokered DNS/TLS readinessThe default setup path uses the Definite setup broker and the returned *.onprem.definite.app URL
agent-sandbox CRDsCustom resources the Fi runtime uses to dispatch per-run sandboxes
Run it once against the fresh cluster:
definite bootstrap
On GKE Autopilot, definite bootstrap pins cert-manager’s leader-election lease to the cert-manager namespace. The chart default of kube-system is Google-managed on Autopilot and rejects writes; without the override the webhook CA is never injected. The CLI applies this automatically.
--dry-run prints what it would install without touching the cluster. The command is idempotent; safe to re-run.

3. Let definite init request Definite-brokered DNS

The ingress controller provisions a Google Cloud Network Load Balancer. Wait for it to land, then grab its external IP:
LB_IP=$(kubectl get svc ingress-nginx-controller -n ingress-nginx \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
)
echo "$LB_IP"
You’ll get something like 34.x.y.z. For the default path, do not stop here to ask the customer to create DNS. definite init calls the Definite setup broker for you, using the ingress target above when brokered DNS supports that target type. The backend contract is POST /onprem/v1/setup-sessions, but users and agents should not run a direct curl by default.
definite init --config config.yaml --dns-mode definite-brokered
The CLI returns a URL like:
https://<slug>.onprem.definite.app
Customer-owned DNS is supported as an advanced/custom production option after the system is healthy. If the customer explicitly selected that mode, create a Cloud DNS or external A record from the customer hostname to $LB_IP and use the released CLI’s customer-owned mode. The expected flag shape is --dns-mode customer-owned --hostname <hostname>. Private/internal production deployments may also need a customer certificate or private CA. Do not use nip.io, cert-manager, or Let’s Encrypt as the default first-run path.

4. Build config.yaml

Start from the GKE example config: minimal-gke.yaml. The shape:
deployment:
  name: definite
  namespace: definite
  dns_mode: definite-brokered

broker:
  # Optional. Omit for the default GCP path: definite init uses gcloud to
  # acquire a short-lived setup token automatically.
  # setupToken:
  #   env: DEFINITE_ONPREM_SETUP_TOKEN

postgres:
  url: postgres://definite:${POSTGRES_PASSWORD}@<cloud-sql-private-ip>:5432/definite

object_store:
  type: gcs
  bucket: <your-bucket-name>
  # GCS HMAC pair from `gcloud storage hmac create`.
  credentials:
    key_id:
      env: GCS_HMAC_KEY_ID
    secret:
      env: GCS_HMAC_SECRET

lakehouse:
  prefix: lake/
  storage:
    size: 50Gi
    storage_class_name: premium-rwo  # GKE shorthand for PD-SSD

auth:
  mode: local                        # or `oidc` for SSO
  initial_admin_email: admin@<your-domain>
  initial_admin_password:
    env: INITIAL_ADMIN_PASSWORD

llm:
  provider: vertex
  project: <your-project-id>
  region: <your-region>
  # Credentials via Workload Identity on the node SA; no credentials block needed.

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
GKE Autopilot uses standard-rwo (Balanced PD) and premium-rwo (SSD PD) as its two built-in StorageClasses. The lakehouse benefits from SSD, so premium-rwo is the default. Run kubectl get storageclass to see what your cluster has.
For the full list of knobs (image registry overrides, ingress class, sandbox configuration, etc.), see the config reference in Definite’s built-in docs, available in-product once your deployment is running.

5. Export secrets

config.yaml references env vars for every secret:
export POSTGRES_PASSWORD="<your-postgres-password>"
export GCS_HMAC_KEY_ID="<accessId-from-step-4>"
export GCS_HMAC_SECRET="<secret-from-step-4>"
export INITIAL_ADMIN_PASSWORD="$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24)"
export OIDC_CLIENT_SECRET=...        # only if auth.mode: oidc
Do not export LICENSE_KEY or DEFINITE_ONPREM_SETUP_TOKEN for the default brokered flow. definite init obtains the setup token and license from Definite after proving the active GCP identity.
Don’t commit config.yaml to a public repo while these env vars are exported into your shell history. If you want to script the install, source the values from a secret manager (e.g. gcloud secrets versions access).

6. (Optional) Use LiteLLM gateway

If you want a single LLM gateway in front of Vertex (for rate limiting, request logging, model routing, etc.), deploy LiteLLM into the cluster and set llm.provider: anthropic in config.yaml with api_key pointing at LiteLLM’s master key. LiteLLM presents an Anthropic-shaped API while talking to Vertex behind the scenes.

7. Preflight with definite doctor

definite doctor --config config.yaml
doctor runs a battery of preflight checks: it connects to Postgres and runs SELECT version(), validates the Kubernetes context, checks object-store config shape, and (for Anthropic) pings the LLM API. Fix anything it flags before moving on.

8. Deploy with definite init

definite init --config config.yaml
init re-runs preflight, renders the bundled Helm chart with your values, and runs helm upgrade --install. It waits for pods to reach Ready by default. Useful flags:
FlagPurpose
--dry-runRender values, don’t apply
--wait=falseReturn as soon as Helm finishes; don’t wait for pods
--skip-preflightSkip doctor. Use only when the troubleshooting path says a private-network check is expected to fail from your laptop
Watch the rollout in another terminal:
definite status --config config.yaml
definite logs api --follow

9. Log in as the initial admin

For local auth, the auth.initial_admin_email and auth.initial_admin_password.env settings in config.yaml are passed into the Helm install. On first startup, the API creates the initial admin from those values; you do not need a follow-up kubectl set env rollout. Save the generated INITIAL_ADMIN_PASSWORD somewhere safe (1Password, Vault, etc.). When the brokered URL reports DNS/TLS ready, open https://<brokered-hostname> in a browser and log in with the admin email and password from above.

Day-2 operations

The same CLI handles upgrades, logs, license, and lakehouse maintenance. A few of the most common commands:
definite status   --config config.yaml         # `kubectl get pods,svc,ingress` for the namespace
definite logs api --follow                     # stream component logs
definite upgrade  --config config.yaml         # re-render and re-apply with the current CLI version
definite license  status                       # show the deployment's live plan + activation status
definite run maintenance stats                 # lakehouse file/snapshot stats
Definite’s built-in docs include a full CLI reference for every command and flag.

Phase 3: Teardown

definite bootstrap provisions a Google Cloud Load Balancer via a Kubernetes Service: LoadBalancer (owned by the cluster, not by gcloud). Delete the ingress Service first so the LB’s forwarding rules and target pools are cleaned up before the cluster goes away. gcloud container clusters delete will usually clean orphans up, but deleting the Service first avoids a stuck LB you have to track down by hand.
# Trigger Kubernetes to delete the cloud load balancer cleanly.
kubectl delete svc ingress-nginx-controller -n ingress-nginx --ignore-not-found

# Lakehouse bucket (snapshot first if you care about the data).
gcloud storage rm -r "gs://${BUCKET}"

# Service account + HMAC key.
gcloud storage hmac list --service-account="$SA_EMAIL"
gcloud storage hmac delete <accessId>
gcloud iam service-accounts delete "$SA_EMAIL"

# Postgres (take a snapshot first if you want a backup).
gcloud sql instances delete "$SQL_INSTANCE"

# GKE cluster.
gcloud container clusters delete "$CLUSTER_NAME" --region "$REGION"
For production teardowns, snapshot Cloud SQL and back up the GCS bucket first; once they’re deleted, they’re gone.

Troubleshooting

SymptomLikely causeFix
GKE cluster takes 10-15 min to provisionNormal for Autopilot regional clustersWait
Lakehouse pod stuck Pending on PVCNo default StorageClass, or the name doesn’t existkubectl get storageclass; set lakehouse.storage.storage_class_name to one that exists (premium-rwo on Autopilot)
definite doctor Postgres check fails with “connect timeout” from a laptopCloud SQL is on a private IP; the laptop is outside the VPCExpected for private Cloud SQL from a laptop. The in-cluster API pods reach Cloud SQL over the VPC’s private network. To fully validate before install, run definite doctor from a pod in the cluster. Use --skip-preflight only for this known private-network check, not for object-store, Kubernetes, license, or LLM failures
definite doctor Postgres check fails from in-clusterCloud SQL has no private IP, or the cluster can’t reach itConfirm Cloud SQL is on the same VPC as the cluster with a private IP
LB IP never appearsIngress controller isn’t running, or the GKE ingress controller is fighting with ingress-nginxkubectl get pods -n ingress-nginx; if you have both controllers, pick one
Brokered URL never becomes readyThe setup broker has not pointed *.onprem.definite.app at the load balancer yet, or the ingress target is wrongRe-run definite init --config config.yaml --dns-mode definite-brokered, confirm the ingress Service target, and retry before debugging the app
Customer-owned hostname does not workCustomer-owned DNS was selected, but the A record does not point at the load balancer or the custom cert/private CA path is incompleteFix the customer DNS/cert path or switch back to the default Definite-brokered DNS mode for first install
cert-manager webhook never ready (Autopilot)Lease pinned to kube-system, which is Google-manageddefinite bootstrap pins the lease to cert-manager; if you installed cert-manager by hand, set --set global.leaderElection.namespace=cert-manager
Fi can’t reach VertexNode SA missing roles/aiplatform.user, or model not enabled in your regionConfirm the binding (step 5 above) and enable the model in Vertex Model Garden
Lakehouse can’t read/write GCSHMAC key was created against a service account without object-admin on the bucketRe-grant roles/storage.objectAdmin to the SA, or regenerate the HMAC pair
You can log in, but every product page or API call returns HTTP 403Brokered license injection failed, or customer-owned/manual mode has an invalid static licenseIn the default brokered flow, capture definite init output and API logs, confirm cloud attestation still works, then retry once before escalating to Definite. Only customer-owned/manual mode should add or rotate a static license block

Next steps

  • Connect a data source to start ingesting data.
  • Set up the MCP server so Claude, Cursor, or Windsurf can query your deployment.
  • For deeper config (custom container registry, sandbox network policies, OIDC tuning), see the config reference in Definite’s built-in docs.

Support

For issues or questions, contact hello@definite.app.