Skip to main content
Work through this page before you start the install. Several choices here can’t be changed later without rebuilding something, and each one is marked Decision. Write your answers down: the install steps ask for them. The commands on this page use the shell variables from the install’s first step, Set the install variables. Set them first.

Tools on your workstation

Your workstation must also reach the cluster’s control plane. By default the control plane has a public endpoint, which you restrict with master_authorized_networks. With a private endpoint, you run kubectl and helm from inside your network or over a VPN. See Control-plane access.

Google Cloud project

  • The project must already exist, with billing enabled. The configuration doesn’t create projects.
  • Use a project that holds nothing else. The application’s service account gets roles/datastore.user at project level, because Firestore has no per-database data role. In a shared project, that grant silently reaches every other Firestore database too. The APIs the configuration enables and the network it creates are project-scoped for the same reason.

Permissions for the deploying identity

OpenTofu runs as you, through Application Default Credentials (gcloud auth application-default login). No service-account key is needed. Grant that identity roles/owner on the project. The configuration grants IAM roles to the service accounts it creates, and an identity that can grant roles can already reach everything the project holds. If your policy doesn’t allow Owner, grant roles that cover every resource type the configuration creates.
Installations are tested with Owner. The roles below are derived from the resources the configuration creates, and haven’t yet been validated on a test installation. If an apply fails with a permission error, add the role the error names, and tell your Jedify contact so this list can be corrected.
On a Shared VPC, the host project needs grants of its own. See Shared VPC.

APIs

OpenTofu enables every API it needs on the first apply: compute, container, firestore, storage, secretmanager, sqladmin, redis, servicenetworking, artifactregistry, aiplatform, iam, iamcredentials, dns and modelarmor (all .googleapis.com). With Private Service Connect it also enables memorystore and networkconnectivity. OpenTofu leaves them enabled when you destroy the installation, because disabling an API affects the whole project. To enable APIs and manage project IAM, OpenTofu itself needs two APIs first. Enable them:
To verify, list them. Both names should appear:
If you’ll serve a Google-managed certificate through a Certificate Manager map, also enable certificatemanager.googleapis.com.

Capacity and quotas

At the default values, the application’s pods request about 9.6 vCPU and 37.5 GiB of memory: seventeen pods at 0.5 vCPU and 2 GiB, plus the MCP server, the sandbox, a queue worker and one smaller component. The platform services in the jedify namespace add to that, and GKE Autopilot gives default requests to any container that doesn’t set its own. CronJobs start short-lived pods every minute.
  • CPU quota. Make sure your regional CPU quota covers those requests, with room for the extra nodes Autopilot adds while it upgrades or consolidates.
  • Nodes. Autopilot adds nodes as load needs them. A typical installation runs 6 to 7 nodes.
  • Datastores. The defaults are a production shape: Cloud SQL db-custom-2-7680 with a standby in a second zone (REGIONAL), and Memorystore STANDARD_HA with a replica. For a pilot, postgres_availability_type = "ZONAL" and redis_tier = "BASIC" roughly halve their cost. See the OpenTofu reference.

Region and architecture

Decision: the region. It holds the cluster, the bucket, Cloud SQL, Memorystore and Artifact Registry. Decision: the Firestore location. Firestore’s location list is its own: it takes regions such as europe-west1 and multi-regions such as nam5 and eur3, and not every compute region is on it. You can’t change it after the database exists. Moving means a second database and a migration, so get it right the first time if data residency matters.

Arm or amd64 nodes

Decision: the node architecture. Every pod runs on one architecture, chosen in one place per values file.
Pods run on GKE Autopilot’s autopilot-arm compute class. It’s available in seven regions only:us-central1, us-east1, us-west1, europe-west1, europe-west2, europe-west4, asia-southeast1and from GKE version 1.35.3-gke.1389000. The cluster follows the REGULAR release channel, so a new cluster is newer than that. On an older cluster, every Arm pod stays Pending; OpenTofu warns about it after the apply.OpenTofu refuses a region outside that list while node_architecture is arm64. That’s deliberate: otherwise the apply succeeds and every pod then waits for an Arm node that never comes.Each pod names both the compute class and the architecture. The class gets Arm nodes provisioned at all; the architecture label adds the toleration that lets a pod onto them. During the install, Autopilot prints this for every pod:
That warning is expected. Don’t remove the label to silence it: with the class alone, pods stay Pending next to a ready Arm node.
Always name an architecture. A pod that names none lands on Autopilot’s default class, which is x86. And copy images with every architecture they’re published for: a single-architecture copy fails on the other kind of node with no match for platform in manifest, which looks like a missing image. See Airgapped installations.

Network

Decision: who owns the network. Either OpenTofu creates a dedicated VPC (the default), or you deploy into a network your network team already runs, including a Shared VPC.
OpenTofu creates, in your project:Change the ranges with subnet_cidrs before the first apply. Secondary ranges can’t be resized once the cluster uses them: running out of pod addresses means rebuilding the cluster. The control plane takes 172.16.0.0/28 (master_ipv4_cidr_block), which must not overlap anything your network routes.

