Skip to content

Your App VM ​

Some projects deploy to a full server instead of static files. This page explains how that server, your App VM, works: what runs there, what Stasho does on your behalf, and what you can check yourself.

Prerequisite: none, but Your data on-chain covers the encryption and verification model this page builds on.

Prompt for your agent

Copy this into Claude Code, Cursor or any agent that can run shell commands. Creating the VM runs in your browser; the agent guides you through it, then reads the VM's public status document.

text
Read https://docs.stasho.xyz/guides/your-app-vm.md. I am creating my App VM on Stasho. Walk
me through the Create App VM steps one at a time, in the page's words, and wait for me after
each step. The SSH key is mine: tell me how to print my public key, never generate or ask for
a private one. Never ask for the VM key, my wallet key or an SSH session. When I paste the
IPv6 host from the App VM home (it comes with square brackets), verify it: fetch
https://<ipv6 host>:8443/ (the certificate is self-issued) and read me each app's state.

The model in one paragraph ​

Your App VM is one machine that Stasho creates, runs and pays for on your behalf, on a confidential host: its disk is encrypted and only unlocked once the host proves it runs the expected image, which protects it from the node operator. Each server app you deploy to it runs in its own container, watched over by the supervisor: a small always-on program that installs each server app, restarts it if it crashes, and reports its state back to the dashboard. Your usage stats (live CPU and memory per server app) are encrypted to your wallet's key before they leave the VM. The VM is included: you pay nothing for it while it exists.

What's public, what's encrypted ​

Public by protocolEncrypted
IPv6 address, and the IPv4 host and port used for SSH (a NAT mapping)Your project names
Allocation: vCPU, RAM, diskYour usage stats: live CPU and memory per server app
VM image version and supervisor version
SSH public keys and fingerprints
Each server app's project ID and the hash of the bundle it runs

The public column above is provisioning detail that's already visible to anyone who reads the underlying on-chain messages or asks the host running your VM directly; none of it is a Stasho decision to disclose, and deleting the VM doesn't retroactively hide what was public while it ran. What stays encrypted is exactly what would otherwise leak your project identity or activity: the project's own name, and how hard it's actually working. See Your data on-chain for the same split applied to projects and deployments.

How code reaches the VM ​

Stasho holds the keys that unlock your VM and vouch for its supervisor, so you trust Stasho with it. The node operator, who runs the physical host, cannot read its memory or its disks: your VM is protected from the node operator.

Code reaches the VM by exactly one path: the supervisor, running on the VM itself, pulls your bundle from the public network, checks it, and runs it. There is no push path from our backend, and no remote-exec channel into your VM.

You talk to the Stasho backend, which publishes your deployment to a public network. Your App VM is a separate box: inside it, the supervisor independently pulls, verifies, and runs your bundle as your server app. There is no push path or remote-exec channel from the backend into the VM.You talk to the Stasho backend, which publishes your deployment to a public network. Your App VM is a separate box: inside it, the supervisor independently pulls, verifies, and runs your bundle as your server app. There is no push path or remote-exec channel from the backend into the VM.
The supervisor inside the VM pulls and runs your code. There's no push path in.

You can check what the supervisor runs. Before it runs a single line of your code, it works through a chain of checks, and every one of them is either an on-chain fact or a cryptographic proof, never a claim from Stasho:

  1. Resolve your deployment's on-chain storage reference and fetch the encrypted bundle from the storage network.
  2. Hash the fetched bytes and compare them against the hash your deployment recorded on-chain. A mismatch is rejected before anything is decrypted.
  3. Decrypt the bundle using the VM's own private key, a key nobody (not Stasho, not you) holds a copy of anywhere else. A wrong key or tampered ciphertext fails this step outright.
  4. Validate the unpacked manifest, including a check that the app was built for the exact Node.js major version the VM runs.
  5. Re-hash the unpacked application code against the hash recorded inside the bundle itself.
  6. Only after every step above passes does the supervisor start your app's container.

This is the same verify-what-runs principle described in Your data on-chain, applied to a running server instead of a static file. The exact wire format each step checks against is documented in the storage schema.

Filesystem layout ​

The supervisor and your server apps use a fixed set of paths on the VM, all on its encrypted state disk:

