Infrastructure
All ObsidianPalace infrastructure is managed by Terraform, following the directories-over-workspaces pattern. State is stored in Terraform Cloud.
Directory Structure
terraform/
├── environments/
│ └── prod/
│ ├── versions.tf # Terraform Cloud backend, provider versions
│ ├── provider.tf # Google provider configuration
│ ├── variables.tf # Root variables (pass-through to module)
│ ├── main.tf # Module instantiation + health check
│ ├── outputs.tf # Root outputs
│ └── backend.tf # Commented-out GCS backend alternative
└── modules/
└── obsidian_palace/
├── versions.tf # Module provider requirements
├── variables.tf # Module input variables
├── apis.tf # GCP API enablement
├── secrets.tf # Secret Manager resources + IAM
├── network.tf # Static IP, firewall rules
├── compute.tf # Service account, persistent disk, GCE instance
├── registry.tf # Artifact Registry Docker repository
└── outputs.tf # Module outputs
Resources Provisioned
APIs Enabled
| API |
Purpose |
compute.googleapis.com |
GCE instances, disks, firewall, static IPs |
secretmanager.googleapis.com |
Secret storage and retrieval |
artifactregistry.googleapis.com |
Docker image registry |
iam.googleapis.com |
Service account and IAM management |
Secret Manager
Four secrets are created, each with IAM bindings granting the GCE service account roles/secretmanager.secretAccessor:
| Secret |
Content |
obsidian-palace-google-oauth-client-id |
Google OAuth client ID |
obsidian-palace-google-oauth-client-secret |
Google OAuth client secret |
obsidian-palace-allowed-email |
Authorized Google account email |
obsidian-palace-anthropic-api-key |
Anthropic API key for AI placement |
Networking
| Resource |
Configuration |
| Static IP |
Regional external IP |
| Firewall: HTTPS |
Port 443 from all IPs (OAuth handles auth) |
| Firewall: HTTP |
Port 80 from all IPs (Let's Encrypt ACME only) |
| Firewall: SSH |
Port 22 from specified CIDRs only (optional) |
Compute
| Resource |
Configuration |
| Service Account |
obsidian-palace@YOUR_PROJECT_ID.iam.gserviceaccount.com |
| IAM Roles |
Artifact Registry Reader, Log Writer, Metric Writer |
| Boot Disk |
10 GB pd-standard, Container-Optimized OS |
| Data Disk |
20 GB pd-standard, mounted at /mnt/disks/data |
| Machine Type |
e2-small (2 vCPU, 2 GB RAM) |
DNS
DNS is managed at your domain registrar, not via GCP Cloud DNS. You create a single A record pointing your domain at the static IP output by Terraform.
| Record Type |
Name |
Value |
| A |
Your subdomain (e.g., vault) |
Static IP from terraform output instance_ip |
Artifact Registry
| Resource |
Configuration |
| Repository |
obsidian-palace (Docker format) |
| Cleanup |
Keeps the 5 most recent tagged images |
Post-Deploy Health Check
The root module includes a check {} block that validates the service is reachable after deployment:
check "health" {
data "http" "health" {
url = "https://YOUR_DOMAIN/health"
retry {
attempts = 3
min_delay_ms = 5000
}
}
assert {
condition = data.http.health.status_code == 200
error_message = "ObsidianPalace health endpoint did not return 200."
}
}
Startup Sequence
The GCE instance runs a startup script (COS-compatible -- no apt-get) that:
- Mounts the persistent disk at
/mnt/disks/data (formats on first boot)
- Creates data directories (
vault, chromadb, obsidian-config, letsencrypt)
- Pulls secrets from Secret Manager via the
gcloud Docker image (COS has no gcloud on the host)
- Runs certbot in Docker for Let's Encrypt SSL (first boot only)
- Symlinks certificates to a stable path the container's nginx config expects
- Configures Docker auth for Artifact Registry (writes config to the data disk since COS root is read-only)
- Pulls and starts the Docker container with all env vars and volume mounts
- Writes a cert renewal script to the data disk for scheduled execution