Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Deployment/Providers/Gcp/Deploying Care with the GCP template

From OHC Network Wiki
guidedeploymentCARE 3.0+

This guide covers deploying Care on Google Cloud using the gcp_template repository — an infrastructure-as-code template built with OpenTofu and Helm. It provisions the entire platform (network, cluster, databases, storage, encryption keys) and then installs Care and its supporting services as Helm releases.

This is the recommended path for new GCP deployments. It replaces the older click-through console workflow described in Deploying Care on GKE, which relied on manually created resources, Cloud Source Repositories, and the infra_template manifests.

📝 Note
The repository is the source of truth. Variable names, defaults, and chart values change over time — always check the repository's README.md, environments/sample.tfvars, and root variables.tf against the version you are deploying.

What the template provisions

The template is split into four OpenTofu modules, each with its own state and its own Makefile.

Module Purpose
pre-infra/ Project bootstrap — enables the required Google Cloud APIs and optionally creates the Cloud DNS zone.
infra/ The core platform — VPC and subnets, the GKE cluster and node pools, Cloud SQL for PostgreSQL, Cloud Storage buckets, Cloud Armor, Workload Identity Federation for GitHub, an optional jump host, and optional Scribe credentials.
KMS/ The KMS key ring and the customer-managed encryption keys used to encrypt Cloud Storage buckets and other resources.
deploy/ The Kubernetes layer — namespace, Secrets and ConfigMaps, and the Helm releases for every application.

Two further directories support these modules: helm_charts/ holds the in-repo Helm charts, and environments/ holds the sanitised tfvars template. Helper scripts for synchronising configuration with Secret Manager live in scripts/.

Applications installed by the deploy module

Chart What it runs
care_be The Care backend — the API deployment, the Celery worker, the Celery beat scheduler, and a database migration job. Includes optional HPAs for the API and the worker.
care_fe The Care frontend, served as a container behind the gateway.
gateway The Gateway API resources, the cert-manager ClusterIssuer and Certificate, the HTTP-to-HTTPS redirect, and the GCP gateway policy.
redis Redis, used as the Celery broker and the cache.
metabase Metabase, backed by its own Cloud SQL instance.
dcm4chee Optional. The DICOM stack — dcm4chee-arc, OpenLDAP, an nginx proxy, and the OHIF viewer. Enabled with enable_dicom.

cert-manager itself is installed from the upstream Jetstack chart with the Gateway API integration enabled, so certificates are issued directly against the Gateway.

Prerequisites

  • OpenTofu and Helm installed locally.
  • The Google Cloud SDK (gcloud), authenticated against the target project.
  • A GCP project with billing enabled, and a project number.
  • A GCS bucket to hold the OpenTofu remote state.
  • Permission to read and write Secret Manager secrets in the project.
  • Domain names for the frontend, the API, and (if used) Metabase and DICOM.

Configuration

All configuration is supplied as a single tfvars file per environment. The canonical template is environments/sample.tfvars; the source of truth for every variable is the root variables.tf.

Real environment files are never committed. They live in Secret Manager under the naming convention tofu-tfvars-<env>, and each module's Makefile pulls the file down at runtime before planning or applying.

📝 Note
Only sanitised samples belong in the repository. Treat environments/<env>.tfvars on your workstation as a transient artefact.

Required environment variables

Set these before running any make target:

Variable Description
PROJECT_ID The GCP project ID. May also be supplied as TF_VAR_project_id.
ENV_NAME The environment name. May also be supplied as TF_VAR_environment or TF_VAR_env_name.
BACKEND_BUCKET The GCS bucket holding the OpenTofu state.
export BACKEND_BUCKET="<your-state-bucket>"
export PROJECT_ID="<your-gcp-project-id>"
export ENV_NAME="<environment-name>"

Preparing the tfvars file

  1. Copy the template: cp environments/sample.tfvars environments/<env>.tfvars
  2. Edit the values for your environment.
  3. Push it to Secret Manager so the modules can retrieve it:
cd pre-infra
make push-tfvars PROJECT_ID=<gcp-project> ENV_NAME=<env>

Override the default file path with LOCAL_TFVARS_FILE=<path> if your file lives elsewhere.

Key configuration groups

The tfvars file is organised into the following groups. See environments/sample.tfvars for the full annotated template.

Group Notable variables
Environment identity region, project_id, project_number, zones, zone, org, app, environment
Domains and DNS web_domain_name, api_domain_name, metabase_domain_name, dicom_domain_name, enable_dns_zone, dns_zone_domain
Cluster node_pools (machine type, counts, disk, node locations, private nodes), cluster_name
Networking database_subnets, gke_subnets, gke_pods_range, gke_services_range, proxy_only_subnet_cidr
Databases cloudsql_tier, cloudsql_disk_size, cloudsql_read_replica_count, cloudsql_read_replica_tier, metabase_cloudsql_tier
Feature toggles enable_dicom, enable_cloud_armor, enable_github_wif, enable_scribe, enable_jumphost, enable_legacy_ingress, enable_local_cors
Applications helm_config — image repository and tag per service, plus the API and Celery worker autoscaling settings
Secrets and config jwks_base64, additional_secrets, additional_config_map_data
TLS external_tls_cert, external_tls_key, external_tls_base_domains
💡 Tip
Domain variables are lists, even when you only have one hostname — web_domain_name = ["app.example.org"]. Cloud SQL sizing values are numbers, not quoted strings.

Node pools

Node pools are configured as a list of objects. Each entry sets the machine type, the autoscaling bounds, the disk size, the zones the pool spans, and whether the nodes are private.

node_pools = [
  {
    name                 = "default"
    machine_type         = "e2-standard-2"
    min_count            = 1
    max_count            = 2
    preemptible          = false
    disk_size_gb         = 100
    node_locations       = "asia-south1-a,asia-south1-b"
    enable_private_nodes = true
  },
]