PathWhat's there
/var/lib/stasho/vm-keyThe VM's own private key, generated the first time it boots. Never leaves the VM.
/var/lib/stasho/state.jsonThe supervisor's own record of what it has already applied: assigned ports, deployed bundles, its own version.
/srv/stasho/apps/<projectId>/<deploymentId>/Your unpacked application code, one directory per deployment, mounted read-only into the container. The supervisor keeps the newest 3 per project and removes older ones.
/srv/stasho/apps/<projectId>/data/Your app's /data (see What your app can write).

Each server app runs as the container stasho-app-<projectId>.

Everyday operations ​

SSH into your VM with one of the keys you added when you created it (the App VM home lists them on the Access card), and use podman to inspect your app:

bash
# Watch a project's logs live
podman logs -f stasho-app-<projectId>

# Check whether it's currently running
podman ps --filter name=stasho-app-<projectId>

The supervisor owns these containers

The supervisor starts stasho-app-<projectId> fresh on every deploy. Anything you change by hand is gone the next time you push. Treat SSH access as a window for inspection and debugging, not a place to configure your app permanently: changes belong in your repo.

Ports and the firewall ​

The VM's firewall denies all inbound traffic by default. Only these are open:

PortWhat it's for
22SSH
80, 443Your apps, served over HTTPS on their default subdomains and custom domains
8443The public status endpoint (below)

Your apps listen on the VM's loopback interface only; a reverse proxy on the VM serves them on 80 and 443 for their domains, so an app's own port is never reachable from outside. The firewall also accepts ICMPv6, which is what makes IPv6 itself work.

The VM key ​

At first boot, the VM generates its own private key at /var/lib/stasho/vm-key and never sends it anywhere. It exists purely so the VM can decrypt bundles addressed to it.

Never copy this key off the VM, and never share it. Losing the VM (a move to a new host, a delete) is not the same as losing your deployments: every bundle you deploy is also encrypted to a key derived from your own wallet, so you can always recover your deployment history and app code with nothing but your wallet, even if the VM that ran it is long gone. See Recover your data.

What your app can write ​

Your server app runs as a Next.js app (standalone output) on one Node runtime. It can write to two places, and everything else on the VM is read-only:

  • /data (also available as process.env.DATA_DIR). Files here are kept on this VM across restarts and updates: a redeploy replaces the app container, not /data. It is not backed up and it does not move with the VM. When the VM is replaced or re-created, the files in /data are gone.
  • /tmp. It is memory-backed and cleared on every restart.

Keep durable data in a hosted database, not in /data. Treat /data as a local cache or scratch space that you can afford to lose.

When your VM moves ​

If the host your VM runs on stops responding, Stasho replaces the VM on a new host and redeploys your apps there. The timeline from a real replacement:

  1. A watchdog waits 15 minutes 30 seconds to confirm the host is really down.
  2. The VM then shows as moving to a new host. The replacement takes about 5 minutes to come up and be endorsed.
  3. Your apps are back about 10 minutes after the move starts, which is about 26 minutes after the host stopped.

Until the automatic redeploy lands, an app whose last bundle cannot be opened on the fresh VM reads "Waiting for redeploy". The old VM's /data is gone, so anything your app kept there has to be rebuilt or reloaded from your database.

You get one of two emails, sent to the address you gave when you created the VM, or to your sign-in email:

  • "Your App VM moved to a new host" is sent when the replacement is up and your apps are redeployed.
  • "Your App VM is down" is sent when a replacement failed after several attempts. The App VM home then offers "Create a new VM".

The status card on the App VM home shows the same states: running on a confidential host, moving to a new host (about 10 minutes), could not move after several attempts, and deleting.

Environment variables ​

Your project's environment variables travel inside the encrypted bundle itself, the same one the supervisor verifies and decrypts before running your app. There's no separate channel for them, and no way to change a running app's environment without redeploying: rotating a variable means redeploying. The next deploy bakes the new values into a new bundle, and the supervisor replaces the running container with one built from it.

Updates ​

