Cloud Infrastructure Setup

This chapter shows how to define the GCP resources around ERDDAP with Terraform. Terraform is an infrastructure-as-code tool that supports multiple cloud providers; the examples here use the Google Cloud provider and are therefore GCP-specific.

The code blocks are sanitized templates. They are not a complete deployment that should be applied unchanged.

1. Establish Source and State Ownership

Keep the deployable Terraform configuration in one controlled Git repository so future maintainers can find its history, owners, and review process. A public documentation repository may contain sanitized examples, while the configuration containing real project names and operational values normally belongs in a private organization repository.

Terraform source and Terraform state are different assets:

Item Purpose Recommended location
Terraform source Versioned .tf files, examples, and review history Controlled GitHub or Google Secure Source Manager repository
Terraform state Records the resources managed by this configuration Dedicated, access-controlled GCS backend bucket

Do not copy .terraform/, local state files, saved plan files, or credentials between systems. Do not let two unrelated Terraform configurations manage the same resource.

Before continuing, identify who owns the following:

  • GCP project, billing, APIs, quotas, and organization policies;
  • Terraform repository and state bucket;
  • service accounts and IAM bindings;
  • VPC, subnet, firewall, and load-balancer resources;
  • approved boot images and snapshot policies;
  • required organizational labels.

2. Prepare the Terraform Workstation

Terraform can run from Cloud Shell, a managed workstation, or an approved CI runner. First check whether it is already installed:

terraform version
gcloud version

If Terraform is missing, install it using the official HashiCorp installation instructions.

Authenticate to the intended GCP project:

gcloud auth login
gcloud config set project YOUR_GCP_PROJECT_ID
gcloud auth list
gcloud config list

On a local workstation, the Google Terraform provider commonly uses Application Default Credentials:

gcloud auth application-default login

Cloud Shell and organization-managed runners may provide credentials differently. Authentication proves identity; IAM still determines which resources that identity may inspect or change.

Clone the controlled infrastructure repository and work on a reviewable branch:

git clone https://github.com/YOUR_ORGANIZATION/YOUR_TERRAFORM_REPOSITORY.git
cd YOUR_TERRAFORM_REPOSITORY
git switch -c YOUR_CHANGE_BRANCH

3. Organize the Configuration

One clear file layout is shown below. Terraform loads all .tf files in the working directory together, so the filenames organize the code for people rather than controlling execution order.

File Purpose
versions.tf Terraform version constraint and required providers
backend.tf Remote-state backend declaration
variables.tf Input variable types and descriptions
terraform.tfvars.example Sanitized example values that may be committed
terraform.tfvars Deployment values; commit only if organizational policy permits and it contains no secrets
bucket.tf Optional GCS bucket and bucket-level IAM
instance.tf VM, boot disk, persistent data disk, and service account attachment
snapshots.tf Snapshot policy and disk-policy attachments
outputs.tf Useful names, addresses, and resource identifiers

Keep shared platform resources outside this configuration when another team or Terraform state owns them. Reference those resources as inputs or data sources instead of declaring a second copy.

4. Configure Providers, Backend, and Inputs

4.1 Provider and backend

versions.tf identifies Terraform and provider requirements:

