feat: add tf file for GCP

This commit is contained in:
2026-05-13 18:43:35 +02:00
parent c2ba691b5e
commit b34e27c5cc
12 changed files with 410 additions and 0 deletions
+14
View File
@@ -36,3 +36,17 @@ yarn-error.log*
# Schema Spy
schemaspy/output/
# Terraform
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars
*.tfvars.json
override.tf
override.tf.json
_override.tf
_override.tf.json
.terraformrc
terraform.rc
+22
View File
@@ -0,0 +1,22 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/google" {
version = "5.45.2"
constraints = "~> 5.0"
hashes = [
"h1:k8taQAdfHrv2F/AiGV5BZBZfI+1uaq8g6O8dWzjx42c=",
"zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a",
"zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804",
"zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e",
"zh:3e555cf22a570a4bd247964671f421ed7517970cd9765ceb46f335edc2c6f392",
"zh:688bd5b05a75124da7ae6e885b2b92bd29f4261808b2b78bd5f51f525c1052ca",
"zh:6db3ef37a05010d82900bfffb3261c59a0c247e0692049cb3eb8c2ef16c9d7bf",
"zh:70316fde75f6a15d72749f66d994ccbdde5f5ed4311b6d06b99850f698c9bbf9",
"zh:84b8e583771a4f2bd514e519d98ed7fd28dce5efe0634e973170e1cfb5556fb4",
"zh:9d4b8ef0a9b6677935c604d94495042e68ff5489932cfd1ec41052e094a279d3",
"zh:a2089dd9bd825c107b148dd12d6b286f71aa37dfd4ca9c35157f2dcba7bc19d8",
"zh:f03d795c0fd9721e59839255ee7ba7414173017dc530b4ce566daf3802a0d6dd",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}
+117
View File
@@ -0,0 +1,117 @@
resource "google_cloud_run_v2_service" "backend" {
name = "tasknote-api"
location = var.region
ingress = "INGRESS_TRAFFIC_ALL"
depends_on = [google_project_service.run]
template {
service_account = google_service_account.cloudrun_sa.email
vpc_access {
connector = google_vpc_access_connector.connector.id
egress = "ALL_TRAFFIC"
}
containers {
image = var.backend_image
ports {
container_port = 8585
}
env {
name = "POSTGRES_DB"
value = var.db_name
}
env {
name = "POSTGRES_HOST"
value = google_sql_database_instance.instance.private_ip_address
}
env {
name = "POSTGRES_USER"
value = var.db_user
}
env {
name = "POSTGRES_PASSWORD"
value = var.db_password
}
env {
name = "POSTGRES_PORT"
value = "5432"
}
env {
name = "CORS_ALLOWED_ORIGINS"
value = var.cors_allowed_origins
}
env {
name = "SERVER_SERVLET_CONTEXT_PATH"
value = "/"
}
env {
name = "TARGET_ENV"
value = "production"
}
env {
name = "SECURITY_KEY"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.security_key.secret_id
version = "latest"
}
}
}
env {
name = "MAILGUN_APIKEY"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.mailgun_apikey.secret_id
version = "latest"
}
}
}
}
}
}
resource "google_cloud_run_v2_service" "frontend" {
name = "tasknote-app"
location = var.region
ingress = "INGRESS_TRAFFIC_ALL"
depends_on = [google_project_service.run]
template {
service_account = google_service_account.cloudrun_sa.email
containers {
image = var.frontend_image
ports {
container_port = 5000
}
env {
name = "VITE_BACKEND_SERVER"
value = google_cloud_run_v2_service.backend.uri
}
}
}
}
# Allow unauthenticated access to both services
resource "google_cloud_run_service_iam_member" "backend_public" {
location = google_cloud_run_v2_service.backend.location
service = google_cloud_run_v2_service.backend.name
role = "roles/run.invoker"
member = "allUsers"
}
resource "google_cloud_run_service_iam_member" "frontend_public" {
location = google_cloud_run_v2_service.frontend.location
service = google_cloud_run_v2_service.frontend.name
role = "roles/run.invoker"
member = "allUsers"
}
# Grant Cloud SQL Client role to the service account
resource "google_project_iam_member" "cloudsql_client" {
project = var.project_id
role = "roles/cloudsql.client"
member = "serviceAccount:${google_service_account.cloudrun_sa.email}"
}
+32
View File
@@ -0,0 +1,32 @@
resource "google_sql_database_instance" "instance" {
name = "tasknote-db-instance"
region = var.region
database_version = "POSTGRES_15"
depends_on = [
google_service_networking_connection.private_vpc_connection,
google_project_service.sqladmin,
google_project_service.servicenetworking
]
settings {
tier = "db-f1-micro" # Smallest tier for dev/small app
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.vpc_network.id
}
}
deletion_protection = false # Set to true for production
}
resource "google_sql_database" "database" {
name = var.db_name
instance = google_sql_database_instance.instance.name
}
resource "google_sql_user" "users" {
name = var.db_user
instance = google_sql_database_instance.instance.name
password = var.db_password
}
+35
View File
@@ -0,0 +1,35 @@
resource "google_compute_network" "vpc_network" {
name = "tasknote-vpc"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "subnet" {
name = "tasknote-subnet"
ip_cidr_range = "10.0.0.0/24"
region = var.region
network = google_compute_network.vpc_network.id
}
# Private IP for Cloud SQL
resource "google_compute_global_address" "private_ip_address" {
name = "tasknote-private-ip"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.vpc_network.id
}
resource "google_service_networking_connection" "private_vpc_connection" {
network = google_compute_network.vpc_network.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
}
# Serverless VPC Access Connector
resource "google_vpc_access_connector" "connector" {
name = "tasknote-vpc-connector"
region = var.region
ip_cidr_range = "10.8.0.0/28"
network = google_compute_network.vpc_network.name
depends_on = [google_project_service.vpcaccess, google_project_service.compute]
}
+15
View File
@@ -0,0 +1,15 @@
output "backend_url" {
value = google_cloud_run_v2_service.backend.uri
}
output "frontend_url" {
value = google_cloud_run_v2_service.frontend.uri
}
output "cloud_sql_instance_ip" {
value = google_sql_database_instance.instance.private_ip_address
}
output "artifact_registry_repo" {
value = google_artifact_registry_repository.repo.name
}
+25
View File
@@ -0,0 +1,25 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "tasknote"
key = "gcp/terraform.tfstate"
region = "auto"
endpoints = { s3 = "https://d17eb09b6bce2f90e16e800bb2a6baf9.r2.cloudflarestorage.com" }
skip_credentials_validation = true
skip_region_validation = true
skip_requesting_account_id = true
skip_metadata_api_check = true
skip_s3_checksum = true
}
}
provider "google" {
project = var.project_id
region = var.region
}
+7
View File
@@ -0,0 +1,7 @@
resource "google_artifact_registry_repository" "repo" {
location = var.region
repository_id = "tasknote-repo"
description = "Docker repository for TaskNote images"
format = "DOCKER"
depends_on = [google_project_service.artifactregistry]
}
+49
View File
@@ -0,0 +1,49 @@
resource "google_service_account" "cloudrun_sa" {
account_id = "tasknote-cloudrun-sa"
display_name = "TaskNote Cloud Run Service Account"
}
resource "google_secret_manager_secret" "security_key" {
secret_id = "security-key"
replication {
user_managed {
replicas {
location = var.region
}
}
}
}
resource "google_secret_manager_secret_version" "security_key_version" {
secret = google_secret_manager_secret.security_key.id
secret_data = var.security_key
}
resource "google_secret_manager_secret" "mailgun_apikey" {
secret_id = "mailgun-apikey"
replication {
user_managed {
replicas {
location = var.region
}
}
}
depends_on = [google_project_service.secretmanager]
}
resource "google_secret_manager_secret_version" "mailgun_apikey_version" {
secret = google_secret_manager_secret.mailgun_apikey.id
secret_data = var.mailgun_apikey
}
resource "google_secret_manager_secret_iam_member" "security_key_access" {
secret_id = google_secret_manager_secret.security_key.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.cloudrun_sa.email}"
}
resource "google_secret_manager_secret_iam_member" "mailgun_apikey_access" {
secret_id = google_secret_manager_secret.mailgun_apikey.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.cloudrun_sa.email}"
}
+34
View File
@@ -0,0 +1,34 @@
resource "google_project_service" "compute" {
service = "compute.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "sqladmin" {
service = "sqladmin.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "run" {
service = "run.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "secretmanager" {
service = "secretmanager.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "vpcaccess" {
service = "vpcaccess.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "artifactregistry" {
service = "artifactregistry.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "servicenetworking" {
service = "servicenetworking.googleapis.com"
disable_on_destroy = false
}
+10
View File
@@ -0,0 +1,10 @@
project_id = "your-gcp-project-id"
region = "us-central1"
db_user = "tasknoteuser"
db_password = "your-db-password"
db_name = "tasknote"
security_key = "your-security-key"
mailgun_apikey = "your-mailgun-apikey"
backend_image = "ghcr.io/rmcampos/tasknote/api:latest"
frontend_image = "ghcr.io/rmcampos/tasknote/app:latest"
cors_allowed_origins = "*"
+50
View File
@@ -0,0 +1,50 @@
variable "project_id" {
type = string
description = "The GCP Project ID"
}
variable "region" {
type = string
default = "us-central1"
description = "The GCP region to deploy resources"
}
variable "db_user" {
type = string
sensitive = true
}
variable "db_password" {
type = string
sensitive = true
}
variable "db_name" {
type = string
default = "tasknote"
}
variable "security_key" {
type = string
sensitive = true
}
variable "mailgun_apikey" {
type = string
sensitive = true
}
variable "backend_image" {
type = string
default = "ghcr.io/rmcampos/tasknote/api:latest"
}
variable "frontend_image" {
type = string
default = "ghcr.io/rmcampos/tasknote/app:latest"
}
variable "cors_allowed_origins" {
type = string
default = "*"
}