Infrastructure as Code for ChatGPT Apps: Terraform Complete Guide

Managing ChatGPT app infrastructure manually is error-prone, time-consuming, and impossible to scale. As your app portfolio grows from 1 to 50+ apps across multiple environments (dev, staging, production), manual configuration becomes a maintenance nightmare. Infrastructure as Code (IaC) with Terraform solves this by treating infrastructure as version-controlled, reviewable, testable code.

This guide shows you how to build production-ready ChatGPT app infrastructure using Terraform. You'll learn to create reusable modules, manage state across teams, deploy to multiple clouds (AWS, GCP, Azure), and integrate IaC into CI/CD pipelines. Whether you're deploying your first MCP server or managing 100+ ChatGPT apps, Terraform gives you the automation, consistency, and scalability you need.

By the end of this article, you'll have working Terraform configurations for ChatGPT app infrastructure, complete with module design, state management, multi-cloud deployment, and production best practices. If you want to skip the infrastructure complexity entirely, MakeAIHQ.com provides pre-built infrastructure with zero configuration required.

Why Infrastructure as Code for ChatGPT Apps?

Manual infrastructure management fails at scale. Here's why IaC is essential:

Version Control and Collaboration: Infrastructure changes go through Git workflows—pull requests, code reviews, rollbacks. Your entire team can collaborate on infrastructure just like application code, with full audit trails of who changed what and when.

Consistency Across Environments: The same Terraform configuration deploys to dev, staging, and production, eliminating environment drift. No more "works on my machine" or mysterious production-only bugs caused by configuration inconsistencies.

Automated Disaster Recovery: Infrastructure code is documentation that executes. If your entire cloud account is deleted, terraform apply rebuilds everything in minutes. Your disaster recovery plan is literally git clone + terraform apply.

Cost Optimization: Terraform enables infrastructure-as-code reviews where teams catch expensive mistakes before deployment. Use terraform plan to see cost impacts, tag resources for cost allocation, and destroy unused environments with a single command.

Multi-Cloud Portability: ChatGPT apps often need hybrid deployments—databases in AWS, compute in GCP, CDN in Cloudflare. Terraform's provider ecosystem (3,000+ providers) lets you orchestrate resources across any cloud with unified workflows.

Compliance and Security: Infrastructure code enables automated security scanning (checkov, tfsec), policy enforcement (Sentinel, OPA), and compliance validation before deployment. Every change is auditable, reviewable, and testable.

For ChatGPT apps specifically, IaC solves unique challenges: managing hundreds of MCP server deployments, coordinating database migrations across apps, handling OAuth configurations, and orchestrating multi-region deployments for global users.

Learn more about ChatGPT app architecture in our ChatGPT App Development Complete Guide and see how MakeAIHQ's no-code builder handles infrastructure automatically.

Terraform Basics for ChatGPT Infrastructure

Terraform uses HashiCorp Configuration Language (HCL) to declare infrastructure. Let's build a foundation with providers, resources, variables, and outputs.

Providers: Providers are Terraform plugins that interact with cloud APIs (AWS, GCP, Azure). Each provider has resources (EC2 instances, Cloud Functions, databases) and data sources (query existing infrastructure).

Resources: Resources are infrastructure components you want to create—databases, compute instances, DNS records, load balancers. Terraform manages their lifecycle (create, update, delete).

Variables: Variables make configurations reusable across environments. Instead of hardcoding region = "us-east-1", use var.region and override per environment.

Outputs: Outputs expose values from Terraform (database URLs, API endpoints, load balancer IPs) for use by other systems or modules.

Here's a complete foundation configuration:

# terraform.tf - Terraform version and required providers
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.6"
    }
  }

  # Remote state backend (covered in State Management section)
  backend "gcs" {
    bucket = "makeaihq-terraform-state"
    prefix = "chatgpt-apps"
  }
}

# variables.tf - Input variables
variable "environment" {
  description = "Environment name (dev, staging, production)"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Environment must be dev, staging, or production."
  }
}

variable "project_id" {
  description = "GCP project ID"
  type        = string
}

variable "region" {
  description = "Primary deployment region"
  type        = string
  default     = "us-central1"
}

