This guide will help you set up and deploy the Cover Letter LLM application infrastructure using Terraform.
Before deploying to the cloud, you can test your Terraform configuration locally:
terraform version
cd terraform/environments/development
cp terraform.tfvars.example terraform.tfvars
Edit terraform.tfvars
and set:
# Required: Use any placeholder value for local testing
project_id = "test-project-local"
# Optional: Keep other values as-is for testing
region = "us-central1"
zone = "us-central1-a"
container_image = "gcr.io/test-project-local/cover-letter-llm:latest"
# Initialize Terraform
terraform init
# Validate configuration syntax
terraform validate
# Format code
terraform fmt -recursive
# Test configuration (will show plan but won't create resources)
terraform plan
Expected Result: You should see a plan showing resources to be created, ending with a Google credentials error. This is normal for local testing and confirms your configuration is valid.
You’ve successfully validated that your Terraform configuration is syntactically correct and structurally sound.
Purpose: Your Terraform creates Cloud Run services that need a container image to deploy.
Steps:
cd /home/elreyuno/cover-letter-llm/CoverLetterApp
docker build -t cover-letter-llm:latest .
Why: Cloud Run needs a containerized application to run. Without this, your infrastructure will deploy but have nothing to serve.
Purpose: Decide how you want to deploy - locally controlled vs automated CI/CD.
Options:
Purpose: Create the actual cloud environment where your infrastructure will live.
Steps:
# Create project
gcloud projects create your-project-id
# Enable billing and required APIs
gcloud services enable compute.googleapis.com run.googleapis.com sql-component.googleapis.com
Why: You need a real GCP project to deploy actual resources that users can access.
Purpose: Give Terraform permission to create resources in your GCP project.
Steps:
gcloud auth application-default login
# OR
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"
Why: Terraform needs credentials to interact with Google Cloud APIs.
Purpose: Store Terraform state securely and enable team collaboration.
Steps:
# Create GCS bucket for state
gsutil mb gs://your-project-terraform-state
Why: Local state files get lost and can’t be shared. Remote state enables collaboration and recovery.
Purpose: Deploy your actual infrastructure to the cloud.
Steps:
cd terraform/environments/development
terraform apply
Why: This creates the actual infrastructure users will interact with.
Purpose: Deploy your Rails application to the created infrastructure.
Why: Infrastructure without the application is just empty servers.
Purpose: Make your application accessible via a proper domain name with HTTPS.
Why: Users need a memorable URL and secure connections.
You’re building a complete, production-ready deployment pipeline that:
Which step would you like to tackle next?
your-company-coverllm-dev
your-company-coverllm-staging
your-company-coverllm-prod
gcloud services enable compute.googleapis.com
gcloud services enable run.googleapis.com
gcloud services enable sql-component.googleapis.com
gcloud services enable redis.googleapis.com
gcloud services enable secretmanager.googleapis.com
gcloud services enable storage-component.googleapis.com
gcloud services enable monitoring.googleapis.com
gcloud services enable logging.googleapis.com
gcloud services enable servicenetworking.googleapis.com
# For each environment
gcloud iam service-accounts create terraform-deployer \
--description="Service account for Terraform deployments" \
--display-name="Terraform Deployer"
# Grant necessary permissions
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:terraform-deployer@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/editor"
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:terraform-deployer@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/secretmanager.admin"
# Generate service account keys
gcloud iam service-accounts keys create terraform-key.json \
--iam-account=terraform-deployer@PROJECT_ID.iam.gserviceaccount.com
gsutil mb gs://your-company-terraform-state
gsutil versioning set on gs://your-company-terraform-state
git clone https://github.com/yourusername/cover-letter-llm.git
cd cover-letter-llm
git checkout terraform-infrastructure
# Set your Google Cloud project
export GOOGLE_PROJECT="your-company-coverllm-dev"
export GOOGLE_APPLICATION_CREDENTIALS="path/to/terraform-key.json"
cd terraform/environments/development
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your project details
# Format and validate
../../../terraform/deploy.sh development fmt
../../../terraform/deploy.sh development validate
# Initialize Terraform
../../../terraform/deploy.sh development init
# Plan deployment
../../../terraform/deploy.sh development plan
# Apply changes
../../../terraform/deploy.sh development apply
Configuration:
# terraform/environments/development/terraform.tfvars
project_id = "your-company-coverllm-dev"
container_image = "gcr.io/your-company-coverllm-dev/cover-letter-llm:latest"
region = "us-central1"
Configuration:
# terraform/environments/staging/terraform.tfvars
project_id = "your-company-coverllm-staging"
container_image = "gcr.io/your-company-coverllm-staging/cover-letter-llm:staging"
domain_name = "staging.coverletter.yourcompany.com"
region = "us-central1"
Configuration:
# terraform/environments/production/terraform.tfvars
project_id = "your-company-coverllm-prod"
container_image = "gcr.io/your-company-coverllm-prod/cover-letter-llm:latest"
domain_name = "coverletter.yourcompany.com"
region = "us-central1"
notification_channels = ["projects/PROJECT_ID/notificationChannels/CHANNEL_ID"]
Before deploying, you need to manually create some secrets:
# Generate a new master key in your Rails app
cd CoverLetterApp
bundle exec rails credentials:edit
# Copy the master key and create the secret
gcloud secrets create cover-letter-llm-development-rails-master-key \
--data-file=config/master.key
# Create secret for your Google AI API key
echo "your-google-ai-api-key" | gcloud secrets create cover-letter-llm-development-google-api-key \
--data-file=-
# Generate a secret key base
SECRET_KEY_BASE=$(bundle exec rails secret)
echo $SECRET_KEY_BASE | gcloud secrets create cover-letter-llm-production-secret-key-base \
--data-file=-
Add these secrets to your GitHub repository:
Development:
GCP_SA_KEY_DEV
: Service account key JSON for developmentGCP_PROJECT_ID_DEV
: Development project IDCONTAINER_IMAGE_DEV
: Development container image URLStaging:
GCP_SA_KEY_STAGING
: Service account key JSON for stagingGCP_PROJECT_ID_STAGING
: Staging project IDCONTAINER_IMAGE_STAGING
: Staging container image URLDOMAIN_NAME_STAGING
: Staging domain nameProduction:
GCP_SA_KEY_PROD
: Service account key JSON for productionGCP_PROJECT_ID_PROD
: Production project IDCONTAINER_IMAGE_PROD
: Production container image URLDOMAIN_NAME_PROD
: Production domain nameNOTIFICATION_CHANNEL_PROD
: Production notification channel IDGCP_REGION
: Default region (e.g., us-central1
)GCP_ZONE
: Default zone (e.g., us-central1-a
)terraform-infrastructure
branchmain
branchAutomatic alerts are configured for:
1. Terraform State Lock
# If state is locked, force unlock (use carefully)
terraform force-unlock LOCK_ID
2. Service Account Permissions
# Check current permissions
gcloud projects get-iam-policy PROJECT_ID \
--flatten="bindings[].members" \
--format="table(bindings.role)" \
--filter="bindings.members:terraform-deployer@PROJECT_ID.iam.gserviceaccount.com"
3. API Not Enabled
# Enable all required APIs
gcloud services enable compute.googleapis.com run.googleapis.com sqladmin.googleapis.com redis.googleapis.com secretmanager.googleapis.com storage.googleapis.com monitoring.googleapis.com logging.googleapis.com servicenetworking.googleapis.com
4. Insufficient Quota
5. Docker Build Issues
# Build and test locally
cd CoverLetterApp
docker build -t cover-letter-llm .
docker run -p 3000:3000 cover-letter-llm
# Destroy development environment
./terraform/deploy.sh development destroy --auto-approve
# Destroy staging environment
./terraform/deploy.sh staging destroy --auto-approve
# Destroy production environment (be very careful!)
./terraform/deploy.sh production destroy --auto-approve
# Remove state files from GCS bucket
gsutil rm -r gs://your-company-terraform-state/cover-letter-llm/