DEV Community

Cover image for A Customer Asked Who Else Was on Their Server. I Didn't Have a Good Answer.
Dhruv Malaviya
Dhruv Malaviya

Posted on

A Customer Asked Who Else Was on Their Server. I Didn't Have a Good Answer.

A security questionnaire exposed that my tenants shared a kernel. Now each tenant runs in a dedicated Firecracker microVM on Krova Cloud — here's the signup-time provisioning, the sleep policy, and the honest cost math.

The question came from a customer, buried in a security questionnaire: "Describe the tenant isolation of your production environment."

My honest answer was "we use containers" — which, the more I thought about it, was a description of packaging, not isolation. Every customer's workload shared one kernel. Namespaces drew polite lines; a kernel bug doesn't respect politeness. The threat model that kept me up wasn't strangers on the internet. It was my own tenants, one kernel apart.

Now each tenant's workload runs in its own Cube on Krova Cloud — a Firecracker microVM with its own kernel. Here's the actual setup.

Provisioning at signup

The signup flow shells out to a script. Idempotency key included, so a retried webhook can't create two Cubes for one tenant:

# provision-tenant.sh <tenant-slug>
set -euo pipefail
TENANT="$1"

krova cubes create "tenant-$TENANT" \
  --image ubuntu-24.04 \
  --ssh-key "$(cat ~/.ssh/tenant_deploy.pub)" \
  --vcpu 1 --ram 2 --disk 20 \
  --idempotency-key "signup-$TENANT"

# tenant gets their own subdomain over managed TLS ingress
krova domains add "tenant-$TENANT" \
  --domain "$TENANT.app.example.com" --port 8080
Enter fullscreen mode Exit fullscreen mode

Same thing from the API when the orchestrator does it directly:

curl -X POST https://krova.cloud/api/v1/spaces/$SPACE/cubes \
  -H "X-API-KEY: $KROVA_KEY" \
  -H "Idempotency-Key: signup-$TENANT" \
  -d '{
    "name": "tenant-acme",
    "image": "ubuntu-24.04",
    "resources": { "vcpu": 1, "ramGb": 2, "diskGb": 20 },
    "sshPublicKey": "ssh-ed25519 AAAA..."
  }'
Enter fullscreen mode Exit fullscreen mode

Then verify what the tenant actually got — this is the part I print into the security questionnaire
answer:

$ krova ssh tenant-acme -- ip -brief addr
eth0  UP  10.0.x.x/24        # private NAT'd network. no public address.

$ krova ssh tenant-acme -- ss -tlnp
# only the tenant's own app listens; nothing reachable from the internet
Enter fullscreen mode Exit fullscreen mode

The API agrees: publicIpv4 stays null until you explicitly map a port. Default-deny is the default.

The three properties that matter

  1. Own kernel per tenant. One customer's kernel bug can't reach another customer. Hardware-enforced, not configured-correctly-on-a-good-day.
  2. No overselling. Per the docs, RAM and disk are reserved 1:1 — "your resources" finally means your resources. The noisy neighbor didn't get quieter; he moved out.
  3. No public IP. Tenants' boxes aren't scannable addresses. Traffic enters via managed TLS ingress on their subdomain; only ports I explicitly open are reachable, and I open none.

Keeping the bill survivable
One Cube per tenant is one bill per tenant, so the economics get engineering:

# nightly: tenants inactive 7+ days go to sleep — stopped Cubes bill disk only
krova list --json | jq -r '.[] | select(.state=="running") | .name' \
  | grep '^tenant-' | while read -r cube; do
      is_active "$cube" || krova cubes power-off "$cube"
    done

# tenant logs in next morning → auth layer wakes them:
krova cubes wake tenant-acme   # back in seconds, disk intact
Enter fullscreen mode Exit fullscreen mode

Micro sizes for light tenants, sleep for idle ones, right-size when usage says so. Rough math: 30 tenants on $5–10 Cubes is $150–300/mo — priced into plans as a trust line-item, because that's exactly what it is. Insurance you can quote in a sales call.

The honest part

  • Isolation is a boundary, not authorization. A wrong WHERE tenant_id = ... in my own app still leaks data between tenants. The microVM stops machines from touching; it doesn't stop my bugs. App-level tenant checks stay and get tested.
  • You write some orchestration. Provision, sleep, wake, deprovision-on-churn, snapshot before migrations — maybe a hundred lines plus cron. Real cost; still smaller than the cluster I didn't adopt.
  • Draw the line where the question gets asked. Ten thousand free-tier users don't each get a VM. I started with tenants handling serious data — the ones filling out questionnaires. That's where the risk and the trust live.

Tenancy is billing. Isolation is physics.
"Multi-tenant" drifted into meaning "shares everything." It doesn't have to: bill tenants together, run them apart. When a customer asks who else is on their machine, there's only one answer that ends the conversation happily: nobody.

Top comments (0)