---
season: 3
season_name: "Season 3 — October 2026 (Autumn / Halloween Edition)"
day: 17
title: "Infrastructure as Code: Terraform & Cloud Build CI/CD"
summary: "Pair Terraform with Cloud Build to package agent containers and declaratively deploy Agent Runtimes, SPIFFE identities, and Agent Gateways."
tags: ["Phase 4: Infrastructure", "Cloud Build", "CI/CD", "Governance"]
canonical_url: "https://adventofagents.com/2026/10/17"
markdown_url: "https://adventofagents.com/2026/10/17.md"
video_url: "https://www.youtube.com/embed/cTRYWzMmV1s"
primary_video:
  title: "Day 17: Infrastructure as Code (IaC) & Production CI/CD"
  creator_name: "Joyce Chen"
  duration: "15:11"
  video_url: "https://www.youtube.com/embed/cTRYWzMmV1s"
---

# 🏗️ Day 17: Infrastructure as Code: Terraform & Cloud Build CI/CD

> **Season 3 — October 2026 (Autumn / Halloween Edition)** · [Interactive Web View](https://adventofagents.com/2026/10/17?utm_source=adventofagents&utm_medium=markdown&utm_campaign=adventofagents_s3_2026&utm_content=day17) · [Raw Markdown](https://adventofagents.com/2026/10/17.md?utm_source=adventofagents&utm_medium=markdown&utm_campaign=adventofagents_s3_2026&utm_content=day17)

**Summary:** Pair Terraform with Cloud Build to package agent containers and declaratively deploy Agent Runtimes, SPIFFE identities, and Agent Gateways.

**Day 17 of Google's Advent of Agents — Season 3**

Prototyping from the CLI and clicking through the Google Cloud console is great for speed, but manual setup breaks down as soon as you promote an agent across development, staging, and production environments:

- **Configuration Drift**: Hand-configured settings drift between projects.
- **Unreviewed Changes**: Console edits can accidentally widen IAM permissions or bypass security proxies.
- **Slow Recovery**: Rebuilding a multi-layer agent stack from scratch after an incident is slow and error-prone.

Production agents require reproducible **Infrastructure as Code (IaC)**, where every security guardrail and runtime dependency is defined in version-controlled configuration files and peer-reviewed before deployment.

**How It Works**

Pairing **Terraform** with **Cloud Build** automates your entire agent stack across any agent framework or runtime, including ADK on **Agent Runtime** and **Cloud Run**. The pipeline codifies the security layers from earlier this season: **SPIFFE Agent Identities** from Day 5, **Agent Gateways** from Day 8, and **Model Armor** prompt shields from Day 10.

![Automated Production Agent Deployment with Terraform and Cloud Build](/s03/s3-day17-terraform-cicd.png)

Your Terraform configuration is split into two state layers, a foundation layer for shared networking and security guardrails and a runtime layer for the agent deployment itself, both backed by a remote Cloud Storage state bucket. When you open a pull request, Cloud Build previews infrastructure changes with a Terraform plan; when you merge to main, it runs the sequential three-stage pipeline below.

**The Three-Stage Pipeline**

- **Stage 1: Shared Foundation Infrastructure**: Provisions your VPC network, Artifact Registry repository, Ingress and Egress Agent Gateways, and Model Armor prompt and response screening policies, then outputs the gateway resource IDs and TLS root certificates for the container build.
- **Stage 2: Automated Container Build & Push**: Packages your ADK agent code and the exported gateway TLS certificates into a versioned Docker container image tagged with the Cloud Build ID and pushes it to Artifact Registry.
- **Stage 3: Governed Agent Runtime Rollout**: Deploys the Agent Runtime instance pointing to the newly built container image, mints its cryptographic SPIFFE Agent Identity, grants least-privilege IAM roles, and wires inbound and outbound traffic through the Stage 1 gateways.

**Try the End-to-End Demo**: Clone the [Day 17 Kata Demo Repository](https://g.dev/cloud/adventofagents-season3-day17-demo) and run `./deploy.sh` in Cloud Shell, then test your deployed `terraform-demo-agent` in the Agent Platform Playground!

**Resources:**

- [Day 17 Kata Demo Repository](https://g.dev/cloud/adventofagents-season3-day17-demo)
- [Provision Agents with Terraform](https://g.dev/cloud/adventofagents-season3-docs-use-terraform)
- [Terraform & Cloud Build GitOps](https://g.dev/cloud/adventofagents-season3-docs-managing-infrastructure-as-code)
- [Terraform for Agent Platform](https://g.dev/cloud/adventofagents-season3-docs-use-terraform-agent-platform)

## Code & Commands

### 1. Start Here: Clone & Deploy (cloudbuild.yaml)

```yaml
# START HERE: run the end-to-end demo in Cloud Shell or your terminal:
#   gcloud config set project YOUR_PROJECT_ID
#   git clone https://github.com/chjoyce/advent-of-agents-s3-terraform-day17.git
#   cd advent-of-agents-s3-terraform-day17
#   ./deploy.sh
#
# deploy.sh enables the required APIs, creates the Terraform state bucket, grants Cloud Build
# its IAM roles, and then submits the pipeline below.
#
# REFERENCE ONLY: the YAML below is a simplified copy of cloudbuild.yaml from the demo repo.
# You do not need to copy it. To re-run only the pipeline after the first ./deploy.sh:
#   gcloud builds submit --config=cloudbuild.yaml #     --substitutions=_TF_BUCKET="${PROJECT_ID}-terraform-state",_REGION="us-central1"
#
# Expected result:
#   1. Cloud Build completes all 3 sequential stages (Stage 1 -> Stage 2 -> Stage 3) with STATUS: SUCCESS.
#   2. In Cloud Console -> Agent Platform -> Agents -> Deployments, "terraform-demo-agent" is live with its SPIFFE ID and Gateways.
#   3. Open the Playground tab on "terraform-demo-agent": normal prompts get a response, while prompt injections are blocked by Model Armor.

steps:
  # Stage 1 (Foundation): Provision VPC, Agent Gateways, Model Armor & Authz Policies
  - name: hashicorp/terraform:1.13
    id: terraform-foundation
    entrypoint: sh
    args:
      - -c
      - |
        cd terraform/foundation
        terraform init -reconfigure -backend-config="bucket=${_TF_BUCKET}" -backend-config="prefix=agent-demo/foundation"
        terraform apply -auto-approve -var="project_id=${PROJECT_ID}" -var="region=${_REGION}"
        terraform output -raw ingress_gateway_id > /workspace/ingress_gateway_id
        terraform output -raw egress_gateway_id > /workspace/egress_gateway_id
        terraform output -raw egress_gateway_root_certificates > /workspace/agent/agw_root_certs.crt || true

  # Stage 2 (Container Build): Build & push ADK agent container (with Gateway TLS certs) to Artifact Registry
  - name: gcr.io/cloud-builders/docker
    id: build-and-push-agent
    entrypoint: sh
    args:
      - -c
      - |
        docker build -t "${_REGION}-docker.pkg.dev/${PROJECT_ID}/agent-images/demo-agent:${BUILD_ID}" ./agent
        docker push "${_REGION}-docker.pkg.dev/${PROJECT_ID}/agent-images/demo-agent:${BUILD_ID}"

  # Stage 3 (Runtime): Deploy Agent Runtime bound to the Stage 2 image and Stage 1 Gateways
  - name: hashicorp/terraform:1.13
    id: terraform-runtime
    entrypoint: sh
    args:
      - -c
      - |
        cd terraform/runtime
        cat > runtime.auto.tfvars <<EOF
        project_id         = "${PROJECT_ID}"
        project_number     = "${PROJECT_NUMBER}"
        region             = "${_REGION}"
        image_tag          = "${BUILD_ID}"
        ingress_gateway_id = "$(cat /workspace/ingress_gateway_id)"
        egress_gateway_id  = "$(cat /workspace/egress_gateway_id)"
        EOF
        terraform init -reconfigure -backend-config="bucket=${_TF_BUCKET}" -backend-config="prefix=agent-demo/runtime"
        terraform apply -auto-approve

substitutions:
  _REGION: us-central1
  _TF_BUCKET: ${PROJECT_ID}-terraform-state
```

### 2. Reference: Stage 1 Foundation, Gateways & Model Armor (terraform/foundation/main.tf)

```hcl
# REFERENCE ONLY: simplified excerpt of terraform/foundation/main.tf from the demo repo.
# You do not need to copy it. ./deploy.sh applies this file as Stage 1 of cloudbuild.yaml (snippet 1).
#
# To run it manually instead:
#   terraform -chdir=terraform/foundation init -reconfigure #     -backend-config="bucket=${PROJECT_ID}-terraform-state" -backend-config="prefix=agent-demo/foundation"
#   terraform -chdir=terraform/foundation apply -var="project_id=${PROJECT_ID}"
#
# Why separate "foundation" from "runtime"?
#   - Shared networking, Artifact Registry, Agent Gateways, and Model Armor policies change infrequently
#     and must exist BEFORE the agent container is built and deployed.
#   - Outputs ingress_gateway_id, egress_gateway_id, and TLS root certs for Stages 2 & 3.

# 1. Ingress & Egress Agent Gateways (CLIENT_TO_AGENT & AGENT_TO_ANYWHERE)
resource "google_network_services_agent_gateway" "ingress" {
  provider = google-beta
  name     = "agent-demo-ingress"
  location = var.region
  google_managed {
    governed_access_path = "CLIENT_TO_AGENT"
  }
}

resource "google_network_services_agent_gateway" "egress" {
  provider = google-beta
  name     = "agent-demo-egress"
  location = var.region
  google_managed {
    governed_access_path = "AGENT_TO_ANYWHERE"
  }
  network_config {
    egress {
      network_attachment = google_compute_network_attachment.agent_gateway.id
    }
  }
}

# 2. Attach Model Armor Prompt & Response Templates to Ingress Gateway
resource "google_network_services_authz_extension" "ma_extension_ingress" {
  provider        = google-beta
  name            = "agent-demo-ma-extension-ingress"
  location        = var.region
  service         = "modelarmor.${var.region}.rep.googleapis.com"
  timeout         = "3s"
  fail_open       = var.ma_ingress_fail_open
  forward_headers = ["authorization"]
  metadata = {
    model_armor_settings = jsonencode([{
      request_template_id  = google_model_armor_template.agent_security.id
      response_template_id = google_model_armor_template.security_responses.id
    }])
  }
}

resource "google_network_security_authz_policy" "ingress_ma_policy" {
  provider       = google-beta
  name           = "agent-demo-ma-ingress-policy"
  location       = var.region
  action         = "CUSTOM"
  policy_profile = "CONTENT_AUTHZ"
  target {
    resources = [google_network_services_agent_gateway.ingress.id]
  }
  custom_provider {
    authz_extension {
      resources = [google_network_services_authz_extension.ma_extension_ingress.id]
    }
  }
}
```

### 3. Reference: Stage 3 Runtime, Agent & SPIFFE Identity (terraform/runtime/main.tf)

```hcl
# REFERENCE ONLY: simplified excerpt of terraform/runtime/main.tf from the demo repo.
# You do not need to copy it. ./deploy.sh applies this file as Stage 3 of cloudbuild.yaml (snippet 1).
#
# To run it manually instead:
#   terraform -chdir=terraform/runtime init -reconfigure #     -backend-config="bucket=${PROJECT_ID}-terraform-state" -backend-config="prefix=agent-demo/runtime"
#   terraform -chdir=terraform/runtime apply
#
# Expected result:
#   - Deploys "terraform-demo-agent" on Agent Runtime pointing to the container image built in Stage 2
#   - Mints a cryptographic SPIFFE identity (principal://agents.global.org-.../reasoningEngines/...)
#   - Binds least-privilege IAM roles (roles/aiplatform.user, roles/logging.logWriter) to that identity
#   - Wires inbound and outbound traffic through the Stage 1 Ingress and Egress Agent Gateways

# 1. Deploy ADK Agent Runtime with SPIFFE Identity & Agent Gateways
resource "google_vertex_ai_reasoning_engine" "agent" {
  provider     = google-beta
  project      = var.project_id
  region       = var.region
  display_name = "terraform-demo-agent"
  spec {
    agent_framework = "google-adk"
    identity_type   = "AGENT_IDENTITY" # Mints cryptographic SPIFFE ID
    container_spec {
      image_uri = "${var.region}-docker.pkg.dev/${var.project_id}/${var.repository_name}/demo-agent:${var.image_tag}"
    }
    class_methods = jsonencode(local.class_methods)
    deployment_spec {
      agent_gateway_config {
        client_to_agent_config {
          agent_gateway = var.ingress_gateway_id
        }
        agent_to_anywhere_config {
          agent_gateway = var.egress_gateway_id
        }
      }
    }
  }
}

# 2. Grant least-privilege IAM roles to the SPIFFE Agent Identity
resource "google_project_iam_member" "agent_identity_roles" {
  for_each = toset(local.agent_identity_roles)
  project  = var.project_id
  role     = each.value
  member   = "principal://${google_vertex_ai_reasoning_engine.agent.spec[0].effective_identity}"
}
```

## Resources & Links

- **[Day 17 Kata Demo Repository](https://g.dev/cloud/adventofagents-season3-day17-demo)** — End-to-end Terraform and Cloud Build agent deployment demo with single-command deploy.sh.
- **[Provision Agents with Terraform](https://g.dev/cloud/adventofagents-season3-docs-use-terraform)** — Deploy Agent Runtimes, SPIFFE identities, and Agent Gateways with Terraform.
- **[Terraform & Cloud Build GitOps](https://g.dev/cloud/adventofagents-season3-docs-managing-infrastructure-as-code)** — Automate Terraform plan on pull requests and apply on merge in Cloud Build.
- **[Terraform for Agent Platform](https://g.dev/cloud/adventofagents-season3-docs-use-terraform-agent-platform)** — Configure Google Cloud Terraform providers and Vertex AI resources.
