A Terraform module for deploying the Hrafnar AI application on Google Cloud Platform with Cloud Run, Cloud SQL PostgreSQL, and optional Cloudflare DNS integration.
- 🚀 Cloud Run Deployment: Scalable serverless deployment for the Hrafnar application
- 🔐 Secure Secret Management: AI API keys and database credentials stored in Google Secret Manager
- 🗄️ Managed PostgreSQL: Cloud SQL PostgreSQL with automated backups and private networking
- 📡 Cloudflare Integration: Optional DNS management with automatic TLS certificates
- 🔧 MCP Server Support: Integration with Model Context Protocol servers
- 🛡️ VPC Security: Private networking with Cloud NAT for outbound connectivity
- 📊 Monitoring Ready: Built-in support for Google Cloud Monitoring and Logging
The module deploys:
- Hrafnar Application: Main Python/HTMX application on Cloud Run
- PostgreSQL Database: Private Cloud SQL instance with automated backups
- VPC Network: Private subnet with Cloud NAT for secure networking
- Secret Manager: Secure storage for API keys and database credentials
- DNS Records (optional): Cloudflare-managed DNS with automatic TLS
module "hrafnar_deploy" {
source = "openteams-ai/hrafnar-gcp-deploy/gcp"
project_id = "my-gcp-project"
name_prefix = "prod-hrafnar"
app_image = "gcr.io/my-project/hrafnar:latest"
ai_api_keys = {
OPENAI_API_KEY = "sk-..."
}
}.
├── .github/
│ └── workflows/
│ └── terraform.yml # CI/CD pipeline
├── examples/ # Usage examples
│ ├── dev/ # Development environment
│ └── prod/ # Production environment
├── test/ # Terratest suite
├── cloud-run.tf # Hrafnar application deployment
├── database.tf # Cloud SQL PostgreSQL
├── dns.tf # Cloudflare DNS records
├── iam.tf # Service accounts and permissions
├── locals.tf # Local values and computed resources
├── networking.tf # VPC, subnets, and Cloud NAT
├── outputs.tf # Module outputs
├── secrets.tf # Secret Manager configuration
├── variables.tf # Input variables
├── versions.tf # Provider version constraints
└── README.md # This documentation
The following section contains auto-generated documentation for this Terraform module using terraform-docs:
module "hrafnar_gcp_deploy" {
source = "openteams-ai/hrafnar-gcp-deploy/gcp"
version = "~> 1.0"
project_id = "my-gcp-project"
region = "us-central1"
name_prefix = "acme-hrafnar"
# Hrafnar application configuration
app_image = "gcr.io/my-project/hrafnar:latest"
# AI API keys (stored securely in Secret Manager)
ai_api_keys = {
OPENAI_API_KEY = "sk-..."
ANTHROPIC_API_KEY = "sk-ant-..."
}
}module "hrafnar_gcp_deploy" {
source = "openteams-ai/hrafnar-gcp-deploy/gcp"
version = "~> 1.0"
project_id = "my-gcp-project"
region = "us-central1"
name_prefix = "acme-hrafnar"
# Hrafnar application
app_image = "gcr.io/my-project/hrafnar:latest"
# Optional React frontend
enable_react_frontend = true
react_image = "gcr.io/my-project/hrafnar-ui:latest"
# Cloudflare DNS configuration
enable_cloudflare_dns = true
cloudflare_api_token = "your-cloudflare-token"
cloudflare_zone_id = "your-zone-id"
base_domain = "example.com"
api_subdomain = "api"
ui_subdomain = "app"
# AI configuration
ai_api_keys = {
OPENAI_API_KEY = "sk-..."
ANTHROPIC_API_KEY = "sk-ant-..."
}
# MCP servers
mcp_servers = {
filesystem = {
url = "https://mcp-fs.example.com"
description = "Filesystem MCP server"
}
}
}| Name | Version |
|---|---|
| terraform | >= 1.5 |
| cloudflare | ~> 5.0 |
| ~> 7.0 | |
| random | ~> 3.0 |
| Name | Version |
|---|---|
| cloudflare | 5.9.0 |
| 7.1.0 | |
| random | 3.7.2 |
No modules.
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| ai_api_keys | Map of AI API keys where key is the environment variable name (e.g., OPENAI_API_KEY, ANTHROPIC_API_KEY) and value is the actual API key (stored in Secret Manager) | map(string) |
{} |
no |
| app_command | Command to run the container | list(string) |
[ |
no |
| app_config_files | Configuration files to mount as volumes from Secret Manager. Key is the config name, value contains file content and mount path. | map(object({ |
{} |
no |
| app_cpu | CPU allocation for the hrafnar application | string |
"1000m" |
no |
| app_env_vars | Environment variables for the hrafnar application | map(string) |
{} |
no |
| app_image | Container image for the hrafnar application (without tag) | string |
n/a | yes |
| app_image_sha | Container image SHA (takes precedence over tag if provided) | string |
"" |
no |
| app_image_tag | Container image tag | string |
"latest" |
no |
| app_max_instances | Maximum number of instances for the hrafnar application | number |
10 |
no |
| app_memory | Memory allocation for the hrafnar application | string |
"512Mi" |
no |
| app_min_instances | Minimum number of instances for the hrafnar application | number |
0 |
no |
| app_port | Port the application listens on | number |
8080 |
no |
| base_domain | Base domain name managed by Cloudflare (e.g., 'example.com'). A subdomain will be created under this domain for application access | string |
"" |
no |
| cloudflare_zone_id | Cloudflare zone ID for DNS records (required if enable_cloudflare_dns is true) | string |
"" |
no |
| database_backup_enabled | Enable automated database backups | bool |
true |
no |
| database_backup_retention_days | Number of days to retain database backups | number |
7 |
no |
| database_disk_autoresize_limit | Maximum disk size in GB for database autoresize | number |
100 |
no |
| database_disk_size | Database disk size in GB | number |
20 |
no |
| database_log_retention_days | Number of days to retain database transaction logs | number |
7 |
no |
| database_ssl_mode | SSL mode for database connections | string |
"ENCRYPTED_ONLY" |
no |
| database_tier | Database instance tier | string |
"db-f1-micro" |
no |
| enable_artifact_registry | Enable Artifact Registry remote repository for quay.io | bool |
false |
no |
| enable_cloudflare_dns | Enable Cloudflare DNS management | bool |
false |
no |
| enable_database | Enable Cloud SQL database deployment | bool |
true |
no |
| enable_monitoring | Enable Google Cloud Monitoring and Logging | bool |
true |
no |
| enable_nat_gateway | Enable Cloud NAT for outbound internet access | bool |
true |
no |
| enable_storage | Enable Cloud Storage bucket for the application | bool |
false |
no |
| enable_valkey | Enable Google Cloud Memorystore for Redis (Valkey-compatible) deployment | bool |
false |
no |
| hrafnar_subdomain | Subdomain for hrafnar application access (e.g., 'hrafnar' for hrafnar.example.com) | string |
"hrafnar" |
no |
| labels | Labels to apply to all resources | map(string) |
{} |
no |
| log_level | Log level for applications | string |
"INFO" |
no |
| mcp_servers | MCP server configurations | map(object({ |
{} |
no |
| name_prefix | Prefix for resource naming | string |
n/a | yes |
| private_subnet_cidr | CIDR block for the private subnet | string |
"10.0.0.0/24" |
no |
| project_id | The GCP project ID where resources will be created | string |
n/a | yes |
| region | The GCP region for resources | string |
"us-central1" |
no |
| storage_app_role | IAM role for the application to access the storage bucket | string |
"roles/storage.objectAdmin" |
no |
| storage_cors_config | CORS configuration for the storage bucket | list(object({ |
[ |
no |
| storage_create_hmac_key | Create HMAC key for S3-compatible access to the storage bucket | bool |
false |
no |
| storage_dev_access_members | List of IAM members to grant development access to the storage bucket (e.g., 'user:[email protected]') | list(string) |
[] |
no |
| storage_dev_role | IAM role for development access to the storage bucket | string |
"roles/storage.objectAdmin" |
no |
| storage_enable_dev_access | Enable external access to the storage bucket for development | bool |
false |
no |
| storage_folders | List of folders to create in the storage bucket | list(string) |
[ |
no |
| storage_force_destroy | Force destroy the storage bucket even if it contains objects | bool |
false |
no |
| storage_lifecycle_rules | Lifecycle rules for the storage bucket | list(object({ |
[ |
no |
| storage_public_access_prevention | Public access prevention setting for the storage bucket | string |
"enforced" |
no |
| storage_versioning_enabled | Enable versioning for the storage bucket | bool |
true |
no |
| valkey_auth_enabled | Whether AUTH is enabled for the Redis instance | bool |
true |
no |
| valkey_maintenance_policy | Maintenance policy for Redis instance | object({ |
{ |
no |
| valkey_memory_size_gb | Redis instance memory size in GB | number |
1 |
no |
| valkey_redis_configs | Redis configuration parameters | map(string) |
{ |
no |
| valkey_redis_version | Redis version for the instance | string |
"REDIS_7_0" |
no |
| valkey_tier | Service tier of the Redis instance (BASIC or STANDARD_HA) | string |
"BASIC" |
no |
| valkey_transit_encryption_mode | TLS mode for Redis instance | string |
"SERVER_AUTHENTICATION" |
no |
| vpc_cidr | CIDR block for the VPC | string |
"10.0.0.0/16" |
no |
| Name | Description |
|---|---|
| ai_api_key_secret_names | Names of the Secret Manager secrets for AI API keys |
| app_domain | Full domain name for application access |
| common_labels | Common labels applied to all resources |
| database_connection_name | Connection name for the Cloud SQL database instance |
| database_connection_secret_name | Name of the Secret Manager secret for database connection string |
| database_instance_name | Name of the Cloud SQL database instance |
| database_password_secret_name | Name of the Secret Manager secret for database password |
| database_private_ip | Private IP address of the Cloud SQL database instance |
| hrafnar_app_service_account_email | Email of the hrafnar application service account |
| hrafnar_app_service_name | Name of the hrafnar Cloud Run service |
| hrafnar_app_url | URL of the hrafnar application |
| private_subnet_id | ID of the private subnet |
| private_subnet_name | Name of the private subnet |
| resource_prefix | Prefix used for naming resources |
| storage_bucket_name | Name of the Cloud Storage bucket |
| storage_bucket_self_link | Self link of the Cloud Storage bucket |
| storage_bucket_url | URL of the Cloud Storage bucket |
| storage_hmac_access_id_secret_name | Name of the Secret Manager secret containing the storage HMAC access ID |
| storage_hmac_secret_secret_name | Name of the Secret Manager secret containing the storage HMAC secret key |
| valkey_auth_secret_name | Name of the Secret Manager secret containing the Valkey auth string |
| valkey_connection_secret_name | Name of the Secret Manager secret containing the Valkey connection URL |
| valkey_host | Host IP of the Valkey/Redis instance |
| valkey_instance_name | Name of the Valkey/Redis instance |
| valkey_memory_size_gb | Memory size of the Valkey/Redis instance in GB |
| valkey_port | Port of the Valkey/Redis instance |
| valkey_tier | Service tier of the Valkey/Redis instance |
| vpc_id | ID of the VPC network |
| vpc_name | Name of the VPC network |
This README uses terraform-docs to automatically generate and maintain module documentation. The content between <!-- BEGIN_TF_DOCS --> and <!-- END_TF_DOCS --> is automatically generated.
- Auto-generate: Run
make docsto update the terraform-docs section - Manual content: Edit sections outside the terraform-docs markers
- Configuration: Modify
.terraform-docs.ymlto customize the generated content
- The
make docscommand uses Docker to run terraform-docs - It reads your Terraform files (main.tf, variables.tf, outputs.tf, etc.)
- Generates documentation in Markdown format
- Injects the content between the
<!-- BEGIN_TF_DOCS -->and<!-- END_TF_DOCS -->markers - Preserves all custom content outside these markers
Important: Never manually edit content between the terraform-docs markers as it will be overwritten.
See the examples/ directory for complete usage examples:
- Development: Minimal configuration for development environments
- Production: Full-featured production deployment with React frontend and Cloudflare DNS
- Secret Management: All sensitive data (API keys, database passwords) is stored in Google Secret Manager
- Network Security: Database runs in a private subnet with no public IP
- Access Control: Fine-grained IAM permissions for service accounts
- TLS Encryption: Database connections require TLS, Cloudflare provides automatic HTTPS
The module includes comprehensive tests using Terratest:
- Hrafnar Module Functionality: Tests variable validation and core functionality
- Cloudflare Integration: Validates DNS configuration when enabled
- GCP Resources: Tests Cloud Run, Cloud SQL, and networking components
- Configuration Scenarios: Tests minimal and full-featured deployments
# Run validation tests (no cloud resources)
cd test && go test -v -run TestTerraformValidation
cd test && go test -v -run TestExamplesValidation
cd test && go test -v -run TestHrafnarModuleFunctionalityIntegration tests deploy actual infrastructure to test end-to-end functionality.
Required Environment Variables:
export TF_VAR_project_id="your-gcp-project-id"
export TF_VAR_app_image="gcr.io/your-project/hrafnar:latest"
export TF_VAR_openai_api_key="sk-your-openai-key"
# Optional (for Cloudflare DNS testing):
export TF_VAR_cloudflare_api_token="your-cloudflare-token"
export TF_VAR_cloudflare_zone_id="your-zone-id"
export TF_VAR_base_domain="yourdomain.com"Run Integration Tests:
# Test development environment deployment
cd test && go test -v -run TestDevEnvironmentDeployment -timeout 30m
# Test production environment deployment
cd test && go test -v -run TestProdEnvironmentDeployment -timeout 30m
# Test minimal configuration
cd test && go test -v -run TestMinimalDeployment -timeout 30m
# Run all integration tests
cd test && go test -v -run Integration -timeout 45mImportant Notes:
- Integration tests will create and destroy real GCP resources
- Tests use unique prefixes to avoid naming conflicts
- Resources are automatically cleaned up after each test
- Ensure you have appropriate GCP permissions and billing enabled
| Command | Description |
|---|---|
make help |
Display available make targets with descriptions |
make init |
Initialize OpenTofu and install pre-commit hooks |
make fmt |
Format all Terraform files |
make validate |
Validate Terraform configuration |
make lint |
Run all linting checks |
make test |
Run the full test suite |
make docs |
Generate documentation with terraform-docs |
make clean |
Clean up temporary files and directories |