Skip to content

Infrastructure Documentation

Cloud Provider: Google Cloud Platform (GCP) IaC Tool: Terraform CI/CD: Google Cloud Build Regions: Canada (northamerica-northeast1), US (us-central1)


Table of Contents

  1. Overview
  2. Infrastructure Diagram
  3. Local Development Stack
  4. Docker Configuration
  5. Terraform Infrastructure
  6. Google Cloud Run Deployment
  7. CI/CD with Cloud Build
  8. Database Management
  9. Networking & VPC
  10. Monitoring & Logging
  11. Security
  12. Cost Management

Overview

The Noumaris infrastructure is designed for multi-region deployment with a focus on:

  • Compliance: HIPAA-ready architecture (private database, encryption, audit logs)
  • Performance: <200ms response times in both Canada and US
  • Cost Efficiency: Serverless Cloud Run (scale to zero when idle)
  • Developer Experience: One-command local development setup
  • Automation: Terraform for IaC, Cloud Build for CI/CD

Infrastructure Layers

  1. Local Development - Docker Compose (PostgreSQL, Keycloak)
  2. Cloud Infrastructure - Terraform (Cloud Run, Cloud SQL, VPC, Load Balancer)
  3. CI/CD - Cloud Build (automated builds and deployments)
  4. Monitoring - Cloud Logging, Cloud Monitoring, error reporting

Infrastructure Diagram

mermaid
graph TB
    subgraph "Local Development"
        DevDB[(PostgreSQL<br/>:5433)]
        DevKC[Keycloak<br/>:8081]
        DevBackend[FastAPI<br/>:8000]
        DevFrontend[Vite<br/>:5173]

        DevFrontend -->|JWT Auth| DevKC
        DevFrontend -->|HTTP/WSS| DevBackend
        DevBackend -->|SQL| DevDB
        DevBackend -->|Validate Token| DevKC
    end

    subgraph "Google Cloud - Production"
        subgraph "Load Balancer"
            LB[Global HTTPS LB<br/>api.noumaris.com]
        end

        subgraph "Canada Region"
            CRCA[Cloud Run<br/>Backend CA]
            DBCA[(Cloud SQL<br/>PostgreSQL CA)]
            KCCA[Cloud Run<br/>Keycloak CA]
            VPCCA[VPC Connector CA]

            CRCA -.->|Private IP| VPCCA
            VPCCA -.->|Private IP| DBCA
            CRCA -->|HTTP| KCCA
        end

        subgraph "US Region"
            CRUS[Cloud Run<br/>Backend US]
            DBUS[(Cloud SQL<br/>PostgreSQL US)]
            KCUS[Cloud Run<br/>Keycloak US]
            VPCUS[VPC Connector US]

            CRUS -.->|Private IP| VPCUS
            VPCUS -.->|Private IP| DBUS
            CRUS -->|HTTP| KCUS
        end

        LB -->|Route| CRCA
        LB -->|Route| CRUS

        subgraph "Shared Services"
            AR[Artifact Registry<br/>Container Images]
            SM[Secret Manager<br/>API Keys, DB Passwords]
            CB[Cloud Build<br/>CI/CD]
        end

        CB -->|Push Images| AR
        CRCA -->|Pull Images| AR
        CRUS -->|Pull Images| AR
        CRCA -->|Read Secrets| SM
        CRUS -->|Read Secrets| SM
    end

    subgraph "External Services"
        Anthropic[Anthropic Claude<br/>LLM]
        Deepgram[Deepgram<br/>Transcription]
        Cloudflare[Cloudflare Pages<br/>Frontend Hosting]
    end

    CRCA -->|API Calls| Anthropic
    CRCA -->|API Calls| Deepgram
    CRUS -->|API Calls| Anthropic
    CRUS -->|API Calls| Deepgram
    Cloudflare -->|API Calls| LB

Local Development Stack

Docker Compose Configuration

File: docker-compose.yml

yaml
version: '3.8'