variable "app_configs" {
  description = "ChatGPT app configurations"
  type = map(object({
    name        = string
    runtime     = string
    memory      = number
    min_instances = number
    max_instances = number
    environment_vars = map(string)
  }))
}

variable "database_tier" {
  description = "Cloud SQL tier"
  type        = string
  default     = "db-f1-micro"
}

variable "enable_monitoring" {
  description = "Enable Cloud Monitoring and logging"
  type        = bool
  default     = true
}

variable "allowed_oauth_redirects" {
  description = "Allowed OAuth redirect URLs"
  type        = list(string)
  default     = ["https://chatgpt.com/connector_platform_oauth_redirect"]
}

# outputs.tf - Expose infrastructure values
output "app_endpoints" {
  description = "ChatGPT app API endpoints"
  value = {
    for name, app in google_cloud_run_v2_service.chatgpt_apps :
    name => app.uri
  }
}

output "database_connection_name" {
  description = "Cloud SQL connection name"
  value       = google_sql_database_instance.main.connection_name
}

output "database_private_ip" {
  description = "Cloud SQL private IP"
  value       = google_sql_database_instance.main.private_ip_address
  sensitive   = true
}

output "load_balancer_ip" {
  description = "Global load balancer IP address"
  value       = google_compute_global_address.main.address
}

output "service_accounts" {
  description = "Service account emails for apps"
  value = {
    for name, sa in google_service_account.app_accounts :
    name => sa.email
  }
}

output "environment" {
  description = "Deployed environment"
  value       = var.environment
}

This foundation provides:

  • Multi-provider support (GCP, AWS, utilities)
  • Environment validation (only dev/staging/production allowed)
  • Flexible app configuration (runtime, memory, scaling)
  • Secure outputs (database IPs marked sensitive)
  • Remote state storage (GCS bucket)

For ChatGPT apps, this pattern scales from 1 to 100+ apps by iterating over var.app_configs. Each app gets dedicated resources but shares common infrastructure (databases, load balancers, monitoring).

Explore more deployment patterns in our ChatGPT App Deployment Guide and MCP Server Architecture Best Practices.

Modular Infrastructure Design

Terraform modules are reusable infrastructure components—like functions in programming. Instead of copying 500 lines of HCL for each ChatGPT app, create a module once and reuse it with different inputs.

Module Structure: A module is a directory with Terraform files (main.tf, variables.tf, outputs.tf). Modules can call other modules, creating composable infrastructure.

Module Registry: Publish modules to the Terraform Registry (public) or private registries (Terraform Cloud, GitLab, Artifactory) for organization-wide reuse.

Here's a production-ready ChatGPT app module:

# modules/chatgpt-app/main.tf - Reusable ChatGPT app module
resource "random_id" "app_suffix" {
  byte_length = 4
}

# Service account for the app
resource "google_service_account" "app" {
  account_id   = "${var.app_name}-sa-${random_id.app_suffix.hex}"
  display_name = "Service Account for ${var.app_name}"
  description  = "Managed by Terraform for ChatGPT app: ${var.app_name}"
}

# IAM roles for service account
resource "google_project_iam_member" "app_roles" {
  for_each = toset([
    "roles/cloudsql.client",
    "roles/secretmanager.secretAccessor",
    "roles/logging.logWriter",
    "roles/monitoring.metricWriter"
  ])

  project = var.project_id
  role    = each.value
  member  = "serviceAccount:${google_service_account.app.email}"
}

# Cloud Run service
resource "google_cloud_run_v2_service" "app" {
  name     = "${var.app_name}-${var.environment}"
  location = var.region

  template {
    service_account = google_service_account.app.email

    scaling {
      min_instance_count = var.min_instances
      max_instance_count = var.max_instances
    }

    containers {
      image = var.container_image

      resources {
        limits = {
          cpu    = var.cpu_limit
          memory = "${var.memory_mb}Mi"
        }
      }

      env {
        name  = "ENVIRONMENT"
        value = var.environment
      }

      env {
        name  = "DATABASE_HOST"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.db_host.id
            version = "latest"
          }
        }
      }

      dynamic "env" {
        for_each = var.environment_vars
        content {
          name  = env.key
          value = env.value
        }
      }

      ports {
        container_port = var.port
      }

      startup_probe {
        http_get {
          path = "/health"
          port = var.port
        }
        initial_delay_seconds = 10
        timeout_seconds       = 5
        period_seconds        = 10
        failure_threshold     = 3
      }

      liveness_probe {
        http_get {
          path = "/health"
          port = var.port
        }
        period_seconds    = 30
        timeout_seconds   = 5
        failure_threshold = 3
      }
    }

    vpc_access {
      connector = var.vpc_connector_id
      egress    = "PRIVATE_RANGES_ONLY"
    }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
    percent = 100
  }

  lifecycle {
    ignore_changes = [
      template[0].annotations["run.googleapis.com/client-name"],
      template[0].annotations["run.googleapis.com/client-version"]
    ]
  }
}