Two different things update independently, on different schedules:

  • The supervisor updates itself in place. It checks a signed, on-chain registry, and when a genuinely newer, hash-verified release is available, it swaps itself in and restarts, without disturbing your running server apps.
  • The VM image (the OS, Node.js, the firewall rules) is fixed for the life of a VM. A new VM, or a VM moved to a new host, starts from the latest image.

Crashes ​

If your app keeps crashing, its container is restarted automatically, up to a point. Past that point, your deployment card in the dashboard shows:

App crashed on your App VM after N restarts. Check logs over SSH.

That message is deliberately generic. We don't ship your logs anywhere; they stay on the VM. SSH in and run podman logs -f stasho-app-<projectId> (see Everyday operations, above) to see what actually happened.

When your VM will not start ​

Creating an App VM takes about 6 minutes, and the App VM page shows each stage while it runs. If a stage fails, the page says "We could not prepare your VM." with one sentence naming the stage. When the failure happened before the VM existed, Try again reopens the setup with your SSH keys filled in; otherwise Delete clears it so you can create a new one. If preparing runs past 20 minutes, the page offers to delete the VM and start over.

The status endpoint ​

Port 8443 on your VM serves a small, public JSON document, no authentication, by design: it's what our health checks read, and anyone else can read it too. It exposes exactly:

  • The supervisor's own version, and a hash identifying this VM
  • For each deployed server app: its project ID, the hash of the bundle it's running, its current state (downloading, verifying, starting, serving, crashed, or verify-failed, which means the last bundle cannot be opened on this VM and a redeploy is on its way), its restart count, and its port
  • An encrypted blob of that server app's live CPU and memory use, sealed to your wallet's encryption key. The endpoint carries only the ciphertext; it never sees, and never serves, the plaintext numbers.

It never exposes environment variables, keys, logs, file paths, project names, or anything else about your account beyond what's already public on-chain. The dashboard's App VM home decrypts the stats blob in your browser to show live CPU and memory; if your encryption key is locked, it shows "Unlock to view stats" instead.

Deleting your VM ​

Delete App VM on the App VM home removes the VM for good: every server app on it stops, its address is gone, and the files in /data and /tmp go with it. A new App VM gets a different address. Your deployments stay on-chain: when you create a new VM, Stasho redeploys your server apps on it once it comes up.

Working with an agent ​

An agent cannot create an App VM for you: the three-step setup runs in your browser, and the server apps that run on the VM arrive through a repo import, which is yours too. An agent key reaches uploaded sites only, never an App VM app. What an agent can do is read: the status document below is public, and an agent whose public key you added at creation can SSH in as root and read logs with podman like you would (the keys are set when the VM is created).

App VMs are enabled per wallet. With no App VM entry in the sidebar, /vm reads "App VMs are not enabled for this wallet" and the setup cannot start: ask for access at hello@stasho.xyz first.

What the agent hands you, in these words:

text
In the Stasho dashboard open App VM in the sidebar. If there is no App VM entry, stop and
tell me: your wallet is not enabled yet. Step 1, What runs here: read it, tick the
acknowledgement, Continue. Step 2, SSH keys: paste the public key(s) that should open the
VM (print yours with `cat ~/.ssh/id_ed25519.pub`), Continue. Step 3, Email: optional, except
on a free trial, where it is required and you also tick the 14-day evaluation checkbox (a
"Connect your personal GitHub account" step may come first). Click Create. The page shows each
stage for about 6 minutes. When the status card reads Running, copy the IPv6 host value from
the Access card (it comes with square brackets) and paste it to me.

How the agent verifies it afterwards. The VM's status document is public by design, so the check needs no account:

bash
curl -sk "https://$VM_HOST:8443/"

VM_HOST is the bracketed value as copied, for example [2001:db8::1]; wrap a bare address in brackets yourself. -k is needed because the certificate is self-issued. The document carries agentVersion, instanceHash and apps: one entry per server app with its projectId, bundleHash, state (downloading, verifying, starting, serving, crashed or verify-failed), restarts and port. A fresh VM answers with an empty apps list; after a server app's first deploy its entry reads serving. A connection refused or timed out means the VM is not up yet, or the address was mistyped. A 429 means the endpoint's limit of 300 requests a minute is spent: retry in a minute. "Network is unreachable" means the machine running the check has no IPv6.