- Add production Dockerfile with bun build --compile, multi-stage Alpine build - Add .dockerignore - Swap bcrypt -> bcryptjs (pure JS, no native addons) - Add programmatic migrations on startup via drizzle migrator - Add /v1/version endpoint with APP_VERSION baked in at build time - Add .gitea/workflows/ci.yml (lint + test with postgres/valkey services) - Add .gitea/workflows/build.yml (version bump, build, push to registry) - Update CLAUDE.md and docs/architecture.md to remove multi-tenancy - Add docs/deployment.md covering DOKS + ArgoCD architecture Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
194 lines
5.2 KiB
Markdown
194 lines
5.2 KiB
Markdown
# 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
|
|
```
|