# IAM policy for public access (ChatGPT)
resource "google_cloud_run_v2_service_iam_member" "public_access" {
  count = var.allow_public_access ? 1 : 0

  location = google_cloud_run_v2_service.app.location
  name     = google_cloud_run_v2_service.app.name
  role     = "roles/run.invoker"
  member   = "allUsers"
}

# Secret for database host
resource "google_secret_manager_secret" "db_host" {
  secret_id = "${var.app_name}-db-host-${var.environment}"

  replication {
    auto {}
  }
}

resource "google_secret_manager_secret_version" "db_host" {
  secret      = google_secret_manager_secret.db_host.id
  secret_data = var.database_host
}

# Cloud Monitoring alert for high error rate
resource "google_monitoring_alert_policy" "high_error_rate" {
  count        = var.enable_monitoring ? 1 : 0
  display_name = "${var.app_name} High Error Rate"
  combiner     = "OR"

  conditions {
    display_name = "Error rate > 5%"

    condition_threshold {
      filter          = "resource.type=\"cloud_run_revision\" AND resource.labels.service_name=\"${google_cloud_run_v2_service.app.name}\" AND metric.type=\"run.googleapis.com/request_count\" AND metric.labels.response_code_class=\"5xx\""
      duration        = "60s"
      comparison      = "COMPARISON_GT"
      threshold_value = 5

      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "ALIGN_RATE"
      }
    }
  }

  notification_channels = var.notification_channels
}

# modules/chatgpt-app/variables.tf
variable "app_name" {
  description = "ChatGPT app name (alphanumeric, hyphens)"
  type        = string
  validation {
    condition     = can(regex("^[a-z0-9-]+$", var.app_name))
    error_message = "App name must be lowercase alphanumeric with hyphens."
  }
}

variable "environment" {
  description = "Environment (dev, staging, production)"
  type        = string
}

variable "project_id" {
  description = "GCP project ID"
  type        = string
}

variable "region" {
  description = "GCP region"
  type        = string
}

variable "container_image" {
  description = "Container image URL"
  type        = string
}

variable "memory_mb" {
  description = "Memory in MB"
  type        = number
  default     = 512
}

variable "cpu_limit" {
  description = "CPU limit (cores)"
  type        = string
  default     = "1"
}

variable "min_instances" {
  description = "Minimum instances"
  type        = number
  default     = 0
}

variable "max_instances" {
  description = "Maximum instances"
  type        = number
  default     = 10
}

variable "port" {
  description = "Container port"
  type        = number
  default     = 8080
}

variable "environment_vars" {
  description = "Environment variables"
  type        = map(string)
  default     = {}
}

variable "database_host" {
  description = "Database host (stored in Secret Manager)"
  type        = string
}

variable "vpc_connector_id" {
  description = "VPC connector ID for private networking"
  type        = string
}

variable "allow_public_access" {
  description = "Allow public (unauthenticated) access"
  type        = bool
  default     = true
}

variable "enable_monitoring" {
  description = "Enable monitoring and alerting"
  type        = bool
  default     = true
}

variable "notification_channels" {
  description = "Notification channel IDs for alerts"
  type        = list(string)
  default     = []
}

# modules/chatgpt-app/outputs.tf
output "service_url" {
  description = "Cloud Run service URL"
  value       = google_cloud_run_v2_service.app.uri
}

output "service_name" {
  description = "Cloud Run service name"
  value       = google_cloud_run_v2_service.app.name
}

output "service_account_email" {
  description = "Service account email"
  value       = google_service_account.app.email
}

