Serve Many Tenants From One Environmentpublic
Last verified 25 Sep 2026
DigitalOcean Harness Runtime provides managed, hardware-isolated microVM sandboxes with built-in tools such as Chromium to run harnesses and execute arbitrary code. Rich lifecycle APIs preserve conversational history and working state across sessions, letting you pause, resume, and fork work to control costs and adapt to the nonlinear nature of agentic workflows. Scale complete agents such as Claude Code or use sandboxes independently for code execution, all through the same service. See What You Can Build for example use cases.
When your product is the agent, every customer needs the same environment and none of them should share one. This example saves a configuration once and starts a session per tenant from it, so onboarding a customer is one API call rather than a new spec, and no tenant can reach another tenant’s workspace.
It also covers the trick that makes evaluation affordable: warm an environment up once, checkpoint it, and fork the checkpoint instead of rebuilding the setup for every case.
Save the Environment Once
Start from a spec that describes the environment your product needs, with no per-tenant values in it. Save the following as product-agent.yaml:
name: product-agent
agent: opencode
size: mars-2vcpu-4gb
idle_timeout: 20m
env:
HARNESS_INFERENCE_MODEL: openai-gpt-5.6-sol
secrets:
HARNESS_INFERENCE_API_KEY: ${HARNESS_INFERENCE_API_KEY}
tools:
- do.actions: [exa_web_search, exa_web_fetch]
permissions:
default: allow
rules:
- tool: bash
match: { command: "rm -rf *" }
action: denyTurn it into an Environment Config:
doctl harness-runtime config create --spec product-agent.yaml --name product-agent-v1Credentials declared under secrets go to DigitalOcean Secrets Manager when the config is created and stay server-side. Sessions started from the config get them without you resupplying anything, which is what makes per-tenant provisioning a single call. That includes the model access key OpenCode uses against DigitalOcean Serverless Inference, so one key covers every tenant and the model usage for all of them lands on one DigitalOcean bill.
A config is immutable, and the version suffix in the name above is deliberate. Changing the environment means creating product-agent-v2 rather than editing v1, so a session can always be traced to the exact configuration it ran under. You cannot delete a config while sessions are still using it.
Start a Session Per Tenant
Give each session a name you can map back to a customer:
doctl harness-runtime config start-session product-agent-v1 --name tenant-acmeEach session is a separate microVM with its own workspace. To see everything one config has produced:
doctl harness-runtime config list-sessions <config-id>Going the other direction, doctl harness-runtime show <session-name> reports the config a session started from, which is what you want when one tenant reports behavior the others do not.
Pause a session when its tenant is idle. The workspace is preserved and active compute charges stop, so an account that logs in weekly is not paying to sit in memory:
doctl harness-runtime pause tenant-acmeFork a Warm Environment for Evaluations
Provisioning a session and letting it install dependencies and clone a repository is the slow part of an evaluation run, and it is identical for every case. Start a session from the config, let it warm up, then snapshot it:
doctl harness-runtime config start-session product-agent-v1 --name product-agent-eval
doctl harness-runtime checkpoint create product-agent-eval --label warmedFork the checkpoint into independent sessions, up to four at a time:
doctl harness-runtime fork product-agent-eval --from-checkpoint <checkpoint-id> --count 4Each fork is a full session with its own sandbox, starting from the warmed state. Send a different case to each, compare results, and remove them. Repeat the fork command if you have more cases than one batch covers. To find the forks you created:
doctl harness-runtime list --parent-session-id product-agent-evalThe same mechanism covers the demo-environment case: warm up a configured workspace, checkpoint it, and fork a fresh copy per prospect.
Adapt It
| To do this | Change this |
|---|---|
| Vary behavior per tenant without a new config | Keep the config fixed and vary the prompt. Configuration belongs in the environment; task belongs in the prompt. |
| Give each tenant its own credentials | Create a config per tenant. Secrets are captured at config creation, so they cannot be overridden per session. |
| Offer tenants a choice of model | HARNESS_INFERENCE_MODEL is part of the config, so each model needs its own. Name them for the model rather than the tenant, and map tenants to configs on your side. |
| Run your own agent instead of a listed adapter | Package it as an OCI image and register it. See Use a Custom Template. |
| Run a framework agent | Set agent: langgraph. See Run a LangGraph Agent. |
| Undo a bad run instead of forking | Roll the session back to a checkpoint with doctl harness-runtime rollback. |
| Drive this from code rather than the CLI | Every command here has an API equivalent. See the Harness Runtime API reference. |
Related
- Use Environment Configs for the full config surface.
- Checkpoint, Fork, and Roll Back for snapshot semantics.
- Environments, Sessions, and Runs for how the three levels relate.