Install on GCP
Stand up Definite in your own GCP project: GKE, Cloud SQL Postgres, GCS, driven by gcloud and the definite CLI.
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
| Resource | Notes |
|---|---|
| VPC + subnet | Regional VPC with a subnet for the GKE nodes; private cluster recommended |
| GKE cluster | Kubernetes 1.30 (configurable). Autopilot works; Standard is also supported |
| Cloud SQL Postgres 15 | Private IP, reachable only from the VPC |
| GCS bucket | Lakehouse data; uniform bucket-level access, versioning recommended |
| GCS HMAC key | Service-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 StorageClass | GKE 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
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>).
Install the following on the machine you'll run gcloud and definite from:
| Tool | Version | Check |
|---|---|---|
gcloud | recent | gcloud version |
kubectl | 1.28+ | kubectl version --client |
helm | 3.12+ | helm version |
gke-gcloud-auth-plugin | recent | gke-gcloud-auth-plugin --version |
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.
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).
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
| Option | When 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 Identity | Use 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:
| Prerequisite | What it provides |
|---|---|
| Ingress controller | HTTP/S routing for the deployment's Ingress resource |
| Definite-brokered DNS/TLS readiness | The default setup path uses the Definite setup broker and the returned *.onprem.definite.app URL |
| agent-sandbox CRDs | Custom 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:
| Flag | Purpose |
|---|---|
--dry-run | Render values, don't apply |
--wait=false | Return as soon as Helm finishes; don't wait for pods |
--skip-preflight | Skip 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
| Symptom | Likely cause | Fix |
|---|---|---|
| GKE cluster takes 10-15 min to provision | Normal for Autopilot regional clusters | Wait |
Lakehouse pod stuck Pending on PVC | No default StorageClass, or the name doesn't exist | kubectl 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 laptop | Cloud SQL is on a private IP; the laptop is outside the VPC | Expected 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-cluster | Cloud SQL has no private IP, or the cluster can't reach it | Confirm Cloud SQL is on the same VPC as the cluster with a private IP |
| LB IP never appears | Ingress controller isn't running, or the GKE ingress controller is fighting with ingress-nginx | kubectl get pods -n ingress-nginx; if you have both controllers, pick one |
| Brokered URL never becomes ready | The setup broker has not pointed *.onprem.definite.app at the load balancer yet, or the ingress target is wrong | Re-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 work | Customer-owned DNS was selected, but the A record does not point at the load balancer or the custom cert/private CA path is incomplete | Fix 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-managed | definite 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 Vertex | Node SA missing roles/aiplatform.user, or model not enabled in your region | Confirm the binding (step 5 above) and enable the model in Vertex Model Garden |
| Lakehouse can't read/write GCS | HMAC key was created against a service account without object-admin on the bucket | Re-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 403 | Brokered license injection failed, or customer-owned/manual mode has an invalid static license | In 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.