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
- Overview
- Infrastructure Diagram
- Local Development Stack
- Docker Configuration
- Terraform Infrastructure
- Google Cloud Run Deployment
- CI/CD with Cloud Build
- Database Management
- Networking & VPC
- Monitoring & Logging
- Security
- 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
- Local Development - Docker Compose (PostgreSQL, Keycloak)
- Cloud Infrastructure - Terraform (Cloud Run, Cloud SQL, VPC, Load Balancer)
- CI/CD - Cloud Build (automated builds and deployments)
- Monitoring - Cloud Logging, Cloud Monitoring, error reporting
Infrastructure Diagram
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| LBLocal Development Stack
Docker Compose Configuration
File: docker-compose.yml
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: bridgeQuick Start Commands
# 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 -vService Endpoints
| Service | URL | Credentials |
|---|---|---|
| PostgreSQL | localhost:5433 | medical_user / password123 |
| Keycloak | http://localhost:8081 | admin / admin |
| Backend | http://localhost:8000 | N/A (start manually) |
| Frontend | http://localhost:5173 | N/A (start manually) |
Keycloak Setup with Terraform
Script: scripts/setup-local-keycloak.sh
This script automates the configuration of local Keycloak using Terraform:
#!/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
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 20for live transcription - Proxy headers - Required for Cloud Run
Build Locally
cd backend
docker build -t noumaris-backend:latest .
docker run -p 8000:8080 --env-file .env noumaris-backend:latestTerraform 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 lockWorkspaces
Terraform workspaces manage multiple environments:
- dev - Development environment
- staging - Staging environment (future)
- prod - Production environment (future)
# List workspaces
terraform workspace list
# Switch workspace
terraform workspace select dev
# Create new workspace
terraform workspace new stagingRegion Configuration
Multi-region setup defined in locals.tf:
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
# 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 outputGoogle Cloud Run Deployment
Backend Service Configuration
File: terraform/services.tf
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
- Scale to Zero - No cost when idle
- Concurrency - 80 concurrent requests per instance
- Health Checks - Startup and liveness probes
- Secrets - API keys from Secret Manager
- Private Database - VPC connector for Cloud SQL
- Timeout - 300s for long-running operations (transcription, note generation)
Service Account Permissions
# 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
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: 50Build Trigger
# 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
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 successfulDeployment Time: ~5 minutes (build + deploy)
Database Management
Cloud SQL Configuration
File: terraform/database.tf
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:
# Create migration
alembic revision --autogenerate -m "description"
# Apply migrations
alembic upgrade head
# Rollback
alembic downgrade -1Note: Migrations run automatically on Cloud Run startup via migrate.sh script.
Networking & VPC
VPC Configuration
File: terraform/network.tf
# 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
# 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:
# 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 50Cloud 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
# 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:
# 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:
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/editorgrants
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)
| Service | Usage | Cost |
|---|---|---|
| Cloud Run | ~5k requests/day, 0-1 instances | $0-5/month |
| Cloud SQL | db-f1-micro (2 instances) | $15/month |
| VPC Connectors | 2 connectors | $15/month |
| Load Balancer | Minimal traffic | $5/month |
| Cloud Build | ~30 builds/month | $0 (free tier) |
| Secret Manager | 10 secrets | $0.36/month |
| Total | ~$35-40/month |
Cost Optimization Strategies
- Cloud Run Scale-to-Zero - No cost when idle
- db-f1-micro - Smallest Cloud SQL tier ($7.50/month per instance)
- Artifact Registry - Only store last 10 images
- Cloud Build - Free tier covers development usage
- VPC Connectors - Shared across services
Production Cost Estimate
| Service | Usage | Cost |
|---|---|---|
| Cloud Run | ~100k requests/day, 1-5 instances | $50-100/month |
| Cloud SQL | db-g1-small (2 instances) | $50/month |
| VPC Connectors | 2 connectors | $15/month |
| Load Balancer | Moderate traffic | $20/month |
| Cloud Build | ~100 builds/month | $10/month |
| Total | ~$145-195/month |
External Service Costs
| Service | Usage | Cost |
|---|---|---|
| Anthropic Claude | ~10k requests/month (Sonnet 4) | $30-50/month |
| Deepgram | ~50 hours/month transcription | $20-30/month |
| Cloudflare Pages | Unlimited requests | $0/month (free) |
| Total | ~$50-80/month |
Total Infrastructure + External: $195-275/month (production)
Summary
The Noumaris infrastructure is:
- Multi-Region - Canada and US for low latency
- Serverless - Cloud Run scales automatically
- Secure - Private networking, encryption, IAM
- Cost-Efficient - Scale to zero, minimal idle cost
- Automated - Terraform IaC, Cloud Build CI/CD
- 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: