How Nacre works
The mental model. There are two sides: the control plane (runs us), and your VM (runs you). They talk to each other through a small set of signed endpoints.
Two planes, one bot
Every Nacre customer gets exactly one dedicated virtual machine. That VM runs your OpenClaw instance and nothing else — no shared kernel, no noisy neighbours, no multi-tenant container packing.
Our control plane (the thing at nacre.sh) is the dashboard, billing, and the orchestrator that spawns, resizes, and retires VMs. It never reads your conversation content — just metadata (uptime, resource usage, OpenClaw version).
The control plane
- Hosting
- Next.js 14 on Vercel — marketing site, dashboard, API routes.
- Database
- Supabase Postgres with row-level security. Secrets encrypted with pgsodium.
- Auth
- Supabase Auth — email/password and Google OAuth.
- Billing
- Stripe Checkout + Customer Portal + webhook-driven lifecycle.
- DNS & edge
- Cloudflare. Each VM gets an A record at user-<id>.nacre.sh.
- Offsite backups
- Cloudflare R2 — a different cloud than your VM provider.
Your VM
When you pay, we call the Hetzner Cloud API to create a VM with your plan's spec (Solo = 2 vCPU / 4 GB, Scale = 16 vCPU / 32 GB). A single cloud-init script installs Docker and brings up four containers:
- caddy:2-alpine
- TLS termination via Let's Encrypt; routes /terminal and /ws to ttyd, everything else to OpenClaw.
- openclaw
- The AI assistant itself — pinned to a stable release tag.
- ttyd
- Web terminal. Only reachable via JWT-gated Caddy route.
- oc-agent
- Our Go binary. Heartbeats, command execution, backups. ~10 MB.
The /data volume holds your OpenClaw state — memory files, skills, conversation history, channel credentials. It is yours. We don't read it.
How the two planes talk
Three signed endpoints, all HTTPS, all on nacre.sh:
POST /api/internal/vm/:id/heartbeat— every 30 seconds, agent pushes CPU / RAM / disk / OpenClaw version.POST /api/internal/vm/:id/ready— once, from cloud-init, when Docker Compose is up.POST /api/internal/vm/:id/update-result— after an OpenClaw upgrade finishes, agent reports success or rollback.
Every request carries a per-VM HS256 JWT (scope: 'agent'). The control plane constant-time compares it against the copy it stored in pgsodium when the VM was provisioned.
The provisioning sequence
- Stripe webhook fires —
checkout.session.completed. We verify the signature and check idempotency against the event log. - Subscription + VM rows are inserted in Postgres, state =
creating. - Provisioning job is enqueued — the Supabase edge function picks it up.
- Secrets are minted — agent JWT, ttyd password — and encrypted with pgsodium.
- Hetzner API call — new VM in your region with a rendered cloud-init user-data payload.
- Cloudflare DNS — A record for your subdomain created as soon as we have an IP.
- First heartbeat — agent reports in; the VM row flips to
ready; the welcome email goes out. - You land on the dashboard — usually within 60–90 seconds of payment.
What survives a disaster
Nightly, your agent tars /data, encrypts it with a per-customer key (HMAC-SHA256(platform_secret, user_id)), and pushes it to Cloudflare R2 — a different cloud than the one your VM runs on. If Hetzner loses your disk, we restore from R2 to a fresh VM. If Hetzner loses the whole region, we restore from R2 to a different provider. Full DR runbook on the Security page.