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 versionIf 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 listOn a local workstation, the Google Terraform provider commonly uses Application Default Credentials:
gcloud auth application-default loginCloud 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_BRANCH3. 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-16Add 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 validateCreate and inspect a saved plan:
terraform plan -out=tfplan
terraform show tfplanStop 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 tfplanRun 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.