# Infrastructure & Deployment Architecture ## Overview LunarFront runs on DigitalOcean. Each customer is a fully isolated deployment — their own Kubernetes namespace, their own database. There is no multi-tenancy in the application layer. **Stack:** DOKS (Kubernetes) · ArgoCD · Helm · Gitea · Terraform · Ansible The guiding principle is simplicity — Docker Compose on Droplets for Gitea, Kubernetes for customer app instances. No surprise bills. --- ## Monthly Cost | Resource | Spec | Cost | |---|---|---| | DOKS cluster | Control plane | $12/mo | | DOKS nodes (2x s-2vcpu-4gb) | Runs all customer apps | ~$48/mo | | Managed Postgres (shared) | All customer databases | $15-25/mo | | Managed Redis (shared) | All customer queues/cache | $15/mo | | Gitea Droplet | 1 vCPU, 1GB RAM | $6/mo | | Gitea Postgres | Dedicated managed Postgres | $15/mo | | Spaces | Registry, backups, files | ~$21/mo | | **Fixed total** | | **~$132-142/mo** | | **Per customer** | New database in shared Postgres | **~$0 marginal** | --- ## Environments | Environment | Hostname | Purpose | |---|---|---| | Production (per customer) | `{customer}.app.lunarfront.tech` | Live customer instance on DOKS | | Gitea | `git.lunarfront.tech` | Source control, CI/CD, container registry | | Dev | Local / feature namespace on DOKS | Testing and staging | --- ## DNS Managed through Cloudflare. Records defined in Terraform. | Record | Type | Points To | |---|---|---| | `git.lunarfront.tech` | A | Gitea Droplet | | `git-ssh.lunarfront.tech` | A | Gitea Droplet (SSH port 2222) | | `registry.lunarfront.tech` | A | Gitea Droplet (container registry) | | `{customer}.app.lunarfront.tech` | CNAME | DOKS load balancer | --- ## Infrastructure (Terraform) Terraform manages all DigitalOcean and Cloudflare resources. State stored in Spaces. ``` /terraform main.tf providers, backend (Spaces state) variables.tf cluster.tf DOKS cluster + node pools databases.tf shared Postgres, shared Redis gitea.tf Gitea Droplet spaces.tf files, backups, tf-state buckets dns.tf Cloudflare DNS records outputs.tf cluster endpoint, DB URLs terraform.tfvars secrets (gitignored) ``` ### State Backend ```hcl terraform { backend "s3" { endpoint = "https://nyc3.digitaloceanspaces.com" bucket = "lunarfront-terraform-state" key = "terraform.tfstate" region = "us-east-1" skip_credentials_validation = true skip_metadata_api_check = true skip_region_validation = true force_path_style = true } } ``` --- ## Gitea (Source Control + CI/CD) Gitea runs on its own Droplet with a dedicated managed Postgres. It hosts: - Source code repositories - Gitea Actions CI/CD pipelines - Docker container registry (`registry.lunarfront.tech`) Managed by Ansible. See `lunarfront-infra/ansible/roles/gitea/`. ### CI Pipeline (Gitea Actions) On push to `main`: 1. Run lint + unit tests using the shared `ci-runner` image 2. Build Docker image, push to `registry.lunarfront.tech/ryan/lunarfront-app:{sha}` 3. Update the Helm chart `values.yaml` with the new image tag 4. ArgoCD detects the change and syncs all customer deployments --- ## Kubernetes (DOKS + ArgoCD) All customer app instances run on a single DOKS cluster managed by ArgoCD (GitOps). ### Repository Structure ``` /gitops apps/ customer-acme/ values.yaml customer-foo/ values.yaml chart/ Chart.yaml templates/ deployment.yaml service.yaml ingress.yaml job-migrate.yaml # runs drizzle-kit migrate on deploy secret.yaml ``` ### Per-Customer values.yaml ```yaml customer: acme subdomain: acme image: repository: registry.lunarfront.tech/ryan/lunarfront-app tag: "abc123" database: url: "postgresql://lunarfront:pass@db-host:25060/customer_acme?sslmode=require" redis: url: "rediss://..." env: JWT_SECRET: "..." NODE_ENV: production ``` ### Adding a New Customer 1. `CREATE DATABASE customer_x;` on the shared managed Postgres 2. Add `gitops/apps/customer-x/values.yaml` 3. Push — ArgoCD syncs, migration Job runs, instance is live 4. Add DNS CNAME in Terraform --- ## Database Strategy One managed Postgres cluster shared across all customers. Each customer gets their own isolated database (`CREATE DATABASE customer_x`). No cross-customer queries are possible at the database level. - New customer = new database, no new infrastructure cost - Managed DO Postgres handles backups, failover, and SSL - Resize the cluster as total load grows --- ## Day-to-Day Workflow ```bash # Provision infrastructure from scratch terraform init && terraform apply # Configure Gitea server ansible-playbook ansible/gitea.yml -i ansible/inventory.ini # Normal deploy flow — push to main git push origin main # → Gitea Actions: lint + test + build image # → Image pushed to registry # → Helm chart values updated # → ArgoCD syncs all customer deployments automatically # Add a new customer # 1. Create database psql $DATABASE_URL -c "CREATE DATABASE customer_x;" # 2. Add values file, push git add gitops/apps/customer-x/values.yaml && git push # Done — ArgoCD handles the rest ```