services:
  # PostgreSQL Database
  postgres:
    image: postgres:15
    container_name: postgres_db
    environment:
      POSTGRES_USER: medical_user
      POSTGRES_PASSWORD: password123
      POSTGRES_DB: medical_db
    ports:
      - "5433:5432"  # Port 5433 to avoid conflicts
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    networks:
      - medical-scribe-net

  # Keycloak Authentication Server
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    container_name: keycloak_auth
    command: start-dev
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    ports:
      - "8081:8080"  # Port 8081 to avoid conflicts
    volumes:
      - keycloak_data:/opt/keycloak/data
      # Mount Keycloakify theme JARs
      - ./frontend/dist_keycloak:/opt/keycloak/providers:ro
    restart: unless-stopped
    networks:
      - medical-scribe-net

volumes:
  postgres_data:
  keycloak_data:

networks:
  medical-scribe-net:
    driver: bridge

Quick Start Commands

bash
# Start all services
docker-compose up -d

# Configure Keycloak with Terraform
bash scripts/setup-local-keycloak.sh

# View logs
docker-compose logs -f postgres
docker-compose logs -f keycloak

# Stop services
docker-compose down

# Reset volumes (fresh start)
docker-compose down -v

Service Endpoints

ServiceURLCredentials
PostgreSQLlocalhost:5433medical_user / password123
Keycloakhttp://localhost:8081admin / admin
Backendhttp://localhost:8000N/A (start manually)
Frontendhttp://localhost:5173N/A (start manually)

Keycloak Setup with Terraform

Script: scripts/setup-local-keycloak.sh

This script automates the configuration of local Keycloak using Terraform:

bash
#!/bin/bash
# Setup script to configure local Docker Keycloak with Terraform

set -e

# Check if Docker is running
if ! docker info > /dev/null 2>&1; then
    echo "Error: Docker is not running"
    exit 1
fi

# Check if Keycloak container is running
if ! docker ps | grep -q keycloak_auth; then
    echo "Starting Docker Compose services..."
    docker-compose up -d
    sleep 30  # Wait for Keycloak to start
fi

# Check if Keycloak is accessible
max_attempts=10
attempt=0
while [ $attempt -lt $max_attempts ]; do
    if curl -s http://localhost:8081 > /dev/null; then
        echo "✓ Keycloak is accessible"
        break
    fi
    sleep 5
    attempt=$((attempt + 1))
done

# Navigate to terraform/keycloak directory
cd terraform/keycloak

# Initialize Terraform
terraform init

# Apply Terraform configuration
terraform apply

echo "✓ Keycloak setup complete!"

What it configures:

  • Realm: noumaris
  • Roles: superadmin, institution_admin, resident, user
  • Client: fastapi-frontend (public, standard flow)
  • Admin Service: noumaris-admin-service (confidential, service accounts)

See ADR-004: Terraform for Keycloak for rationale.


Docker Configuration

Backend Dockerfile

File: backend/Dockerfile

dockerfile
FROM python:3.11-slim

# Create non-root user (security best practice)
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 --gid 1001 appuser

WORKDIR /home/appuser/app

# Install Poetry
RUN pip install poetry==1.7.1
RUN poetry config virtualenvs.in-project true

# Copy dependency files
COPY pyproject.toml poetry.lock ./

# Install dependencies
RUN poetry install --no-root --only main

# Copy application code
COPY src/ ./src
COPY alembic.ini ./
COPY alembic/ ./alembic/
COPY scripts/migrate.sh ./migrate.sh

RUN chmod +x ./migrate.sh && \
    chown -R appuser:appgroup /home/appuser/app

USER appuser

ENV PYTHONPATH=/home/appuser/app/src

EXPOSE 8080

# Run Uvicorn with production settings
CMD ["/bin/sh", "-c", "./.venv/bin/uvicorn noumaris_backend.api.main:app \
  --host 0.0.0.0 \
  --port ${PORT:-8080} \
  --log-level info \
  --timeout-keep-alive 300 \
  --ws-ping-interval 20 \
  --ws-ping-timeout 20 \
  --proxy-headers \
  --forwarded-allow-ips='*'"]

