Skip to content
Getting started

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&apos;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

  1. Stripe webhook fires checkout.session.completed. We verify the signature and check idempotency against the event log.
  2. Subscription + VM rows are inserted in Postgres, state = creating.
  3. Provisioning job is enqueued — the Supabase edge function picks it up.
  4. Secrets are minted — agent JWT, ttyd password — and encrypted with pgsodium.
  5. Hetzner API call — new VM in your region with a rendered cloud-init user-data payload.
  6. Cloudflare DNS — A record for your subdomain created as soon as we have an IP.
  7. First heartbeat — agent reports in; the VM row flips to ready; the welcome email goes out.
  8. You land on the dashboard — usually within 60–90 seconds of payment.
Why dedicated VMs?
We could pack customers onto shared containers and charge less. We don't, because a runaway loop, a kernel exploit, or a greedy neighbour all become someone else's problem when you have your own VM. The price we pay for that guarantee is honest margin — see pricing.

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.