Skip to content

Deploy to production

This guide brings up the committed prod environment on your Mac: two public static nodes, no Lima, no roaming hosts in the inventory.

Host Role
static-1 Ubuntu 26.04 public endpoint (mesh hub, x86_64)
static-2 Ubuntu 26.04 public endpoint (x86_64)

Optionally, add a roaming node later with Adding a roaming node—after statics are up.

Set hostname and fill public IPs and SSH fingerprints for both static nodes.

Edit inventories/prod/hosts.yml:

  1. Set all.vars.hostname (for example example.com). Nameserver, API, app, and app URLs are derived from that name.
  2. Fill placeholders for static-1 and static-2 (public IP and SSH host-key fingerprint). Install this public key on each VM with static IP (ops user)—same prep as Adding a public node. Go and do that first.
---
all:
vars:
project: example
hostname: example.com
nodes:
hosts:
static-1:
public_ip: "203.0.113.11"
ssh_ed25519_sha256: "SHA256:pRR...e+M"
private_address: 10.217.79.11
static-2:
public_ip: "203.0.113.12"
ssh_ed25519_sha256: "SHA256:0f37...2ed230"
private_address: 10.217.79.12

Create records for the hostname you set in Step 1. Supercompute derives the rest by adding the hostname as a common suffix. Leave example.com at your registrar. If the DNS is hosted on Cloudflare, do not enable the proxy orange icons.

Prefix Name Type Value
ns ns.example.com A static-1 public IPv4
ns ns.example.com A static-2 public IPv4
sc-api sc-api.example.com CNAME ns.example.com
sc-app sc-app.example.com CNAME ns.example.com

Later, dynamic app URLs use the same nameserver:

Prefix Name Type Value
apps apps.example.com NS ns.example.com

Supercompute’s Nameserver then serves *.apps.example.com. If the VM IPs are not ready yet, add the A records before bring-up.

To use different API or app hostnames, edit dns_prefix_api and dns_prefix_app (and other dns_prefix_* values if needed) in inventories/<env>/group_vars/all/main.yml, then create matching DNS records and re-run task up.

Install this public key on both production VMs (ops user)—follow Adding a public node for each host.

Install Ubuntu on each VM, create the ops user, and open bootstrap firewall ports.

For static-1 and static-2:

  1. Ubuntu 26.04 amd64 + public IP (matches provider.image in main.yml).
  2. Create ops, install ~/.ssh/example-prod.pub, passwordless sudo.
  3. Record each host’s SHA256:… from /etc/ssh/ssh_host_ed25519_key.pub.
  4. Fill public_ip and ssh_ed25519_sha256 in hosts.yml.
  5. Firewall: TCP 22 from your Mac /32 during bootstrap. Open UDP 51830 broadly if you plan to add roaming nodes later (Adding a roaming node).

Prove SSH:

Terminal window
ssh -i ~/.ssh/example-prod -o IdentitiesOnly=yes ops@STATIC_1_IP true
ssh -i ~/.ssh/example-prod -o IdentitiesOnly=yes ops@STATIC_2_IP true

Create the encrypted vault and local password file for prod.

Terminal window
task vault-init ENV=prod

Creates inventories/prod/.vault-pass and encrypted vault.yml with vault_meta, placeholder vault_database_url, and auto-generated vault_database_secret. The first task up adds WireGuard keypairs.

Decrypted inventories/prod/group_vars/all/vault.yml looks like this:

---
vault_meta:
project: example
provider: prod
vault_database_url: postgresql://REPLACE_WITH_USER:PASSWORD@HOST:5432/DATABASE
vault_database_secret: mT4nQ8pL2vK6wB9xF1hJ5cY0aZ3dE7gH_rXv

Point the vault at your external Postgres before the first deploy.

Supercompute needs a Postgres database with owner role hosted outside of the Supercompute cloud.

Terminal window
task vault-edit ENV=prod

task vault-edit decrypts inventories/prod/group_vars/all/vault.yml in your editor. You should see vault_meta, vault_database_url, and vault_database_secret (WireGuard keys appear after the first task up). Replace only vault_database_url with your connection URI (same shape as the prod tab). Leave everything else unchanged.

Example after you set the database URL:

---
vault_meta:
project: example
provider: prod
vault_database_url: postgresql://app:[email protected]:5432/supercompute
vault_database_secret: mT4nQ8pL2vK6wB9xF1hJ5cY0aZ3dE7gH_rXv

Save and exit the editor:

  • vim/vi (default on macOS): press EscapeEscape, type :wq, press EnterEnter
  • nano: press Control + OControl + O, EnterEnter, then Control + XControl + X

task up rejects the vault-init placeholder in vault_database_url.

Deploy the WireGuard mesh and cluster stack to both static nodes.

Terminal window
task up ENV=prod

When it asks for BECOME password, enter your macOS password. You can run task up multiple times to retry; it is idempotent.

inventories/prod/group_vars/all/main.yml already sets control_plane: mac, provider.platform: public, Ubuntu 26.04 amd64 (source: template:ubuntu-26.04), and production-sized resources. Change provider.image.source only if your provider automation requires a different image identifier.

Confirm you can reach both nodes over the mesh.

Terminal window
task wg-status ENV=prod
task ssh ENV=prod NODE=static-1
task ssh ENV=prod NODE=static-2

Optional latency check from a node to the Mac mesh peer:

Terminal window
ping -c 10 -I scwg0 10.217.79.1

If you can access both nodes, you are done with this guide.

Item Done
hostname set in hosts.yml (for example example.com)
Parent DNS: ns A per static + api/app CNAME to ns + apps NS (Cloudflare proxy off)
static-1 + static-2 public IPs + fingerprints in hosts.yml
~/.ssh/example-prod on both VMs (ops user)
vault_database_url set in prod vault (not placeholder)
ssh … true works for both static IPs
task ssh ENV=prod NODE=… works for both statics

Store off-repo: ~/.ssh/example-prod (+ .pub), inventories/prod/.vault-pass, and a copy of inventories/prod/group_vars/all/vault.yml.