Conversation
- Create Terraform manifests for Cloud Run, Artifact Registry, and Secret Manager - Add a deployment script to automate infrastructure provisioning and image builds - Configure Cloud Run for free-tier eligibility (scale-to-zero, idle CPU) - Update Dockerfile and ignore files for production compatibility - Support Rails master key management via GCP Secret Manager Co-authored-by: Claude Code <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds Terraform + a helper deploy script to provision GCP resources (Artifact Registry, Secret Manager, Cloud Run) and deploy the Rails app to Cloud Run.
Changes:
- Introduces Terraform configuration (variables, main resources, outputs) for Cloud Run deployment.
- Adds an example
terraform.tfvarsand adeploy.shscript to provision infra, build/push a Docker image, and update Cloud Run. - Updates repo ignores for Terraform artifacts and adds a
.dockerignore.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| terraform/variables.tf | Defines Terraform inputs for GCP project/region, app name, Rails master key, optional domain. |
| terraform/terraform.tfvars.example | Example values for required Terraform variables. |
| terraform/outputs.tf | Exposes Cloud Run service URL and Artifact Registry image/repo strings. |
| terraform/main.tf | Provisions APIs, Artifact Registry, Secret Manager secret, Cloud Run service + IAM, optional domain mapping. |
| terraform/deploy.sh | Automates terraform apply, Docker build/push, and a gcloud run service update. |
| Dockerfile | Switches Ruby base image reference to docker.io/.... |
| .gitignore | Ignores Terraform local state/vars and related files. |
| .dockerignore | Excludes infra/CI/dev/test/docs artifacts from Docker build context. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| image = "${var.region}-docker.pkg.dev/${var.project_id}/${var.app_name}/${var.app_name}:latest" | ||
|
|
||
| ports { | ||
| container_port = 80 |
There was a problem hiding this comment.
Cloud Run container is configured to use port 80, but the Docker image runs as a non-root user (UID 1000). Binding to privileged ports (<1024) will fail at runtime, so the service likely won’t start. Use an unprivileged port (typically 8080) and let the app read PORT (already in config/puma.rb).
| container_port = 80 | |
| container_port = 8080 |
| resource "google_secret_manager_secret_version" "rails_master_key" { | ||
| secret = google_secret_manager_secret.rails_master_key.id | ||
| secret_data = var.rails_master_key | ||
| } |
There was a problem hiding this comment.
google_secret_manager_secret_version.secret_data will store the Rails master key value in the Terraform state file in plaintext (even if the input variable is marked sensitive). This is a significant secret-leak risk for anyone with access to state (local disk, CI artifacts, remote backend). Prefer creating the Secret in Terraform but adding versions outside Terraform (e.g., gcloud secrets versions add), or use a secured/encrypted remote backend with strict access controls and avoid committing/retaining local state.
| resource "google_secret_manager_secret_version" "rails_master_key" { | |
| secret = google_secret_manager_secret.rails_master_key.id | |
| secret_data = var.rails_master_key | |
| } | |
| # NOTE: Secret versions (actual Rails master key values) should be | |
| # created outside Terraform (e.g., via `gcloud secrets versions add`) | |
| # to avoid storing secret data in Terraform state. |
| resource "google_cloud_run_v2_service_iam_member" "public" { | ||
| project = google_cloud_run_v2_service.app.project | ||
| location = google_cloud_run_v2_service.app.location | ||
| name = google_cloud_run_v2_service.app.name | ||
| role = "roles/run.invoker" | ||
| member = "allUsers" | ||
| } |
There was a problem hiding this comment.
This unconditionally makes the Cloud Run service publicly invokable by allUsers. If this app is not intended to be fully public, this should be gated behind a variable (e.g., public_access = true/false) or use a more restrictive principal, otherwise it’s easy to accidentally expose internal environments.
| # Step 5: Deploy to Cloud Run (update the service to use the new image) | ||
| echo "" | ||
| echo "==> Step 5: Deploying new image to Cloud Run..." | ||
| gcloud run services update "$APP_NAME" \ | ||
| --region "$REGION" \ | ||
| --image "$DOCKER_TAG" \ | ||
| --project "$PROJECT_ID" \ | ||
| --quiet |
There was a problem hiding this comment.
The deploy script updates the Cloud Run service with gcloud run services update after Terraform creates it. This will cause configuration drift from Terraform state and makes future terraform apply runs potentially revert or conflict with the deployed image. Consider making the image tag a Terraform variable and updating it via Terraform, or add a Terraform lifecycle { ignore_changes = [template[0].containers[0].image] } if gcloud is intended to own image updates.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Can you deploy this for free on my GCP? Maybe using terraform?
Superconductor Ticket Implementation