|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +This is a Terraform-based infrastructure-as-code (IaC) project that provisions and manages a Kubernetes cluster on DigitalOcean with supporting services, using a GitOps approach via ArgoCD. |
| 8 | + |
| 9 | +## Common Commands |
| 10 | + |
| 11 | +### Terraform Format & Validate |
| 12 | + |
| 13 | +```bash |
| 14 | +# Format check |
| 15 | +terraform -chdir=environments/prod/infra fmt -check |
| 16 | +terraform -chdir=environments/prod/k8s fmt -check |
| 17 | + |
| 18 | +# Auto-format |
| 19 | +terraform -chdir=environments/prod/infra fmt -recursive |
| 20 | +terraform -chdir=environments/prod/k8s fmt -recursive |
| 21 | + |
| 22 | +# Validate |
| 23 | +terraform -chdir=environments/prod/infra validate |
| 24 | +terraform -chdir=environments/prod/k8s validate |
| 25 | +``` |
| 26 | + |
| 27 | +### Terraform Plan & Apply |
| 28 | + |
| 29 | +```bash |
| 30 | +# Init (required after module changes or first run) |
| 31 | +terraform -chdir=environments/prod/infra init |
| 32 | +terraform -chdir=environments/prod/k8s init |
| 33 | + |
| 34 | +# Plan |
| 35 | +terraform -chdir=environments/prod/infra plan |
| 36 | +terraform -chdir=environments/prod/k8s plan |
| 37 | + |
| 38 | +# Apply (infra must be applied before k8s) |
| 39 | +terraform -chdir=environments/prod/infra apply |
| 40 | +terraform -chdir=environments/prod/k8s apply -var="kubeconfig=<kubeconfig_path>" |
| 41 | +``` |
| 42 | + |
| 43 | +## Architecture |
| 44 | + |
| 45 | +### Two-Layer Terraform Structure |
| 46 | + |
| 47 | +The project is split into two sequentially-dependent layers under `environments/prod/`: |
| 48 | + |
| 49 | +1. **`infra/`** — Provisions DigitalOcean infrastructure: VPC (10.0.4.0/22), reserved IP, SSH keys, and the K8s control plane droplet (Ubuntu 22.04, 2vCPU/4GB, SFO2). Outputs a kubeconfig used by the k8s layer. |
| 50 | + |
| 51 | +2. **`k8s/`** — Deploys all Kubernetes workloads using Kubernetes, Helm, kubectl, GitHub, and Cloudflare providers. Depends on the kubeconfig output from the infra layer. This layer also manages Cloudflare DNS records. |
| 52 | + |
| 53 | +The k8s layer receives the kubeconfig by reading infra state output (automated in CI via `terraform -chdir=environments/prod/infra output -raw kubeconfig`). |
| 54 | + |
| 55 | +### Reusable Modules (`modules/`) |
| 56 | + |
| 57 | +All infrastructure logic lives in reusable modules. The k8s environment wires them together: |
| 58 | + |
| 59 | +| Module | Purpose | |
| 60 | +|--------|---------| |
| 61 | +| `controlplane` | Self-managed K8s via cloud-init on a DigitalOcean droplet | |
| 62 | +| `argocd` | GitOps CD platform (Helm chart), syncs from `m8rmclaren/infra-gitops` repo | |
| 63 | +| `argo_helm_app` | Generic wrapper for deploying any Helm app via ArgoCD AppProject | |
| 64 | +| `auth` | Ory Kratos (identity) + Ory Hydra (OAuth2/OIDC) with Apple SIWA support | |
| 65 | +| `cert_manager` | Let's Encrypt via cert-manager with Cloudflare DNS-01 challenge | |
| 66 | +| `database` | PostgreSQL with replication via Helm/ArgoCD | |
| 67 | +| `domain` | Cloudflare DNS A record management | |
| 68 | +| `external_dns` | Automatic DNS updates from K8s service annotations | |
| 69 | +| `gateway` | Kubernetes Gateway API config | |
| 70 | +| `ghcr_pull_secret` | GHCR image pull secrets for private container images | |
| 71 | +| `health_auto_export_server` | Health metrics auto-export server | |
| 72 | +| `istio` | Service mesh | |
| 73 | +| `website` | Website deployment via ArgoCD | |
| 74 | + |
| 75 | +### GitOps Pattern |
| 76 | + |
| 77 | +- **App definitions** live in this repo (Terraform manages ArgoCD `Application` resources) |
| 78 | +- **App manifests/Helm values** live in `m8rmclaren/infra-gitops` (synced by ArgoCD) |
| 79 | +- This means Terraform provisions the ArgoCD `Application` CRD, ArgoCD handles actual K8s deployment |
| 80 | + |
| 81 | +### State Management |
| 82 | + |
| 83 | +- Backend: AWS S3 (`m8rmclaren-terraform-state-infra`, us-west-1) with encryption |
| 84 | +- Separate state files for `infra` and `k8s` layers |
| 85 | +- Auth: GitHub Actions uses AWS OIDC (`arn:aws:iam::691595548805:role/github_actions_full_admin`) |
| 86 | + |
| 87 | +### CI/CD |
| 88 | + |
| 89 | +- **`plan.yml`**: Runs on PRs — formats, validates, and plans both layers; posts results as sticky PR comments |
| 90 | +- **`apply.yml`**: Runs on push to `main` — applies infra first, then k8s. Includes fresh-cluster detection logic that taints resources on first apply (checks namespace count to determine if cluster is new) |
| 91 | + |
| 92 | +### Key Secrets (managed in GitHub Actions environment `prod`) |
| 93 | + |
| 94 | +`DIGITALOCEAN_TOKEN`, `CLOUDFLARE_API_TOKEN`, `GH_PAT`, `EMAIL`, `DOMAIN`, `POSTGRES_ADMIN_PASSWORD`, `POSTGRES_REPLICATION_PASSWORD`, `HYDRA_DATABASE_PASSWORD`, `HYDRA_SYSTEM_SECRET`, `HYDRA_COOKIE_SECRET`, `KRATOS_DATABASE_PASSWORD`, `APPLE_DEVELOPER_TEAM_ID`, `CHAT_PRIMARY_APP_ID` |
| 95 | + |
| 96 | +## Bootstrap |
| 97 | + |
| 98 | +To initialize the S3 state backend (one-time setup): |
| 99 | + |
| 100 | +```bash |
| 101 | +aws s3api create-bucket \ |
| 102 | + --bucket m8rmclaren-terraform-state-infra \ |
| 103 | + --region us-west-1 \ |
| 104 | + --create-bucket-configuration LocationConstraint=us-west-1 |
| 105 | +``` |
| 106 | + |
| 107 | +If the bucket name or region is changed, update both `backend.tf` files accordingly. |
0 commit comments