Key Features:

  • Non-root user - Runs as appuser:appgroup (UID/GID 1001)
  • Poetry - Python dependency management
  • Slim image - Based on python:3.11-slim (~180MB vs ~900MB with full image)
  • WebSocket support - --ws-ping-interval 20 for live transcription
  • Proxy headers - Required for Cloud Run

Build Locally

bash
cd backend
docker build -t noumaris-backend:latest .
docker run -p 8000:8080 --env-file .env noumaris-backend:latest

Terraform Infrastructure

Overview

Terraform manages all GCP infrastructure as code. The configuration is organized into modules:

terraform/
├── main.tf                 # Main configuration
├── providers.tf            # GCP provider config
├── variables.tf            # Input variables
├── locals.tf               # Local values
├── services.tf             # Cloud Run services
├── database.tf             # Cloud SQL databases
├── network.tf              # VPC, subnets, connectors
├── lb.tf                   # Load balancer
├── cloud_build.tf          # Cloud Build triggers
├── iam.tf                  # IAM roles and service accounts
├── secrets.tf              # Secret Manager
├── keycloak.tf             # Keycloak Cloud Run service
├── keycloak/               # Keycloak realm configuration
│   ├── main.tf
│   ├── variables.tf
│   └── terraform.tfvars.local
└── .terraform.lock.hcl     # Provider version lock

Workspaces

Terraform workspaces manage multiple environments:

  • dev - Development environment
  • staging - Staging environment (future)
  • prod - Production environment (future)
bash
# List workspaces
terraform workspace list

# Switch workspace
terraform workspace select dev

# Create new workspace
terraform workspace new staging

Region Configuration

Multi-region setup defined in locals.tf:

hcl
locals {
  region_config = {
    ca = {
      gcp_region = "northamerica-northeast1"  # Montreal
      db_region  = "northamerica-northeast1"
    }
    us = {
      gcp_region = "us-central1"              # Iowa
      db_region  = "us-central1"
    }
  }
}

Terraform Commands

bash
# Initialize
terraform init

# Plan changes (preview)
terraform plan

# Apply changes
terraform apply

# Destroy infrastructure
terraform destroy

# Format code
terraform fmt -recursive

# Validate configuration
terraform validate

# Show current state
terraform show

# Output values
terraform output

Google Cloud Run Deployment

Backend Service Configuration

File: terraform/services.tf

hcl
resource "google_cloud_run_v2_service" "backend" {
  for_each = local.region_config

  name     = "backend-${terraform.workspace}-${each.key}"
  location = each.value.gcp_region
  project  = var.gcp_project_ids[terraform.workspace]

  ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"

  template {
    service_account = google_service_account.cloud_run_sa[each.key].email

    timeout                          = "300s"
    max_instance_request_concurrency = 80

    # VPC Connector for private database access
    vpc_access {
      connector = google_vpc_access_connector.main[each.key].id
      egress    = "ALL_TRAFFIC"
    }

    containers {
      image = "us-docker.pkg.dev/cloudrun/container/hello"

      ports {
        container_port = 8080
      }

      resources {
        limits = {
          cpu    = "1"
          memory = "512Mi"
        }
        startup_cpu_boost = false
      }

      # Startup probe
      startup_probe {
        http_get {
          path = "/health/ready"
        }
        initial_delay_seconds = 10
        timeout_seconds       = 3
        period_seconds        = 10
        failure_threshold     = 3
      }

      # Liveness probe
      liveness_probe {
        http_get {
          path = "/health"
        }
        initial_delay_seconds = 30
        timeout_seconds       = 1
        period_seconds        = 10
        failure_threshold     = 3
      }

      # Environment Variables
      env {
        name  = "DATABASE_URL"
        value = "postgresql+psycopg2://..."
      }

      env {
        name = "ANTHROPIC_API_KEY"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.application_secrets["anthropic-api-key"].secret_id
            version = "latest"
          }
        }
      }

      # ... more env vars
    }

    scaling {
      min_instance_count = 0  # Scale to zero
      max_instance_count = 3
    }
  }

  lifecycle {
    ignore_changes = [
      template[0].containers[0].image,  # Cloud Build updates image
      template[0].labels["commit-sha"],
      template[0].labels["managed-by"],
    ]
  }
}

