Deploy the full db-router stack (PostgreSQL + MongoDB + Redis + gRPC router) on a single VM, on the cloud of your choice. DNS is always managed in Cloudflare.
Prefer the automated deployer? See deployer/ — one
docker runhandles Terraform + Ansible end-to-end with zero manual steps.
| Provider | Root dir | Compute credentials (env) | Defaults (region / size / image) |
|---|---|---|---|
| DigitalOcean | digitalocean/ |
DIGITALOCEAN_TOKEN |
blr1 / s-1vcpu-2gb / debian-13-x64 |
| Linode | linode/ |
LINODE_TOKEN |
ap-south / g6-nanode-1 / linode/debian12 |
| Hetzner | hetzner/ |
HCLOUD_TOKEN |
nbg1 / cx22 / debian-12 |
| AWS | aws/ |
AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY |
us-east-1 / t3.small / latest Debian 12 AMI |
| GCP | gcp/ |
GOOGLE_CREDENTIALS + GOOGLE_PROJECT |
us-central1-a / e2-small / debian-cloud/debian-12 |
| Azure | azure/ |
ARM_CLIENT_ID + ARM_CLIENT_SECRET + ARM_SUBSCRIPTION_ID + ARM_TENANT_ID |
eastus / Standard_B1ms / Debian 12 |
DNS (all providers): CLOUDFLARE_API_TOKEN.
Each provider has a thin root directory that wires three shared modules:
providers/
├── modules/
│ ├── secrets/ # auto-generated DB passwords + credentials summary
│ ├── dns-cloudflare/ # Cloudflare A records (DNS-only / proxied=false)
│ └── compute/<cloud>/ # the VM + firewall/security-group + SSH key
└── <cloud>/ # root: provider blocks + module wiring + outputs
Every compute/<cloud> module exposes the same contract — inputs
(server_name, region, instance_size, image, ssh_public_key,
allowed_ips, tags) and outputs (server_ip, server_id, ssh_user) — so
the roots are near-identical and the deployer is cloud-agnostic.
All roots expose the same normalized outputs: server_ip, ssh_user,
fqdn, grpc_endpoint, postgres_password, mongo_password,
redis_password, credentials_summary.
- Terraform >= 1.5
- An account + API credentials for your chosen cloud (see table above)
- A domain whose DNS is managed in Cloudflare + a
CLOUDFLARE_API_TOKENwithZone:ReadandDNS:Editon that zone - An SSH key (or use the deployer container, which auto-generates one)
Pick your provider's directory and set the matching credentials. Example for Hetzner:
export HCLOUD_TOKEN="xxxxxxxx"
export CLOUDFLARE_API_TOKEN="cf_xxxxxxxx"
cd providers/hetzner
cp terraform.tfvars.example terraform.tfvars # edit allowed_ips, domain, ssh_public_key
terraform init
terraform plan
terraform applyDigitalOcean instead? cd providers/digitalocean and export DIGITALOCEAN_TOKEN=.... The flow is identical for every provider.
Then run Ansible to configure the server, or use the deployer container which does both automatically.
ssh $(terraform output -raw ssh_user)@$(terraform output -raw server_ip)
grpcurl \
-cacert certs/ca.crt \
-cert certs/client.crt \
-key certs/client.key \
$(terraform output -raw fqdn):443 dbrouter.HealthService/CheckThese exist in every root (provider-specific compute defaults differ — see the table):
| Variable | Default | Description |
|---|---|---|
server_name |
db-router |
Instance name |
region |
(per cloud) | Region / location / zone |
instance_size |
(per cloud) | Instance size/type |
image |
(per cloud) | OS image (Debian 12/13) |
ssh_public_key |
"" |
Public key uploaded to the server (required for manual use) |
domain |
0.xeze.org |
Base domain |
subdomain |
db |
Subdomain (→ db.0.xeze.org) |
cloudflare_zone |
xeze.org |
Cloudflare zone (registered domain) managing DNS |
cloudflare_zone_id |
"" |
Zone ID; looked up by cloudflare_zone when empty |
postgres_user |
admin |
PG username |
postgres_db |
unified_db |
Default PG database |
mongo_user |
admin |
Mongo username |
allowed_ips |
["0.0.0.0/0","::/0"] |
CIDRs allowed to reach SSH — set to your IP! |
grpc_port |
50051 |
gRPC port (internal; fronted by Caddy on 443) |
Passwords are auto-generated. View them with
terraform output credentials_summary.
Records are created DNS-only (proxied = false, grey cloud) on purpose:
Caddy terminates TLS and enforces mTLS at the edge, gRPC runs over h2c, and
Let's Encrypt needs a direct path to the origin — Cloudflare's proxy would
break all three. The module creates A records for <subdomain>.<domain> and
grpc.<subdomain>.<domain>.
- Set
allowed_ipsto your IP/office CIDR — e.g.["203.0.113.5/32"]. - Enable mTLS (the deployer sets
enable_mtls = true) — only clients with a signed cert can connect. - Remote state: add an
s3/gcs/azurermbackend for team usage.
cd providers/<provider>
terraform destroy