terraform {
  required_version = ">= 1.6, < 2.0"

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 7.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

Treat the version constraints as an example baseline. Select and test versions according to the organization’s upgrade policy.

Use a partial backend configuration so the public template does not contain a real state-bucket name:

terraform {
  backend "gcs" {}
}

The backend bucket must already exist and should have restricted IAM, versioning or another recovery control, and a retention approach appropriate to the organization.

4.2 Required deployment values

Do not make one region or zone look universal. Require each deployment to choose them explicitly:

variable "project_id" {
  description = "GCP project that will contain the ERDDAP resources"
  type        = string
}

variable "region" {
  description = "GCP region selected for this deployment"
  type        = string
}

variable "zone" {
  description = "GCP zone selected for this deployment"
  type        = string
}

variable "machine_type" {
  description = "Compute Engine machine type selected after workload sizing"
  type        = string
}

Provide values in an approved mechanism such as terraform.tfvars:

project_id   = "YOUR_GCP_PROJECT_ID"
region       = "YOUR_GCP_REGION"       # Example only: us-east4
zone         = "YOUR_GCP_ZONE"         # Example only: us-east4-a
machine_type = "YOUR_MACHINE_TYPE"     # Example only: c4-highmem-16

Add organizational metadata variables and labels required in the target environment. NOAA-specific labels are requirements for NOAA deployments, not general Terraform requirements.

5. Define Storage and Least-Privilege IAM

A GCS data bucket is optional. Create one only when ERDDAP will serve file-backed data from GCS and the bucket is not already managed elsewhere.

resource "google_storage_bucket" "erddap_data" {
  name                        = "YOUR_GLOBALLY_UNIQUE_BUCKET_NAME"
  project                     = var.project_id
  location                    = var.region
  force_destroy               = false
  uniform_bucket_level_access = true

  labels = {
    environment = "YOUR_ENVIRONMENT"
    application = "erddap"
  }
}

resource "google_storage_bucket_iam_member" "erddap_reader" {
  bucket = google_storage_bucket.erddap_data.name
  role   = "roles/storage.objectViewer"
  member = "serviceAccount:YOUR_ERDDAP_VM_SERVICE_ACCOUNT_EMAIL"
}

Keep force_destroy = false for production data. The ERDDAP VM normally needs read-only object access. Give a separate processing or publishing identity write permissions only when that workload must create, replace, or delete objects.

6. Define the VM and Its Disks

The example separates the boot and data disks so both names and snapshot-policy attachments are explicit. Sizes, disk types, and machine types are examples to be replaced after workload sizing.

resource "google_compute_disk" "erddap_boot" {
  name    = "YOUR_ERDDAP_BOOT_DISK_NAME"
  project = var.project_id
  zone    = var.zone
  type    = "hyperdisk-balanced"
  size    = 100
  image   = "YOUR_APPROVED_BOOT_IMAGE"
}

resource "google_compute_disk" "erddap_data" {
  name    = "YOUR_ERDDAP_DATA_DISK_NAME"
  project = var.project_id
  zone    = var.zone
  type    = "hyperdisk-balanced"
  size    = 250
}

resource "google_compute_instance" "erddap" {
  name                      = "YOUR_ERDDAP_VM_NAME"
  project                   = var.project_id
  zone                      = var.zone
  machine_type              = var.machine_type
  allow_stopping_for_update = true

  metadata = {
    block-project-ssh-keys = true
    enable-osconfig        = true
  }

  boot_disk {
    source      = google_compute_disk.erddap_boot.id
    auto_delete = false
  }

  attached_disk {
    source      = google_compute_disk.erddap_data.id
    device_name = "erddap-data"
  }

  network_interface {
    subnetwork = "YOUR_SUBNETWORK_SELF_LINK"
    nic_type   = "GVNIC"
  }

  service_account {
    email  = "YOUR_ERDDAP_VM_SERVICE_ACCOUNT_EMAIL"
    scopes = ["cloud-platform"]
  }

  labels = {
    environment = "YOUR_ENVIRONMENT"
    role        = "erddap-server"
  }
}

Add the target organization’s required labels, shielded-VM settings, reservation settings, network tags, and policies. Confirm that the selected machine family supports the chosen disk and network-interface types.

Attach an existing snapshot policy to both disks when that is part of the backup design:

resource "google_compute_disk_resource_policy_attachment" "erddap_boot" {
  name    = "YOUR_SNAPSHOT_POLICY_NAME"
  disk    = google_compute_disk.erddap_boot.name
  project = var.project_id
  zone    = var.zone
}

resource "google_compute_disk_resource_policy_attachment" "erddap_data" {
  name    = "YOUR_SNAPSHOT_POLICY_NAME"
  disk    = google_compute_disk.erddap_data.name
  project = var.project_id
  zone    = var.zone
}

Normal new deployment versus snapshot restoration

For a normal initial deployment, create the boot disk from an approved image as shown above. The ERDDAP software is then installed and configured on the new VM.

Use a snapshot only when restoring or cloning a disk that already contains a configured system. SWFSC used this path while replacing an existing VM during a machine-family change; it is not the normal first-installation path.

resource "google_compute_disk" "restored_boot" {
  name     = "YOUR_RESTORED_BOOT_DISK_NAME"
  project  = var.project_id
  zone     = var.zone
  type     = "hyperdisk-balanced"
  size     = 100
  snapshot = "projects/YOUR_GCP_PROJECT_ID/global/snapshots/YOUR_EXISTING_SNAPSHOT"
}

The snapshot must already exist. A 404 notFound response indicates an incorrect reference or missing prerequisite, not a resource Terraform will create automatically.

7. Initialize, Review, and Apply

Initialize against the state backend selected for this deployment:

terraform init \
  -backend-config='bucket=YOUR_TERRAFORM_STATE_BUCKET' \
  -backend-config='prefix=YOUR_UNIQUE_STATE_PREFIX'

Format and validate the configuration:

terraform fmt -check -recursive
terraform validate

Create and inspect a saved plan:

terraform plan -out=tfplan
terraform show tfplan

Stop if the plan would replace, detach, or destroy a resource unexpectedly. If resources already exist, resolve ownership and import requirements before applying; do not create duplicate declarations for resources managed by another state.

After review and approval, apply the saved plan:

terraform apply tfplan

Run a fresh terraform plan afterward. A successful deployment should produce no unexpected additional changes and should leave the repository, state, and resource ownership understandable to the next maintainer.