Key Features

  1. Scale to Zero - No cost when idle
  2. Concurrency - 80 concurrent requests per instance
  3. Health Checks - Startup and liveness probes
  4. Secrets - API keys from Secret Manager
  5. Private Database - VPC connector for Cloud SQL
  6. Timeout - 300s for long-running operations (transcription, note generation)

Service Account Permissions

hcl
# IAM: Cloud Run Service Account
resource "google_service_account" "cloud_run_sa" {
  for_each = local.region_config

  account_id   = "cloud-run-sa-${terraform.workspace}-${each.key}"
  display_name = "Cloud Run Service Account (${each.key})"
  project      = var.gcp_project_ids[terraform.workspace]
}

# Grant Cloud SQL Client role
resource "google_project_iam_member" "cloud_run_sa_cloudsql_client" {
  for_each = local.region_config

  project = var.gcp_project_ids[terraform.workspace]
  role    = "roles/cloudsql.client"
  member  = "serviceAccount:${google_service_account.cloud_run_sa[each.key].email}"
}

# Grant Secret Manager Secret Accessor role
resource "google_secret_manager_secret_iam_member" "cloud_run_sa_app_secrets_access" {
  for_each = toset([
    "anthropic-api-key",
    "deepgram-api-key",
    "keycloak-realm",
    "keycloak-client-id"
  ])

  secret_id = google_secret_manager_secret.application_secrets[each.key].id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${google_service_account.cloud_run_sa["ca"].email}"
}

CI/CD with Cloud Build

Cloud Build Configuration

File: cloudbuild.yaml

yaml
steps:
  # Step 1: Build Docker image
  - name: 'gcr.io/cloud-builders/docker'
    args: [
      'build',
      '-t', 'us-central1-docker.pkg.dev/noumaris-mgmt/noumaris-backend/noumaris-backend:$COMMIT_SHA',
      '-t', 'us-central1-docker.pkg.dev/noumaris-mgmt/noumaris-backend/noumaris-backend:latest',
      './backend'
    ]
    id: 'build-image'

  # Step 2: Push image to Artifact Registry
  - name: 'gcr.io/cloud-builders/docker'
    args: [
      'push',
      '--all-tags',
      'us-central1-docker.pkg.dev/noumaris-mgmt/noumaris-backend/noumaris-backend'
    ]
    id: 'push-image'
    waitFor: ['build-image']

  # Step 3: Deploy to Canada region (parallel)
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gcloud'
    args: [
      'run', 'deploy', 'backend-dev-ca',
      '--image', 'us-central1-docker.pkg.dev/noumaris-mgmt/noumaris-backend/noumaris-backend:$COMMIT_SHA',
      '--region', 'northamerica-northeast1',
      '--platform', 'managed',
      '--allow-unauthenticated',
      '--cpu', '1',
      '--memory', '512Mi',
      '--timeout', '300',
      '--concurrency', '80',
      '--min-instances', '0',
      '--max-instances', '3',
      '--vpc-connector', 'vpc-connector-dev-ca',
      '--vpc-egress', 'all-traffic'
    ]
    id: 'deploy-ca'
    waitFor: ['push-image']

  # Step 4: Deploy to US region (parallel)
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gcloud'
    args: [
      'run', 'deploy', 'backend-dev-us',
      '--image', 'us-central1-docker.pkg.dev/noumaris-mgmt/noumaris-backend/noumaris-backend:$COMMIT_SHA',
      '--region', 'us-central1',
      # ... same args as Canada
    ]
    id: 'deploy-us'
    waitFor: ['push-image']

  # Step 5: Verify deployments
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        echo "Testing health endpoint via load balancer..."
        CA_URL="https://api-dev.noumaris.com"

        if curl -f -s "$${CA_URL}/health" > /dev/null; then
          echo "✓ Deployment healthy!"
        else
          echo "✗ Health check failed"
          exit 1
        fi
    id: 'verify-deployments'
    waitFor: ['deploy-ca', 'deploy-us']

