From 66cc8f5666fc808da5dec61ad6f81177e5a0c8a5 Mon Sep 17 00:00:00 2001 From: Brandon Fitzgerald Date: Tue, 20 Oct 2026 16:34:43 -0500 Subject: [PATCH] Deploy free-tier dev instance: external MySQL, scale-to-zero Cloud Run, frontend Dockerfile - Replace the Cloud SQL module with db_host/db_port/db_name/db_user variables pointing at an external MySQL (Aiven free tier); the password lives only in the Secret Manager secret db-password, added out of band - JDBC URL sets sessionVariables=sql_require_primary_key=0 so Liquibase's DATABASECHANGELOG (no primary key) can be created on Aiven - Cloud Run: min 0 / max 2 instances, 1 vCPU, 2Gi backend / 512Mi UI - Backend runs with AUTH_ENABLED=false and placeholder Google client values so the auth.google.* properties resolve; Redis feature gate off - UI gets NEXT_PUBLIC_* URLs for the backend and app - Add frontend Dockerfile and .dockerignore (repo had none); enable the Pub/Sub API in apis.tf; ignore *.tfvars --- frontend/.dockerignore | 5 +++ frontend/Dockerfile | 20 +++++++++++ terraform/.gitignore | 3 ++ terraform/quickstart/apis.tf | 1 + terraform/quickstart/cloud_run_backend.tf | 41 +++++++++++++---------- terraform/quickstart/cloud_run_ui.tf | 20 ++++++++--- terraform/quickstart/db.tf | 40 +++++----------------- terraform/quickstart/secrets.tf | 18 +--------- 8 files changed, 78 insertions(+), 70 deletions(-) create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..68b9c81 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,5 @@ +node_modules +.next +.env* +!.env.template +coverage diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..1dca09c --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,20 @@ +FROM node:20-slim AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +ARG NEXT_PUBLIC_API_BASE_URL +ARG NEXT_PUBLIC_APP_BASE_URL +ARG NEXT_PUBLIC_AUTH_ENABLED=false +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ + NEXT_PUBLIC_APP_BASE_URL=$NEXT_PUBLIC_APP_BASE_URL \ + NEXT_PUBLIC_AUTH_ENABLED=$NEXT_PUBLIC_AUTH_ENABLED \ + NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +FROM node:20-slim +WORKDIR /app +ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 PORT=8080 +COPY --from=builder /app ./ +EXPOSE 8080 +CMD ["npm", "start", "--", "-p", "8080"] diff --git a/terraform/.gitignore b/terraform/.gitignore index 036c86f..3663a6c 100644 --- a/terraform/.gitignore +++ b/terraform/.gitignore @@ -3,3 +3,6 @@ **/.terraform.** *.tfstate *.tfstate.* + +# Local variable files (project ids, hosts) +*.tfvars diff --git a/terraform/quickstart/apis.tf b/terraform/quickstart/apis.tf index 8f3bcda..315a424 100644 --- a/terraform/quickstart/apis.tf +++ b/terraform/quickstart/apis.tf @@ -28,6 +28,7 @@ resource "google_project_service" "apis" { "networksecurity.googleapis.com", "policyanalyzer.googleapis.com", "privateca.googleapis.com", + "pubsub.googleapis.com", "run.googleapis.com", "secretmanager.googleapis.com", "servicenetworking.googleapis.com", diff --git a/terraform/quickstart/cloud_run_backend.tf b/terraform/quickstart/cloud_run_backend.tf index 570d536..a16fd59 100644 --- a/terraform/quickstart/cloud_run_backend.tf +++ b/terraform/quickstart/cloud_run_backend.tf @@ -19,14 +19,14 @@ module "cloud_run_backend" { cloud_run_image = "${local.region}-docker.pkg.dev/${local.project_id}/${var.artifact_registry_repo}/stax-backend" cloud_run_service_name = "stax-backend" - cloud_run_cpu_limit = 4 - cloud_run_memory_limit = "16Gi" + cloud_run_cpu_limit = 1 + cloud_run_memory_limit = "2Gi" cloud_run_startup_cpu_boost = true allow_unauthenticated = true - cloud_run_min_instances = 1 - cloud_run_max_instances = 20 + cloud_run_min_instances = 0 + cloud_run_max_instances = 2 cloud_run_max_instance_concurrency = 80 cloud_run_env_vars = [ @@ -46,6 +46,23 @@ module "cloud_run_backend" { name = "TOS_TYPE", value = "tt" }, + # Dev instance: Google SSO off. auth.google.* placeholders must still resolve. + { + name = "AUTH_ENABLED", + value = "false" + }, + { + name = "GOOGLE_CLIENT_ID", + value = "disabled" + }, + { + name = "GOOGLE_CLIENT_SECRET", + value = "disabled" + }, + { + name = "FEATURE_GATE_REDIS_ENABLED", + value = "false" + }, { name = "SYSTEM_USER_FIRST_NAME", value = "Stax" @@ -103,7 +120,7 @@ module "cloud_run_backend" { }, { name = "JDBC_DATABASE_USERNAME", - value = module.cloudsql_mysql_instance.database_user_name + value = var.db_user }, { name = "JDBC_DATABASE_PASSWORD", @@ -115,24 +132,12 @@ module "cloud_run_backend" { { name = "JDBC_DATABASE_URL", # https://cloud.google.com/sql/docs/mysql/connect-run#java_2 - value = "jdbc:mysql://${module.cloudsql_mysql_instance.private_ip_address}:3306/${module.cloudsql_mysql_instance.database_name}" + value = "jdbc:mysql://${var.db_host}:${var.db_port}/${var.db_name}?sslMode=REQUIRED&serverTimezone=UTC&sessionVariables=sql_require_primary_key=0" } ] ingress_type_cloud_run = "INGRESS_TRAFFIC_ALL" - volumes = [ - { - name = "cloudsql", - cloud_sql_instance = { - instances = [module.cloudsql_mysql_instance.connection_name] - } - } - ] - - volume_mounts_map = { - "cloudsql" = "/cloudsql" - } depends_on = [ google_secret_manager_secret.aes_secret_key, google_secret_manager_secret.token_signing_key, google_secret_manager_secret.tos_token_signing_key ] } \ No newline at end of file diff --git a/terraform/quickstart/cloud_run_ui.tf b/terraform/quickstart/cloud_run_ui.tf index 9e3a096..cbc7c30 100644 --- a/terraform/quickstart/cloud_run_ui.tf +++ b/terraform/quickstart/cloud_run_ui.tf @@ -20,12 +20,12 @@ module "ui_service" { cloud_run_service_name = "stax-ui" cloud_run_image = "${local.region}-docker.pkg.dev/${local.project_id}/${var.artifact_registry_repo}/stax-ui" - cloud_run_cpu_limit = 4 - cloud_run_memory_limit = "2Gi" + cloud_run_cpu_limit = 1 + cloud_run_memory_limit = "512Mi" allow_unauthenticated = true - cloud_run_min_instances = 1 - cloud_run_max_instances = 20 + cloud_run_min_instances = 0 + cloud_run_max_instances = 2 cloud_run_max_instance_concurrency = 80 cloud_run_env_vars = [ @@ -41,5 +41,17 @@ module "ui_service" { name = "TSC_COMPILE_ON_ERROR", value = "true" }, + { + name = "NEXT_PUBLIC_API_BASE_URL", + value = "https://stax-backend-${data.google_project.default_project.number}.${local.region}.run.app" + }, + { + name = "NEXT_PUBLIC_APP_BASE_URL", + value = "https://stax-ui-${data.google_project.default_project.number}.${local.region}.run.app" + }, + { + name = "NEXT_PUBLIC_AUTH_ENABLED", + value = "false" + }, ] } diff --git a/terraform/quickstart/db.tf b/terraform/quickstart/db.tf index eaed2dd..cf0baa4 100644 --- a/terraform/quickstart/db.tf +++ b/terraform/quickstart/db.tf @@ -13,34 +13,12 @@ # limitations under the License. # Call the Cloud SQL MySQL module -module "cloudsql_mysql_instance" { - - source = "../modules/gcp_sql" - - project_id = data.google_project.default_project.project_id - region = local.region - instance_name = "mysql-db" - database_version = "MYSQL_8_4" - cloudsql_edition = "ENTERPRISE_PLUS" - availability_type = "REGIONAL" # Pass the desired availability type - database_tier = "db-perf-optimized-N-2" - # disk_size = 512 # SSD storage to 512 GB - - root_password = random_password.db_root_password.result - database_name = "stax-db" - database_user_name = "user" - database_user_password = random_password.db_user_password.result - database_charset = "utf8mb4" - database_collation = "utf8mb4_0900_ai_ci" - - enable_deletion_protection = false - enable_backup_configuration = true - backup_start_time = "20:55" - - mysql_database_flags = { - "general_log" = "on" - "slow_query_log" = "on" - "log_output" = "FILE" - "skip_show_database" = "on" - } -} \ No newline at end of file +# External MySQL (Aiven free tier). Cloud SQL has no free tier. +# Password lives in Secret Manager secret "db-password" (set out of band, see secrets.tf). +variable "db_host" { type = string } +variable "db_port" { + type = number + default = 3306 +} +variable "db_name" { type = string } +variable "db_user" { type = string } diff --git a/terraform/quickstart/secrets.tf b/terraform/quickstart/secrets.tf index ecaa940..cd824f9 100644 --- a/terraform/quickstart/secrets.tf +++ b/terraform/quickstart/secrets.tf @@ -13,15 +13,6 @@ # limitations under the License. ################################## db_password ################################# -resource "random_password" "db_user_password" { - length = 16 - min_lower = 1 - min_numeric = 1 - min_upper = 1 - min_special = 1 - # removes % and & which are special characters in uri - override_special = "!@#$*()-_=+[]{}<>:?" -} resource "google_secret_manager_secret" "db_password" { project = local.project_id @@ -33,7 +24,7 @@ resource "google_secret_manager_secret" "db_password" { resource "google_secret_manager_secret_version" "db_password" { secret = google_secret_manager_secret.db_password.id - secret_data = random_password.db_user_password.result + secret_data = "REPLACE_ME_VIA_GCLOUD" # placeholder; real value added as a new version out of band lifecycle { ignore_changes = all @@ -46,13 +37,6 @@ resource "google_secret_manager_secret_iam_member" "secretaccess_db_password" { member = "serviceAccount:${local.service_account}" } ################################ root_password #################################### -resource "random_password" "db_root_password" { - length = 16 - min_lower = 1 - min_numeric = 1 - min_upper = 1 - min_special = 1 -} ######################### Secret for AES_SECRET_KEY ############################## resource "random_password" "aes_secret_key" { length = 16