output "secret_ids" {
  description = "Secret Manager secret IDs"
  value = {
    db_host = google_secret_manager_secret.db_host.secret_id
  }
}

Use this module in your root configuration:

# main.tf - Root configuration using module
module "fitness_chatgpt_app" {
  source = "./modules/chatgpt-app"

  app_name        = "fitness-booking-assistant"
  environment     = var.environment
  project_id      = var.project_id
  region          = var.region
  container_image = "gcr.io/${var.project_id}/fitness-app:latest"

  memory_mb      = 1024
  cpu_limit      = "2"
  min_instances  = 1
  max_instances  = 50

  database_host      = module.database.connection_name
  vpc_connector_id   = module.networking.vpc_connector_id

  environment_vars = {
    OAUTH_CLIENT_ID     = var.oauth_client_id
    OAUTH_REDIRECT_URI  = "https://chatgpt.com/connector_platform_oauth_redirect"
    APP_VERSION         = "1.0.0"
  }

  notification_channels = [google_monitoring_notification_channel.email.id]
}

This modular approach provides:

  • Reusability: Same module for 100+ apps with different inputs
  • Consistency: Every app follows the same infrastructure pattern
  • Maintainability: Update module once, all apps inherit changes
  • Testability: Test module in isolation before production use

Learn about module testing in our Terraform Testing Guide and see MakeAIHQ's infrastructure patterns for production examples.

State Management Best Practices

Terraform state is the source of truth mapping your infrastructure code to real-world resources. Improper state management causes data loss, conflicts, and outages. Here's how to manage state safely in team environments.

Remote Backends: Never store state locally (terraform.tfstate files). Use remote backends (GCS, S3, Azure Blob Storage, Terraform Cloud) for durability, versioning, and team collaboration.

State Locking: Prevent concurrent modifications by enabling state locking. GCS and S3 backends support automatic locking—if Alice runs terraform apply, Bob's concurrent apply fails with a lock error.

Workspaces: Terraform workspaces create separate state files for environments (dev, staging, production) within the same backend. Use workspaces for environment isolation without duplicating infrastructure code.

Here's a complete state management setup:

# backend.tf - GCS backend with state locking
terraform {
  backend "gcs" {
    bucket = "makeaihq-terraform-state-prod"
    prefix = "chatgpt-apps"

    # State locking via GCS native locking
    # No additional configuration needed
  }
}

# Alternative: S3 backend with DynamoDB locking
# terraform {
#   backend "s3" {
#     bucket         = "makeaihq-terraform-state"
#     key            = "chatgpt-apps/terraform.tfstate"
#     region         = "us-east-1"
#     encrypt        = true
#
#     # State locking via DynamoDB
#     dynamodb_table = "terraform-state-lock"
#   }
# }

# Setup script for GCS backend
# setup-backend.sh
#!/bin/bash
set -euo pipefail

PROJECT_ID="${1:-makeaihq-prod}"
BUCKET_NAME="makeaihq-terraform-state-prod"
REGION="us-central1"

echo "Setting up Terraform state backend..."

# Create GCS bucket for state
gcloud storage buckets create "gs://${BUCKET_NAME}" \
  --project="${PROJECT_ID}" \
  --location="${REGION}" \
  --uniform-bucket-level-access

# Enable versioning (state file history)
gcloud storage buckets update "gs://${BUCKET_NAME}" \
  --versioning

# Set lifecycle policy (delete versions older than 90 days)
cat > lifecycle.json <<EOF
{
  "lifecycle": {
    "rule": [
      {
        "action": {
          "type": "Delete"
        },
        "condition": {
          "numNewerVersions": 10,
          "daysSinceNoncurrentTime": 90
        }
      }
    ]
  }
}
EOF

gcloud storage buckets update "gs://${BUCKET_NAME}" \
  --lifecycle-file=lifecycle.json

# Restrict access (only Terraform service account)
gcloud storage buckets add-iam-policy-binding "gs://${BUCKET_NAME}" \
  --member="serviceAccount:terraform@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

echo "Backend configured: gs://${BUCKET_NAME}"
echo "Add this to your terraform block:"
echo "  backend \"gcs\" {"
echo "    bucket = \"${BUCKET_NAME}\""
echo "    prefix = \"chatgpt-apps\""
echo "  }"