options:
  logging: CLOUD_LOGGING_ONLY
  machineType: UNSPECIFIED
  diskSizeGb: 50

Build Trigger

hcl
# terraform/cloud_build.tf
resource "google_cloudbuild_trigger" "backend_trigger" {
  name        = "backend-deploy-trigger"
  description = "Trigger backend deployment on push to develop"

  github {
    owner = "noumaris"
    name  = "noumaris"
    push {
      branch = "^develop$"
    }
  }

  filename = "cloudbuild.yaml"

  included_files = [
    "backend/**",
    "cloudbuild.yaml"
  ]
}

Build Process

mermaid
sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub
    participant CB as Cloud Build
    participant AR as Artifact Registry
    participant CRCA as Cloud Run CA
    participant CRUS as Cloud Run US

    Dev->>GH: git push develop
    GH->>CB: Trigger webhook
    CB->>CB: Build Docker image
    CB->>AR: Push image:$COMMIT_SHA
    CB->>AR: Push image:latest

    par Deploy to Regions
        CB->>CRCA: Deploy new image
        CRCA->>CRCA: Rolling update
        CB->>CRUS: Deploy new image
        CRUS->>CRUS: Rolling update
    end

    CB->>CRCA: Verify /health
    CB->>Dev: ✓ Build successful

Deployment Time: ~5 minutes (build + deploy)


Database Management

Cloud SQL Configuration

File: terraform/database.tf

hcl
resource "google_sql_database_instance" "main" {
  for_each = local.region_config

  name             = "noumaris-db-${terraform.workspace}-${each.key}"
  database_version = "POSTGRES_15"
  region           = each.value.db_region
  project          = var.gcp_project_ids[terraform.workspace]

  settings {
    tier              = "db-f1-micro"  # Smallest instance
    availability_type = "ZONAL"        # Single zone (cheaper)
    disk_type         = "PD_SSD"
    disk_size         = 10

    backup_configuration {
      enabled                        = true
      start_time                     = "02:00"  # 2 AM UTC
      point_in_time_recovery_enabled = true
      transaction_log_retention_days = 7
    }

    ip_configuration {
      ipv4_enabled                                  = false
      private_network                               = google_compute_network.main.id
      enable_private_path_for_google_cloud_services = true
    }

    database_flags {
      name  = "max_connections"
      value = "100"
    }
  }

  deletion_protection = true  # Prevent accidental deletion
}

resource "google_sql_database" "default" {
  for_each = local.region_config

  name     = "medical_db"
  instance = google_sql_database_instance.main[each.key].name
}

resource "google_sql_user" "default" {
  for_each = local.region_config

  name     = "medical_user"
  instance = google_sql_database_instance.main[each.key].name
  password = random_password.db_password[each.key].result
}

resource "random_password" "db_password" {
  for_each = local.region_config

  length  = 32
  special = true
}

Database Connection

Connection String (injected via environment variable):

postgresql+psycopg2://medical_user:PASSWORD@PRIVATE_IP/medical_db
  • Private IP only - No public IP (HIPAA compliance)
  • VPC Connector - Cloud Run → VPC → Cloud SQL
  • SSL/TLS - Encrypted in transit
  • Automatic Backups - Daily at 2 AM UTC, 7-day retention

Migrations

Managed with Alembic:

bash
# Create migration
alembic revision --autogenerate -m "description"

# Apply migrations
alembic upgrade head

# Rollback
alembic downgrade -1

Note: Migrations run automatically on Cloud Run startup via migrate.sh script.


Networking & VPC

VPC Configuration

File: terraform/network.tf

hcl
# VPC Network
resource "google_compute_network" "main" {
  name                    = "noumaris-vpc-${terraform.workspace}"
  auto_create_subnetworks = false
  project                 = var.gcp_project_ids[terraform.workspace]
}