What an existing network must provide

Shared VPC

Before the first apply, the host project grants the usual GKE service-project roles:
  • roles/container.hostServiceAgentUser on the host project, to your project’s GKE service agent.
  • roles/compute.networkUser on the subnet, to your project’s GKE service agent and Google APIs service agent.
Set HOST_PROJECT to the host project and SUBNET to the node subnet’s name, then run, as an identity that can grant roles in the host project:
To verify, list who holds roles/compute.networkUser on the subnet. Both service agents should appear:
Decision: how Cloud SQL’s Private Service Connect endpoint is made, if you use Private Service Connect. With the default, cloudsql_psc_endpoint = "forwarding_rule", OpenTofu creates the endpoint, and its forwarding rule needs compute.networks.use on the host VPC. A network can’t be granted on its own, so your deploying identity needs a role on the host project. With cloudsql_psc_endpoint = "connection_policy", Cloud SQL creates the endpoint through the network’s google-cloud-sql Service Connection Policy, and your deploying identity needs nothing on the host project.

Private Services Access or Private Service Connect

Decision: how the cluster reaches Cloud SQL and the cache (datastore_connectivity).
Switching an existing installation between psa and psc replaces both datastores, and their data doesn’t move with them. Choose before the first apply.

Gateway address

By default GKE allocates the Gateway’s address when it creates the Gateway, and releases it when the Gateway goes away. A reinstall, or a change of Gateway class, then moves the address, and your DNS records have to follow. To keep the address, reserve it and give its name to the chart as gateway.addressName:
Use any regular subnet of the cluster’s VPC; the node subnet works. On a Shared VPC, pass the subnet’s full path: projects/<host project>/regions/<region>/subnetworks/<subnet>.The purpose is not optional. An address reserved without it gets the default purpose, GCE_ENDPOINT, and the Gateway never gets an address. You can’t change a purpose: delete the address and reserve it again, with --addresses <ip> to keep the same IP.

Firewall

In a network OpenTofu creates, GKE manages the load balancer’s firewall rules itself. In an existing network, and especially on a Shared VPC, make sure these rules exist:

Egress masquerade

Decision: whether pod addresses may leave the VPC. GKE Autopilot keeps a pod’s own address when it calls a private destination: its default EgressNATPolicy exempts RFC 1918 ranges and 100.64.0.0/10. So a call to a gateway, a firewall or a warehouse on a private address arrives from the pod range. If your pod range must never be seen outside the VPC (a 100.64.x range reused across clusters, or a central firewall that should see node addresses), set egressMasquerade.enabled: true in values/jedify-infra.yaml. Every packet a pod sends out of the cluster then leaves with its node’s address, so firewalls and allowlists see the node subnet. Traffic inside the cluster is unaffected. The chart rewrites the default EgressNATPolicy on every install and upgrade. Turning the setting off later stops the rewrite; it doesn’t restore GKE’s original list.

Control-plane access

Decision: who can reach the Kubernetes API.
  • Public endpoint (default). With master_authorized_networks empty, any address on the internet can reach the API server. Authentication and authorization still apply, but on an installation meant to talk to nobody, that’s the opposite of the intent. Set master_authorized_networks to your operators’ egress ranges. It defaults to empty only because a list that leaves out whoever runs tofu apply locks them out of the cluster they just created.
  • Private endpoint. enable_private_endpoint = true makes the API reachable only from inside the network. GKE then requires authorized networks: list your VPN’s ranges in master_authorized_networks, because with the list empty only the cluster’s own nodes and pods reach the API, and helm install times out from your VPN. If your VPN enters the network in a different region, also set private_endpoint_global_access = true. To place the private endpoint in a subnet your network team chose, set private_endpoint_subnetwork.

Egress

Nodes have no public addresses. Everything below needs a path out of the cluster, through Cloud NAT or a route your network provides. A blocked call hangs instead of failing, which is slower to diagnose, so allow these before the install. From your workstation, tofu init reaches registry.opentofu.org for providers, and you reach the release registry to pull the release.

DNS and TLS

Decision: two hostnames. The installation serves two names, and both resolve to the Gateway’s address: Put them on names you control, not on names derived from an IP address. The web app names its endpoints in its settings and Zitadel stores redirect URIs per application, so a hostname tied to the address turns any address change into a settings change. Decision: the Gateway class (gateway.className). Changing the class reallocates the address, so DNS has to follow. Decision: the certificate. One certificate must cover both hostnames (a wildcard, or both names as subject alternative names), and your users’ browsers must trust it. A certificate from a private certificate authority works only on devices that trust that authority: the web app fetches the identity provider’s discovery document in the background, and browsers offer no click-through warning for a background request, so an untrusted certificate simply fails there. To see the certificate a host actually serves, as opposed to what a certificate resource reports:
GKE ships and manages the Gateway API CRDs. Don’t apply an upstream Gateway API CRD bundle over them: Autopilot rejects the experimental channel, and a standard bundle older than the cluster’s would downgrade them.