Application images and autoscaling

helm_config selects the image for each service and controls the horizontal pod autoscalers on the Care backend. Autoscaling is CPU-based and scales on requests, so it only behaves sensibly when resource requests are set.

helm_config = {
  care_backend = {
    repository = "asia-south1-docker.pkg.dev/example-project/staging/care"
    tag        = "latest"
    # api_autoscaling_enabled      = true
    # api_autoscaling_min_replicas = 2
    # api_autoscaling_max_replicas = 6
    # api_autoscaling_target_cpu   = 80
  }
  care_frontend = {
    repository = "asia-south1-docker.pkg.dev/example-project/staging/care_fe"
    tag        = "latest"
  }
  metabase = {
    repository = "metabase/metabase"
    tag        = "v0.63.13"
  }
  redis = {
    repository = "redis"
    tag        = "8-alpine"
  }
}

TLS certificates

By default, cert-manager issues certificates for every configured hostname through the Gateway. If you already hold a wildcard certificate, supply it with external_tls_cert, external_tls_key, and external_tls_base_domains. cert-manager continues to issue certificates for any hostname the wildcard does not cover.

The deploy module also creates a self-signed placeholder certificate so the Gateway can come up before cert-manager has issued the real one.

Deploying

Modules must be applied in order. The deploy/ module reads remote state outputs from both infra/ and KMS/, so those must exist first.

  1. pre-infra/
  2. infra/
  3. KMS/
  4. deploy/
📝 Note
Confirm the module ordering against the README.md in the revision you are deploying. The relationship between infra/ and KMS/ has been revised, and the correct order depends on whether the infra module consumes KMS keys directly.

Per-module commands

Each module exposes the same targets. Run them from inside the module directory:

make init BACKEND_BUCKET=<state-bucket>
make pull-tfvars PROJECT_ID=<gcp-project> ENV_NAME=<env>
make plan PROJECT_ID=<gcp-project> ENV_NAME=<env> BACKEND_BUCKET=<state-bucket>
make deploy PROJECT_ID=<gcp-project> ENV_NAME=<env> BACKEND_BUCKET=<state-bucket>

make plan and make deploy both run pull-tfvars first, so the plan always reflects what is currently in Secret Manager. make deploy is interactive unless you pass AUTO_APPROVE=true.

Two further targets are available: make lint formats the OpenTofu files recursively, and make destroy tears the module down.

A full first deployment

export BACKEND_BUCKET="<state-bucket>"
export PROJECT_ID="<gcp-project>"
export ENV_NAME="<env>"

cd pre-infra
make init BACKEND_BUCKET=$BACKEND_BUCKET
make plan  PROJECT_ID=$PROJECT_ID ENV_NAME=$ENV_NAME BACKEND_BUCKET=$BACKEND_BUCKET
make deploy PROJECT_ID=$PROJECT_ID ENV_NAME=$ENV_NAME BACKEND_BUCKET=$BACKEND_BUCKET

cd ../infra
make init BACKEND_BUCKET=$BACKEND_BUCKET
make plan  PROJECT_ID=$PROJECT_ID ENV_NAME=$ENV_NAME BACKEND_BUCKET=$BACKEND_BUCKET
make deploy PROJECT_ID=$PROJECT_ID ENV_NAME=$ENV_NAME BACKEND_BUCKET=$BACKEND_BUCKET

cd ../KMS
make init BACKEND_BUCKET=$BACKEND_BUCKET
make plan  PROJECT_ID=$PROJECT_ID ENV_NAME=$ENV_NAME BACKEND_BUCKET=$BACKEND_BUCKET
make deploy PROJECT_ID=$PROJECT_ID ENV_NAME=$ENV_NAME BACKEND_BUCKET=$BACKEND_BUCKET

cd ../deploy
make init BACKEND_BUCKET=$BACKEND_BUCKET
make plan  PROJECT_ID=$PROJECT_ID ENV_NAME=$ENV_NAME BACKEND_BUCKET=$BACKEND_BUCKET
make deploy PROJECT_ID=$PROJECT_ID ENV_NAME=$ENV_NAME BACKEND_BUCKET=$BACKEND_BUCKET

After the first apply

  1. Read the Gateway's external IP address from the cluster and create DNS A records for each hostname pointing at it.
  2. Wait for cert-manager to replace the placeholder certificate with an issued one.
  3. Confirm the migration job for the Care backend completed successfully before treating the deployment as live.

Releasing a new version

Application releases are configuration changes, not infrastructure changes. To roll out a new Care build:

  1. Update the tag for the relevant service under helm_config in your environment's tfvars.
  2. Push the updated file to Secret Manager with make push-tfvars.
  3. Run make plan and then make deploy from the deploy/ module.

Only the deploy/ module needs to run — the platform modules are untouched.

The backend Helm chart annotates its pods with checksums of the rendered Secret and ConfigMap, so a change to configuration triggers a rollout automatically rather than leaving pods running with stale values.

Operational notes

  • Never commit real tfvars. Secret Manager is the only place environment configuration should live.
  • Plan before every apply. make plan runs with -lock=false, so it is safe to run alongside other readers, but it will not protect you from a concurrent apply.
  • Helm history is capped at ten revisions per release, which bounds how far back helm rollback can go.
  • Legacy naming overrides exist for most resources (cluster_name, vpc_network_name, namespace_name, and others) so an existing deployment can be adopted into the template without renaming live infrastructure. Leave them unset for a greenfield deployment and let the naming convention apply.
  • The jump host is optional and controlled by enable_jumphost. It takes its SSH keys from jumphost_ssh_keys. Disable it if you reach the cluster another way.

See also