# Workspace management script
# manage-workspaces.sh
#!/bin/bash
set -euo pipefail

ACTION="${1:-list}"
WORKSPACE="${2:-}"

case "${ACTION}" in
  list)
    echo "Available workspaces:"
    terraform workspace list
    ;;

  create)
    if [[ -z "${WORKSPACE}" ]]; then
      echo "Usage: $0 create WORKSPACE_NAME"
      exit 1
    fi

    echo "Creating workspace: ${WORKSPACE}"
    terraform workspace new "${WORKSPACE}"
    ;;

  select)
    if [[ -z "${WORKSPACE}" ]]; then
      echo "Usage: $0 select WORKSPACE_NAME"
      exit 1
    fi

    echo "Switching to workspace: ${WORKSPACE}"
    terraform workspace select "${WORKSPACE}"
    ;;

  delete)
    if [[ -z "${WORKSPACE}" ]]; then
      echo "Usage: $0 delete WORKSPACE_NAME"
      exit 1
    fi

    # Safety: prevent deleting production
    if [[ "${WORKSPACE}" == "production" ]]; then
      echo "ERROR: Cannot delete production workspace"
      exit 1
    fi

    echo "Destroying resources in workspace: ${WORKSPACE}"
    terraform workspace select "${WORKSPACE}"
    terraform destroy -auto-approve

    terraform workspace select default
    terraform workspace delete "${WORKSPACE}"
    ;;

  show)
    echo "Current workspace: $(terraform workspace show)"
    echo "State file: $(terraform state pull | jq -r '.terraform_version')"
    echo "Resources: $(terraform state list | wc -l)"
    ;;

  *)
    echo "Usage: $0 {list|create|select|delete|show} [WORKSPACE_NAME]"
    exit 1
    ;;
esac

State Migration: When switching backends or restructuring infrastructure, migrate state safely:

#!/bin/bash
# migrate-state.sh - Migrate state from local to GCS

set -euo pipefail

BACKUP_DIR="./state-backups/$(date +%Y%m%d-%H%M%S)"
mkdir -p "${BACKUP_DIR}"

echo "Step 1: Backing up current state..."
terraform state pull > "${BACKUP_DIR}/terraform.tfstate"

echo "Step 2: Configuring new backend in terraform block..."
cat > backend-migration.tf <<EOF
terraform {
  backend "gcs" {
    bucket = "makeaihq-terraform-state-prod"
    prefix = "chatgpt-apps"
  }
}
EOF

echo "Step 3: Initializing with backend migration..."
terraform init -migrate-state

echo "Step 4: Verifying migration..."
NEW_STATE=$(terraform state pull)
OLD_STATE=$(cat "${BACKUP_DIR}/terraform.tfstate")

if diff <(echo "${NEW_STATE}" | jq -S .) <(echo "${OLD_STATE}" | jq -S .) > /dev/null; then
  echo "✅ State migration successful (states identical)"
else
  echo "⚠️  WARNING: State differs after migration. Review carefully."
fi

echo "Backup saved to: ${BACKUP_DIR}"

State Locking Troubleshooting:

# Force unlock if apply crashes (use with caution)
terraform force-unlock LOCK_ID

# Check current lock status
terraform state pull | jq '.lineage, .serial'

For ChatGPT apps with 50+ deployments, workspaces prevent state file conflicts when multiple teams deploy simultaneously. Backend versioning provides rollback capability if a bad deployment corrupts state.

Explore state management patterns in our Terraform State Management Guide and multi-environment deployment strategies.

Multi-Cloud Deployment Strategies

ChatGPT apps often require multi-cloud deployments—databases in AWS RDS, compute in GCP Cloud Run, CDN in Cloudflare, monitoring in Datadog. Terraform's provider ecosystem handles cross-cloud orchestration seamlessly.

Provider Configuration: Terraform supports 3,000+ providers. Configure multiple providers in a single configuration:

# providers.tf - Multi-cloud provider configuration
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
    datadog = {
      source  = "datadog/datadog"
      version = "~> 3.0"
    }
  }
}

# GCP provider
provider "google" {
  project = var.gcp_project_id
  region  = var.gcp_region
}

# AWS provider
provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Environment = var.environment
      ManagedBy   = "Terraform"
      Project     = "ChatGPT-Apps"
    }
  }
}

