GCP Project Layout
The three-project structure, what resources live in each project, and why IAM isolation matters.
Trovella uses three GCP projects. This is an IAM isolation decision, not a complexity preference. Each project has its own IAM policies, billing budgets, and audit logs.
Project Overview
| Project | ID | Purpose | Budget |
|---|---|---|---|
| Production | trovella-prod | All production workloads | $50/month |
| Staging | trovella-staging | Pre-production validation (deferred) | $20/month |
| Shared | trovella-shared | CI/CD infrastructure, state, images | $10/month |
All projects use us-central1 as the default region.
trovella-prod
The production project contains all resources that serve live traffic and store user data.
Resources
| Resource | Terraform Module | Details |
|---|---|---|
| Cloud SQL (PostgreSQL 18) | modules/cloud-sql | db-g1-small, ENTERPRISE edition, ZONAL, 10GB SSD with autoresize |
| Compute Engine VM | modules/compute-vm | e2-custom-2-6144 (2 vCPU, 6GB RAM), Ubuntu 24.04 LTS, 50GB disk |
| Static External IP | modules/compute-vm | Reserved IP for the VM, used in Cloud SQL authorized networks |
| Secret Manager (14 secrets) | modules/secret-manager | Empty shells; values managed outside Terraform |
| Firewall Rules | modules/compute-vm | HTTP/HTTPS (0.0.0.0/0) and IAP SSH (35.235.240.0/20) |
| Cloud Monitoring | modules/compute-vm | CPU, memory, disk alerts + HTTPS uptime check |
| VM Service Account | modules/compute-vm | trovella-vm-prod with scoped IAM roles |
IAM Roles (VM Service Account)
The VM service account (trovella-vm-prod@trovella-prod.iam.gserviceaccount.com) has these roles:
| Role | Purpose |
|---|---|
roles/secretmanager.secretAccessor | Read secrets during deployment |
roles/logging.logWriter | Write structured logs to Cloud Logging |
roles/monitoring.metricWriter | Write custom metrics |
roles/cloudsql.client | Connect via Cloud SQL Auth Proxy |
roles/artifactregistry.reader (on trovella-shared) | Pull Docker images from Artifact Registry |
Secrets
All 14 secrets follow the naming pattern trovella-{name}:
trovella-anthropic-api-key
trovella-google-ai-api-key
trovella-database-url
trovella-better-auth-secret
trovella-better-auth-url
trovella-google-oauth-client-id
trovella-google-oauth-client-secret
trovella-resend-api-key
trovella-upstash-redis-url
trovella-upstash-redis-token
trovella-sentry-dsn
trovella-inngest-event-key
trovella-inngest-signing-key
trovella-typesense-api-key
See Secret Provisioning for how these are created and synced.
trovella-staging
The staging project has the GCP project created and secrets provisioned, but no compute resources deployed. This was an explicit deferral: "don't create infrastructure that has nothing to deploy to."
Resources (Currently Deployed)
| Resource | Details |
|---|---|
| Secret Manager (14 secrets) | Same set as production, via modules/secret-manager |
Resources (Deferred to ~Month 2)
When staging is activated, it will get:
- Compute Engine VM (same module as prod, possibly same VM with a separate compose stack)
- Cloud SQL instance (separate from prod)
- Monitoring alerts
The staging Terraform config (infra/environments/staging/main.tf) has comments marking where the compute module will be added.
trovella-shared
The shared project exists to isolate CI/CD infrastructure from production resources. If these resources lived in trovella-prod, a compromised CI pipeline could modify its own IAM permissions in the production project.
Resources
| Resource | Terraform Module | Details |
|---|---|---|
| Workload Identity Federation Pool | modules/wif | github-actions pool with OIDC provider |
| WIF Service Account | modules/wif | github-actions-ci with cross-project IAM grants |
| Artifact Registry | Direct resource | trovella repository, Docker format, 10-image cleanup policy |
| Terraform State Bucket | Manual (not in TF) | gs://trovella-terraform-state with object versioning |
Artifact Registry Cleanup
The Artifact Registry repository has a cleanup policy that retains only the 10 most recent images per tag. This prevents unbounded storage growth from continuous deployments.
cleanup_policies {
id = "keep-recent"
action = "KEEP"
most_recent_versions {
keep_count = 10
}
}
Cross-Project IAM Grants
The WIF service account in trovella-shared has cross-project permissions. See Workload Identity Federation for the full grant list.
Why Not Two Projects?
The shared project could theoretically be merged with staging. The separation exists because:
- Terraform state isolation -- if state were in a per-environment project, anyone with environment access could corrupt state for all environments
- CI service account isolation -- the GitHub Actions service account cannot modify its own permissions because it lives in a different project than the resources it deploys to
- Billing clarity -- shared infrastructure costs (Artifact Registry, state storage) are tracked separately from environment-specific compute costs