# Subnets (one per region)
resource "google_compute_subnetwork" "main" {
  for_each = local.region_config

  name          = "noumaris-subnet-${terraform.workspace}-${each.key}"
  ip_cidr_range = each.key == "ca" ? "10.8.0.0/28" : "10.9.0.0/28"
  region        = each.value.gcp_region
  network       = google_compute_network.main.id
  project       = var.gcp_project_ids[terraform.workspace]

  private_ip_google_access = true
}

# VPC Connectors (Cloud Run → VPC)
resource "google_vpc_access_connector" "main" {
  for_each = local.region_config

  name          = "vpc-connector-${terraform.workspace}-${each.key}"
  region        = each.value.gcp_region
  network       = google_compute_network.main.name
  ip_cidr_range = each.key == "ca" ? "10.8.0.0/28" : "10.9.0.0/28"
  project       = var.gcp_project_ids[terraform.workspace]
}

# Private Service Connection (for Cloud SQL)
resource "google_compute_global_address" "private_ip_address" {
  name          = "private-ip-address-${terraform.workspace}"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 16
  network       = google_compute_network.main.id
  project       = var.gcp_project_ids[terraform.workspace]
}

resource "google_service_networking_connection" "private_vpc_connection" {
  network                 = google_compute_network.main.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
}

Load Balancer

File: terraform/lb.tf

hcl
# Global HTTP(S) Load Balancer
resource "google_compute_global_forwarding_rule" "https" {
  name       = "https-lb-${terraform.workspace}"
  target     = google_compute_target_https_proxy.default.id
  port_range = "443"
  ip_address = google_compute_global_address.default.address
  project    = var.gcp_project_ids[terraform.workspace]
}

# Backend Service (routes to Cloud Run)
resource "google_compute_backend_service" "backend" {
  name                  = "backend-service-${terraform.workspace}"
  protocol              = "HTTP"
  port_name             = "http"
  timeout_sec           = 300
  enable_cdn            = false
  load_balancing_scheme = "EXTERNAL_MANAGED"
  project               = var.gcp_project_ids[terraform.workspace]

  backend {
    group = google_compute_region_network_endpoint_group.backend_neg["ca"].id
  }

  backend {
    group = google_compute_region_network_endpoint_group.backend_neg["us"].id
  }

  log_config {
    enable      = true
    sample_rate = 1.0
  }
}

# Network Endpoint Groups (one per region)
resource "google_compute_region_network_endpoint_group" "backend_neg" {
  for_each = local.region_config

  name                  = "backend-neg-${terraform.workspace}-${each.key}"
  network_endpoint_type = "SERVERLESS"
  region                = each.value.gcp_region
  project               = var.gcp_project_ids[terraform.workspace]

  cloud_run {
    service = google_cloud_run_v2_service.backend[each.key].name
  }
}

Load Balancer Features:

  • Global - Single IP, routes to nearest region
  • HTTPS - TLS termination at load balancer
  • Health Checks - Automatic failover if region unhealthy
  • Logging - Request logs for debugging

Monitoring & Logging

Cloud Logging

All logs from Cloud Run are automatically sent to Cloud Logging:

bash
# View logs
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=backend-dev-ca" --limit 50

# Tail logs (real-time)
gcloud logging tail "resource.type=cloud_run_revision AND resource.labels.service_name=backend-dev-ca"

# Filter errors
gcloud logging read "resource.type=cloud_run_revision AND severity>=ERROR" --limit 50

Cloud Monitoring

Health Check Dashboard:

  • Request count
  • Response latency (P50, P95, P99)
  • Error rate
  • Instance count
  • CPU/Memory usage

Alerts:

  • Error rate >5% for 5 minutes
  • Latency P95 >1s for 5 minutes
  • Instance count >2 (unexpected scale-up)

Application Metrics

python
# backend/src/noumaris_backend/api/main.py
import logging
from google.cloud import logging as cloud_logging

# Initialize Cloud Logging
client = cloud_logging.Client()
client.setup_logging()

logger = logging.getLogger(__name__)