# Cloudflare provider
provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

# Datadog provider
provider "datadog" {
  api_key = var.datadog_api_key
  app_key = var.datadog_app_key
}

Hybrid Deployment Example: GCP Cloud Run + AWS RDS + Cloudflare CDN

# main-hybrid.tf - Multi-cloud ChatGPT app infrastructure

# AWS RDS PostgreSQL database
resource "aws_db_instance" "chatgpt_db" {
  identifier           = "chatgpt-apps-${var.environment}"
  engine               = "postgres"
  engine_version       = "15.4"
  instance_class       = var.environment == "production" ? "db.t3.medium" : "db.t3.micro"
  allocated_storage    = 100
  storage_encrypted    = true
  storage_type         = "gp3"

  db_name  = "chatgpt_apps"
  username = "chatgpt_admin"
  password = random_password.db_password.result

  vpc_security_group_ids = [aws_security_group.db.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name

  backup_retention_period = 7
  backup_window          = "03:00-04:00"
  maintenance_window     = "Mon:04:00-Mon:05:00"

  enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]

  deletion_protection = var.environment == "production" ? true : false
  skip_final_snapshot = var.environment != "production"

  tags = {
    Name = "ChatGPT Apps Database"
  }
}

resource "random_password" "db_password" {
  length  = 32
  special = true
}

# Store password in AWS Secrets Manager
resource "aws_secretsmanager_secret" "db_password" {
  name = "chatgpt-apps-db-password-${var.environment}"
}

resource "aws_secretsmanager_secret_version" "db_password" {
  secret_id     = aws_secretsmanager_secret.db_password.id
  secret_string = random_password.db_password.result
}

# VPC and networking for RDS
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "ChatGPT Apps VPC"
  }
}

resource "aws_subnet" "private" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "ChatGPT Apps Private Subnet ${count.index + 1}"
  }
}

resource "aws_db_subnet_group" "main" {
  name       = "chatgpt-apps-${var.environment}"
  subnet_ids = aws_subnet.private[*].id
}

