unoverse deploy drives the first two, and these runbooks cover the third:
- Provision: your ground (
infra/digitaloceanorinfra/aws) creates the VM, load balancer, TLS certificate, firewall, Postgres, and Redis, and renders the complete production configuration. - Deploy: the platform is installed onto what was built, migrations included.
- Operate: database, hardening, health, restarts: the runbooks below.
Provision (Terraform, driven by the CLI)
terraform.tfvars, plans, and applies what you approve. Terraform is still the
interface underneath, and a platform team can drive it directly instead
(cd infra/digitalocean && terraform init && terraform apply); deploy then picks up from
the applied ground.
Everything infrastructure is the ground’s job and never a runbook’s: TLS (DO managed Let’s Encrypt / AWS ACM at the load balancer, no proxy software on the VM), DNS records, the cloud firewall (SSH and Dozzle admin-IP-only), Postgres (fresh, adopted, or BYO, see database), and Redis (always provisioned, TLS).
Sizes
size in terraform.tfvars scales the box and the stores, never the topology (all sizes are single-VM). When multi-VM Active-Active arrives it will scale the app tier only: UMAP stays one shared service (UMAP_SERVICE_URL), because spatial coordinates are only comparable through the same trained model instance.
External Dependencies
Supported Platforms
- Cloud grounds: DigitalOcean (
infra/digitalocean), AWS (infra/aws) - On-prem: any Ubuntu 22.04+ / Debian 12 VM, you own firewall and TLS, then Deploy and Operate are identical
Deploy (the CLI)
init stays available on its own for re-runs: deploy db, deploy test.
The CLI reads the deploy target from your ground’s rendered configuration and generates a temporary Ansible inventory on every run, so there is no inventory file to maintain.
Your own work (nodes, design, prompts) never rides a deploy: it arrives via Studio publish (over the API, live immediately) or the Marketplace (per item, database-driven).
Operate (the runbooks)
Logs need no runbook: Dozzle runs by default at
http://<VM_IP>:8080 (admin-IP-only via the cloud firewall), streams straight from the Docker socket, and stores nothing. Log growth is capped by json-file rotation (10 MB × 3 per service) in docker-compose.yml. Enterprise ships logs to its own SIEM by pointing the Docker logging driver there instead.
Environment: One File You Write, One You Don’t
.env is yours: local development only. Copy .env.example, set localhost Postgres, Redis, your OpenAI key, and your OIDC values (or AUTH_ENABLED=false). Docker compose reads it automatically. Gitignored.
Production configuration is not a file you touch. Your terraform.tfvars is the single input; everything downstream is machine-managed:
.env.production (gitignored) as a deploy artifact: unoverse deploy re-renders it from your applied ground whenever it’s missing. Two things are worth knowing about it, and only two:
- It holds the master
CREDENTIAL_ENCRYPTION_KEY. Keep a safe copy with your database backups: a database backup is unreadable without it. - Never edit it. To change any production value, edit terraform.tfvars,
terraform apply, delete the file, and redeploy.
DOMAIN drives Canvas URLs:
When DOMAIN=yourdomain.com is set, docker-compose.yml automatically derives:
VITE_API_URL=https://api.yourdomain.comVITE_SERVER_WS_URL=wss://api.yourdomain.com
DOMAIN is unset (local dev), set API_URL=http://localhost:4105 in .env, Canvas calls the platform’s public listener (unoverse :4105) directly.
Prerequisites
- Terraform 1.5+ and your cloud CLI (doctl or aws) on your machine
- Ansible installed locally (
pip install ansible) - DOCR token for pulling images (from your Unoverse admin)