@app.get("/health")
async def health():
    logger.info("Health check called")
    return {"status": "healthy"}

@app.post("/documents/new")
async def create_document():
    logger.info("Creating new document")
    # ... business logic
    logger.info("Document created successfully", extra={"document_id": doc.id})

Security

1. Network Security

  • Private Database - No public IP, VPC-only access
  • VPC Connectors - Isolated network for Cloud Run
  • Load Balancer - HTTPS termination, DDoS protection
  • Firewall Rules - Ingress only via load balancer

2. Secret Management

All sensitive data stored in Secret Manager:

hcl
# terraform/secrets.tf
resource "google_secret_manager_secret" "application_secrets" {
  for_each = toset([
    "anthropic-api-key",
    "deepgram-api-key",
    "keycloak-realm",
    "keycloak-client-id",
  ])

  secret_id = each.key
  project   = var.gcp_project_ids[terraform.workspace]

  replication {
    auto {}
  }
}

Accessing secrets:

python
from google.cloud import secretmanager

client = secretmanager.SecretManagerServiceClient()
name = f"projects/PROJECT_ID/secrets/anthropic-api-key/versions/latest"
response = client.access_secret_version(request={"name": name})
api_key = response.payload.data.decode("UTF-8")

3. IAM & Service Accounts

  • Cloud Run Service Account - Minimal permissions (Cloud SQL, Secret Manager)
  • Cloud Build Service Account - Deploy permissions only
  • Principle of Least Privilege - No broad roles/editor grants

4. HIPAA Compliance

  • Encryption at Rest - All data encrypted (Cloud SQL, Secret Manager)
  • Encryption in Transit - TLS 1.2+ for all connections
  • Audit Logging - All API calls logged
  • Private Networking - No public database access
  • BAA with Google - Business Associate Agreement signed

Cost Management

Current Monthly Costs (Development)

ServiceUsageCost
Cloud Run~5k requests/day, 0-1 instances$0-5/month
Cloud SQLdb-f1-micro (2 instances)$15/month
VPC Connectors2 connectors$15/month
Load BalancerMinimal traffic$5/month
Cloud Build~30 builds/month$0 (free tier)
Secret Manager10 secrets$0.36/month
Total~$35-40/month

Cost Optimization Strategies

  1. Cloud Run Scale-to-Zero - No cost when idle
  2. db-f1-micro - Smallest Cloud SQL tier ($7.50/month per instance)
  3. Artifact Registry - Only store last 10 images
  4. Cloud Build - Free tier covers development usage
  5. VPC Connectors - Shared across services

Production Cost Estimate

ServiceUsageCost
Cloud Run~100k requests/day, 1-5 instances$50-100/month
Cloud SQLdb-g1-small (2 instances)$50/month
VPC Connectors2 connectors$15/month
Load BalancerModerate traffic$20/month
Cloud Build~100 builds/month$10/month
Total~$145-195/month

External Service Costs

ServiceUsageCost
Anthropic Claude~10k requests/month (Sonnet 4)$30-50/month
Deepgram~50 hours/month transcription$20-30/month
Cloudflare PagesUnlimited requests$0/month (free)
Total~$50-80/month

Total Infrastructure + External: $195-275/month (production)


Summary

The Noumaris infrastructure is:

  1. Multi-Region - Canada and US for low latency
  2. Serverless - Cloud Run scales automatically
  3. Secure - Private networking, encryption, IAM
  4. Cost-Efficient - Scale to zero, minimal idle cost
  5. Automated - Terraform IaC, Cloud Build CI/CD
  6. HIPAA-Ready - Encryption, audit logs, BAA

Key Technologies:

  • Compute: Cloud Run (serverless containers)
  • Database: Cloud SQL PostgreSQL (private IP)
  • Networking: VPC, Load Balancer, VPC Connectors
  • CI/CD: Cloud Build
  • IaC: Terraform
  • Monitoring: Cloud Logging, Cloud Monitoring
  • Security: Secret Manager, IAM

Related Documentation:

Internal documentation for Noumaris platform