resource "aws_security_group" "db" {
  name        = "chatgpt-apps-db-${var.environment}"
  description = "Security group for ChatGPT apps database"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "PostgreSQL from GCP Cloud Run"
    from_port   = 5432
    to_port     = 5432
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # In production, restrict to GCP NAT IPs
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

data "aws_availability_zones" "available" {
  state = "available"
}

# GCP Cloud Run service (connects to AWS RDS)
resource "google_cloud_run_v2_service" "app" {
  name     = "chatgpt-app-${var.environment}"
  location = var.gcp_region

  template {
    containers {
      image = var.container_image

      env {
        name  = "DATABASE_URL"
        value = "postgresql://${aws_db_instance.chatgpt_db.username}:${random_password.db_password.result}@${aws_db_instance.chatgpt_db.endpoint}/${aws_db_instance.chatgpt_db.db_name}"
      }

      env {
        name  = "CLOUDFLARE_CDN_URL"
        value = "https://${cloudflare_record.cdn.hostname}"
      }
    }

    scaling {
      min_instance_count = 1
      max_instance_count = 100
    }
  }
}

# Cloudflare DNS and CDN
resource "cloudflare_record" "cdn" {
  zone_id = var.cloudflare_zone_id
  name    = "chatgpt-${var.environment}"
  type    = "CNAME"
  value   = google_cloud_run_v2_service.app.uri
  proxied = true # Enable Cloudflare CDN
}

resource "cloudflare_page_rule" "cache" {
  zone_id = var.cloudflare_zone_id
  target  = "chatgpt-${var.environment}.makeaihq.com/*"

  actions {
    cache_level = "cache_everything"
    edge_cache_ttl = 7200
  }
}

# Datadog monitoring
resource "datadog_monitor" "app_health" {
  name    = "ChatGPT App Health - ${var.environment}"
  type    = "metric alert"
  message = "ChatGPT app is unhealthy. @slack-alerts @pagerduty"

  query = "avg(last_5m):avg:gcp.run.request.count{service:chatgpt-app-${var.environment}} by {response_code_class}.as_count() > 100"

  monitor_thresholds {
    critical = 100
    warning  = 50
  }

  notify_no_data    = true
  no_data_timeframe = 10

  tags = ["environment:${var.environment}", "service:chatgpt-app"]
}

# Outputs for multi-cloud infrastructure
output "database_endpoint" {
  description = "AWS RDS endpoint"
  value       = aws_db_instance.chatgpt_db.endpoint
  sensitive   = true
}

output "app_url" {
  description = "GCP Cloud Run URL"
  value       = google_cloud_run_v2_service.app.uri
}

output "cdn_url" {
  description = "Cloudflare CDN URL"
  value       = "https://${cloudflare_record.cdn.hostname}"
}

output "datadog_monitor_id" {
  description = "Datadog monitor ID"
  value       = datadog_monitor.app_health.id
}

This multi-cloud setup provides:

  • Best-of-breed services: AWS RDS for databases (mature, reliable), GCP Cloud Run for serverless compute (fast, cheap), Cloudflare for CDN (global edge network)
  • Vendor independence: Avoid lock-in by spreading infrastructure across clouds
  • Disaster recovery: If one cloud has an outage, fail over to backups in another cloud
  • Cost optimization: Use cheapest services per cloud (GCP storage, AWS compute, etc.)

For ChatGPT apps, multi-cloud is common: OAuth secrets in AWS Secrets Manager, MCP servers in GCP Cloud Functions, widget assets on Cloudflare CDN, monitoring in Datadog.

Learn more about multi-cloud patterns in our Multi-Cloud ChatGPT Architecture Guide and cost optimization strategies.

Production Best Practices

Production Terraform requires testing, security scanning, CI/CD integration, and drift detection. Here are battle-tested practices:

Testing with Terratest:

// test/chatgpt_app_test.go - Terratest integration test
package test

import (
    "testing"
    "time"

    "github.com/gruntwork-io/terratest/modules/gcp"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestChatGPTAppInfrastructure(t *testing.T) {
    t.Parallel()

    projectID := "makeaihq-test"
    region := "us-central1"

    terraformOptions := &terraform.Options{
        TerraformDir: "../",
        Vars: map[string]interface{}{
            "project_id":  projectID,
            "region":      region,
            "environment": "test",
            "app_configs": map[string]interface{}{
                "test-app": map[string]interface{}{
                    "name":          "test-app",
                    "runtime":       "nodejs20",
                    "memory":        512,
                    "min_instances": 0,
                    "max_instances": 10,
                },
            },
        },
    }

    defer terraform.Destroy(t, terraformOptions)

    terraform.InitAndApply(t, terraformOptions)

    // Test Cloud Run service exists
    serviceName := terraform.Output(t, terraformOptions, "app_endpoints")
    assert.Contains(t, serviceName, "test-app")

    // Test service is healthy
    serviceURL := terraform.Output(t, terraformOptions, "app_endpoints")
    status := gcp.GetCloudRunServiceStatus(t, projectID, region, "test-app-test")
    assert.Equal(t, "READY", status)

    // Test service responds to HTTP requests
    time.Sleep(10 * time.Second) // Wait for service to stabilize
    // Add HTTP health check here
}

CI/CD Pipeline (GitHub Actions):

# .github/workflows/terraform.yml - Terraform CI/CD pipeline
name: Terraform

on:
  pull_request:
    branches: [main]
    paths:
      - '**.tf'
      - '.github/workflows/terraform.yml'
  push:
    branches: [main]
    paths:
      - '**.tf'

env:
  TF_VERSION: 1.6.0
  TF_WORKSPACE: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}

jobs:
  validate:
    name: Validate
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: Terraform Format Check
        run: terraform fmt -check -recursive

      - name: Terraform Init
        run: terraform init -backend=false

      - name: Terraform Validate
        run: terraform validate

  security:
    name: Security Scan
    runs-on: ubuntu-latest
    needs: validate

    steps:
      - uses: actions/checkout@v4

      - name: tfsec
        uses: aquasecurity/tfsec-action@v1.0.3
        with:
          soft_fail: false

      - name: Checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: .
          framework: terraform
          soft_fail: false

  plan:
    name: Plan
    runs-on: ubuntu-latest
    needs: [validate, security]

    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Terraform Init
        run: terraform init

      - name: Terraform Workspace
        run: |
          terraform workspace select ${{ env.TF_WORKSPACE }} || terraform workspace new ${{ env.TF_WORKSPACE }}

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color -out=tfplan

      - name: Comment PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const output = `#### Terraform Plan 📖

            <details><summary>Show Plan</summary>

            \`\`\`
            ${{ steps.plan.outputs.stdout }}
            \`\`\`

            </details>`;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            });

      - name: Upload Plan
        uses: actions/upload-artifact@v4
        with:
          name: tfplan
          path: tfplan

  apply:
    name: Apply
    runs-on: ubuntu-latest
    needs: plan
    if: github.ref == 'refs/heads/main'
    environment: production

    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Terraform Init
        run: terraform init

      - name: Terraform Workspace
        run: terraform workspace select production

      - name: Download Plan
        uses: actions/download-artifact@v4
        with:
          name: tfplan

      - name: Terraform Apply
        run: terraform apply -auto-approve tfplan

