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:
- ChatGPT App Deployment Complete Guide
- MCP Server Architecture Best Practices
- Multi-Cloud ChatGPT Infrastructure
- Terraform State Management for Teams
- Automated Testing for Terraform
- ChatGPT App Cost Optimization
- Infrastructure Security Best Practices
- CI/CD Pipelines for ChatGPT Apps
- Kubernetes vs Serverless for ChatGPT Apps
- MakeAIHQ Features