Skip to the content.

Developer guide

For deploying a project onto a tee-daemon CVM. For platform context, see the homepage. For auditing a deployed project, see the audit guide.

You need:

The handler contract

Your project is a single module that default-exports a request handler. The shared runtime loads it and calls it on every request to /<project-name>/....

// server.ts (Deno)
export default async function handler(req: Request, ctx?: { env: Record<string,string>, dataDir: string }) {
  return new Response("hello");
}

// Optional: also run standalone for local dev
if (import.meta.main) Deno.serve({ port: 3000 }, handler);

The path the daemon receives (/<name>/foo/bar) is rewritten to /foo/bar before your handler sees it, so handlers don’t need to know their mount point.

ctx.env is the env-var block from your manifest. ctx.dataDir is a per-project writable directory backed by a Docker volume; it survives runtime restarts but is not persisted across CVM redeploys, so treat it as a cache for things you can rebuild.

Other supported runtimes follow the same shape: a single entry file per project. Defaults are autodetected from the entry filename:

Runtime Entry Notes
deno server.ts The example above. Bun shares this contract.
node index.js package.json honored if present.
python app.py requirements.txt honored if present.
static . A directory of files, served verbatim.
dockerfile Dockerfile Custom container; you provide the listener.

For exact signatures of the non-Deno runtimes, see proxy/runtimes.py in the daemon repo — it’s the source of truth.

Deploy

From a public git repo:

TOKEN=...
CVM=https://your-cvm.dstack.phala.network

curl -X POST $CVM/_api/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-app","source":"https://github.com/me/my-app","ref":"main"}'

From a local tarball (no public repo required):

tar czf app.tgz -C my-app .
curl -X POST $CVM/_api/projects \
  -H "Authorization: Bearer $TOKEN" \
  -F 'manifest={"name":"my-app","runtime":"deno"};type=application/json' \
  -F "files=@app.tgz"

mode defaults to dev. The runtime is autodetected from the entry filename. Reach the running app at $CVM/my-app/.

project.json (optional)

Drop this in the project’s repo root to declare the runtime contract alongside the source:

{
  "runtime": "deno",
  "entry": "server.ts",
  "mode": "dev",
  "env": { "DEBUG": "true" }
}

Promote to attested

Promotion is the trust claim. The daemon records the source hash, opens the audit log, binds the hash into the TEE quote, and exposes the public verifier endpoints.

curl -X POST $CVM/_api/projects/my-app/promote -H "Authorization: Bearer $TOKEN"

Treat it like cutting a release — deliberate, not automatic. Subsequent redeploys append to the audit log; a counterparty walking the verifier sees that a change happened and can decide whether to re-audit.

Update or remove

# Re-pull from source (latest commit on the same ref)
curl -X POST $CVM/_api/projects/my-app/redeploy -H "Authorization: Bearer $TOKEN"

# Tear down
curl -X DELETE $CVM/_api/projects/my-app -H "Authorization: Bearer $TOKEN"

API surface

Public (no auth required), only for attested projects:

   
GET / Listing of attested projects. Accept: text/html returns the daemon’s viewer page; Accept: application/json returns JSON.
GET /_api/projects/<name> Project manifest.
GET /_api/projects/<name>/audit Audit log.
GET /_api/attest/<name> Raw dstack quote.
GET /_api/verification/<name> Manifest + quote + audit, in one response.

Authenticated (Authorization: Bearer $TOKEN):

   
GET /_api/projects All projects, including dev.
POST /_api/projects Deploy.
POST /_api/projects/<name>/promote Dev → attested.
POST /_api/projects/<name>/redeploy Re-pull from source.
DELETE /_api/projects/<name> Tear down.

Where to look in the daemon

proxy/ingress.py has the request routing and auth gate. proxy/runtimes.py has the language-runtime container management and the Deno router that loads your handler. proxy/deploy.py has the git-clone path and the source-hash recording. The whole thing is small enough to read end-to-end.