Drift Detection:

#!/bin/bash
# drift-detection.sh - Detect infrastructure drift

set -euo pipefail

WORKSPACE="${1:-production}"
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL}"

echo "Checking for infrastructure drift in workspace: ${WORKSPACE}"

terraform workspace select "${WORKSPACE}"

# Run plan and capture output
PLAN_OUTPUT=$(terraform plan -detailed-exitcode -no-color 2>&1) || PLAN_EXIT=$?

case "${PLAN_EXIT:-0}" in
  0)
    echo "✅ No drift detected - infrastructure matches state"
    ;;
  1)
    echo "❌ Terraform plan error:"
    echo "${PLAN_OUTPUT}"
    exit 1
    ;;
  2)
    echo "⚠️  DRIFT DETECTED - infrastructure differs from state"
    echo "${PLAN_OUTPUT}"

    # Send Slack alert
    curl -X POST "${SLACK_WEBHOOK}" \
      -H 'Content-Type: application/json' \
      -d @- <<EOF
{
  "text": "⚠️ Terraform Drift Detected in ${WORKSPACE}",
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Infrastructure drift detected in workspace: ${WORKSPACE}*\n\nInfrastructure state differs from Terraform configuration. Review and apply changes."
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "\`\`\`${PLAN_OUTPUT:0:2000}\`\`\`"
      }
    }
  ]
}
EOF
    ;;
esac

Secret Management:

# secrets.tf - Secure secret management
resource "google_secret_manager_secret" "oauth_client_secret" {
  secret_id = "oauth-client-secret-${var.environment}"

  replication {
    auto {}
  }
}

# Import existing secret (don't store in Terraform)
# terraform import google_secret_manager_secret.oauth_client_secret projects/PROJECT_ID/secrets/oauth-client-secret-production

# Reference secret in Cloud Run
resource "google_cloud_run_v2_service" "app" {
  # ... other config ...

  template {
    containers {
      env {
        name = "OAUTH_CLIENT_SECRET"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.oauth_client_secret.id
            version = "latest"
          }
        }
      }
    }
  }
}

These practices ensure:

  • Safety: Validate and test changes before production
  • Security: Automated scanning catches vulnerabilities
  • Visibility: Drift detection alerts on manual changes
  • Auditability: CI/CD logs show who changed what and when

Learn more in our Terraform Testing Guide, security best practices, and CI/CD patterns.

For official Terraform documentation, see:

Conclusion

Infrastructure as Code with Terraform transforms ChatGPT app deployment from manual, error-prone processes to automated, testable, version-controlled workflows. You now have production-ready Terraform configurations for modular infrastructure, state management, multi-cloud deployments, and CI/CD integration.

Start small: deploy a single ChatGPT app with Terraform, validate it works, then expand to modules and multi-cloud. Use workspaces for environment separation, remote backends for team collaboration, and automated testing for safety. Within weeks, you'll deploy 50+ apps with confidence using terraform apply.

Terraform's learning curve is steep, but the ROI is massive: infrastructure changes go through code review, deployments are reproducible, disaster recovery is automated, and teams collaborate without conflicts.

Ready to deploy ChatGPT apps without infrastructure complexity? MakeAIHQ.com provides pre-built, production-ready infrastructure with zero Terraform configuration required. From zero to ChatGPT App Store in 48 hours—start your free trial today.

Explore related guides: