all posts

The best ways to host OpenHands in 2026

Ajay Kumar··10 min read

OpenHands is easy to run and genuinely hard to host, and those two facts confuse a lot of people. One docker run and you have a working software-engineering agent with a UI, a shell, a file editor and a browser. That is a real achievement of packaging and it is why the project is where most people meet the autonomous-agent shape for the first time. It is also why so many production deployments are one command that grew.

The reason hosting is hard is that OpenHands is not one workload. It is two, they have close to opposite requirements, and they share a hostname. Workload one is the control app: the web UI, the agent loop, the conversation and event history, and the LLM API calls. It is cheap, long-lived and idle most of the time, because most of its life is spent waiting on a token stream or on a human. Workload two is the runtime: the place where the model's chosen bash commands, file edits and browser actions actually execute. That one is untrusted by construction, bursty, hungry, and it needs a writable workspace that survives between actions.

Untrusted by construction is a phrase worth pausing on, because people hear it as an accusation against the model. It is not. It means that nobody — including the model — knows what the next command is going to be until it is emitted, so the environment cannot be sized, permissioned or reasoned about in advance the way a normal service can. An agent that has been asked to fix a failing test will pip-install things, curl things, delete things, and occasionally interpret "clean up the workspace" with a literalness that would impress a lawyer. None of that is malice. All of it is a reason for a boundary.

So this roundup is organised around the split. For every option: what does it do with the runtime? I'm Ajay, I build PandaStack, which is one of the options near the bottom and is the wrong answer to at least half of this post, so read me as an interested party. Everything I say about anyone else is qualitative on purpose — no invented prices, no invented latencies. OpenHands also moves fast: image tags, environment variable names and the runtime configuration surface change between releases, so treat every snippet here as an anatomy diagram and check the current docs before you copy it.

The two workloads, drawn properly

Five boxes. Get these straight and every option below is just a different opinion about who runs which one.

  • The web app. A server process and a UI you point a browser at. Ordinary, boring, and the part everyone spends their planning time on. It holds an HTTP port and nothing exciting.
  • The agent loop. The thing that assembles context, calls a model, parses an action out of the response and dispatches it. It is I/O-bound to the point of comedy: almost all of its wall clock is spent waiting for a provider's token stream, which is why a machine running six concurrent conversations looks, on a CPU graph, like a machine running nothing.
  • Conversation and event state. Session history, trajectories, settings, and whatever your version persists to disk or a database. Small, unglamorous, and the thing whose loss makes users angriest, because a conversation is a work product.
  • The runtime. An action-execution server that receives a command, runs it, and returns output. This is the security boundary. This is also where the agent's whole personality lives, from the point of view of your host.
  • The workspace. A cloned repo, an installed dependency tree, a node_modules directory of legendary proportions, a build cache, a virtualenv. It must persist between actions within a session, and it is the reason "just make the runtime stateless" is not advice, it is a wish.

The structural fact that makes the split tractable is that the control app talks to the runtime over an ordinary network call. That indirection is deliberate on the project's part — OpenHands treats the execution environment as a swappable component rather than an implementation detail — and it is the single best architectural decision in the codebase from a hosting perspective. It means you can host the two halves on completely different things, and it means the interesting hosting question has an actual seam to be answered at.

The control app: cheap, idle, and not the problem

If you only had the control app to host, this post would be four sentences long. It is a web service with a bit of state. Put it on anything that runs web services. Give it TLS, put SSO in front of it because the project's own auth story is not designed to be your front door on the public internet, and back up whatever holds the conversation history.

The one non-obvious property is the idleness. An agent session is mostly waiting: for the model, for a build, for a human to read a diff and say yes. A control app serving a handful of engineers spends the overwhelming majority of the day doing nothing at all, which makes "what does this cost while nobody is using it" a legitimate question rather than a penny-pinching one. But it is a small question, and if you find yourself in a long meeting about it you are optimising the wrong half.

The runtime: the half that decides everything

Here is the default deployment, and the reason I keep insisting people look at it rather than at the pretty UI.

# ---------------------------------------------------------------------------
# Self-hosted OpenHands, in roughly the shape the quickstart gives you. Image
# tags, env var names and the exact volume set move between releases -- this is
# an anatomy diagram, not a copy-paste. Check the current docs.
# ---------------------------------------------------------------------------

docker run -it --rm \
  --name openhands-app \
  -p 3000:3000 \
  -v ~/.openhands:/.openhands \
  -e SANDBOX_RUNTIME_CONTAINER_IMAGE=<the runtime image for your version> \
  -v /var/run/docker.sock:/var/run/docker.sock \
  <the openhands app image for your version>

# Read the last volume mount again, because it is the entire hosting question
# wearing a disguise.
#
# The app container does not run the agent's commands itself. It asks the HOST
# Docker daemon to start a second container -- the runtime -- and the model's
# bash lands in there. To do that, the app is handed the host's Docker socket.
#
# Docker socket access is not "slightly elevated". It is the ability to start a
# container with the host filesystem mounted and no restrictions, which is
# root on the host with extra steps. The model does not hold that socket; the
# app does. But they are one container escape apart, on one shared kernel.
#
# On your laptop this is completely fine and you should not lose sleep over it.
# On a shared host running other people's work, it is a design you should have
# chosen deliberately rather than inherited from a quickstart.

# The other route -- and the reason OpenHands is nicer to host than most agents
# -- is that the runtime is a swappable component. There is a remote-runtime
# path where the app talks to an execution service over the network instead of
# to a local Docker daemon. The configuration keys for selecting one have moved
# more than once, so look them up for your release rather than trusting a blog
# post, including this one.

Two things follow from that snippet. The first is that the default boundary between model-chosen commands and your host is one container on a shared kernel, plus a control app holding a socket that can start any container it likes. The second, more cheerful, is that the project anticipated this and gave you a seam. Almost everything worth doing about OpenHands hosting is done at that seam.

The failure mode people actually hit is not a break-out. It is a resource one: a headless Chromium plus a dependency install plus a test suite, times the number of concurrent conversations, on one box that was sized by looking at the CPU graph of an agent that spends its life waiting for tokens. The memory arrives all at once, and the OOM killer picks a victim that is not the agent.

What to judge a host on

Six questions. Ask them of every option below, in this order, because the early ones eliminate more than the late ones.

  • Isolation boundary. What separates the model's shell from your host and from other sessions? Namespaces and cgroups over a shared kernel, or a hypervisor? For your own repos on your own hardware, a container is a defensible place to stop. The moment you run a session on behalf of someone else, or point the agent at a repo you did not audit, you are relying on that boundary for security rather than packaging.
  • Per-session lifetime and idle cost. A session lives for as long as a human is engaged with it, which is bursty and unpredictable. What are you paying between actions, overnight, and during the twenty minutes the user went to lunch mid-conversation? A platform with a per-service floor charges you for the lunch.
  • Workspace persistence. The repo, the installed dependencies, the build cache. Does it survive a runtime restart? Does it survive resuming a conversation tomorrow? "Re-clone and re-install every time" is a legitimate answer if your install takes eight seconds and a terrible one if it takes six minutes, which for a real front-end repo it does.
  • Egress control. An agent that can reach your package registry can also reach a paste site. Can you express a default-deny egress policy per session, as a property of the environment rather than a rule inside it? This matters enormously if the agent is ever handed credentials, and it matters even when it is not, because exfiltration is the difference between a contained mess and an incident.
  • Concurrency and fan-out. Not just how many sessions run at once, but whether you can cheaply run the same task several ways in parallel and keep the best result — which is the thing that actually moves success rates on hard issues. That is a question about how expensive a fresh environment is to create.
  • Operational burden. Who is on the pager when the runtime pool wedges, the disk fills with node_modules from sessions that ended a week ago, or a version bump changes the runtime image contract? This is the criterion people weigh last and regret weighting last.

The options, honestly

1. A VM with Docker (the default, and the honest baseline)

One machine, Docker installed, the app container running with the socket mounted, runtime containers spawned per session. This is what the quickstart gives you and what most self-hosted OpenHands actually is. It deserves more respect than the security-minded corner of the internet gives it, and less than its ubiquity implies.

What is genuinely good: it is one machine, one docker ps, one journal. Runtime start-up is fast once the image is in the local cache. The workspace is a volume on local disk, so persistence between actions is free and persistence between sessions is a directory you keep. Debugging is a shell away. For a solo developer or a small team running the agent against their own repos, on hardware they control, this is the right answer and you should stop reading and go do it.

What you are accepting, stated plainly. The boundary between model-chosen commands and your host kernel is a container, and the control app holds a socket that is root-equivalent. Sessions share a kernel, a page cache and a disk with each other. Capacity is one machine's RAM, and the concurrency limit is whatever number causes the OOM killer to intervene — a number you will discover empirically, on a Wednesday. And there is no natural egress boundary: the default Docker bridge lets the agent reach the internet and, more interestingly, your VPC. If any of those sentences describes a risk you actually hold, this option is where you leave, not where you optimise.

2. Kubernetes with a container runtime

The app becomes a Deployment with an Ingress. The runtime becomes a pod per session, with resource requests, a service account, a network policy and a PVC for the workspace. If you already run Kubernetes well, this composes with everything you have, and the network-policy story in particular is a real improvement over a Docker bridge — egress restriction becomes a resource you can write down and review rather than an iptables rule someone remembers.

The pieces that need thought are all about the runtime's lifecycle, not the app's. Pods are a slow unit of creation relative to an interactive agent action, so a per-session pod that must be scheduled and pull an image before the first command runs will make the session feel worse than the laptop version did — the usual fixes are a warm node pool or a pre-pulled image on every node, both of which cost you money while nobody is using them. Workspace persistence means a PVC per session, which means a lifecycle and a garbage collector you own, because nothing will reclaim them for you and a hundred abandoned conversations is a hundred volumes. And the isolation boundary has not changed: a pod is a container on a shared node kernel. gVisor, Kata Containers or a similar sandboxed runtime class is the standard answer to that, and it is a good one — just know it is an addition you configure, not something you get by being on Kubernetes.

The standing rule applies: this is right if Kubernetes is already load-bearing for you and someone knows it well. It is wrong if you would be adopting Kubernetes in order to host an agent.

3. The project's own hosted offering

There is a commercial hosted product from the team behind OpenHands, and the availability, tiers, limits and pricing of it are theirs to set and change — go and read their current documentation rather than a roundup, including this one. But the shape is worth reasoning about even without the specifics, because it removes the most work of anything on this list.

The people who write the runtime operate the runtime. Version skew between app and runtime image simply stops being a category of problem, which is not nothing: a self-hoster eventually learns that the app version and the runtime image version form a compatibility surface, usually at the moment a routine bump makes every session fail to start. Capacity is elastic and not your problem. The agent's environment is professionally maintained rather than being whatever your Dockerfile last inherited.

The questions to ask before committing are the ones any hosted execution product should be able to answer without flinching. What is the isolation boundary between my sessions and other customers' — container, sandboxed runtime, or virtual machine? Where does my source code live while a session runs, and what happens to it after? Can I restrict egress from a session, or is the environment able to reach the whole internet by default? What is the wall-clock ceiling on a single session, since agentic work has a long tail and a hard cutoff amputates exactly the runs that were about to succeed? And can the runtime reach a private network, because an agent that cannot reach your internal package registry is an agent that cannot build your monorepo. None of these are hostile questions. They are the ones that decide the answer.

4. General container PaaS

Render, Railway, Fly, Northflank and that whole class. For the control app, this is an excellent fit and I mean it as praise — a web process with a hostname, TLS, deploy-on-push and a managed database beside it is the exact thing these platforms were built to make trivial. If you were only hosting the app half, this is close to the effort-to-outcome winner.

The runtime is where you have to read carefully, and the specific thing to check is whether the platform will let one of your containers create and control other containers. Usually it will not, and it is entirely right not to: exposing a Docker socket to tenant workloads is precisely the thing a multi-tenant platform exists to prevent. Which means the standard OpenHands deployment does not simply lift onto this class of platform. Your realistic options are to run the runtime somewhere else and point the app at it over the network, or to run the agent's commands in the same container as the app — which is not isolation, it is a rename, and it puts model-chosen bash in the same process space as your session state and your API keys.

The second thing to check is idle behaviour in the other direction. Request-driven autoscaling is a poor match for an agent that has been thinking for four minutes and is about to come back with a file write. Confirm the timeout and scale-down semantics before you find out through a session that died mid-refactor. Verify all of this against the platform's current documentation — this is exactly the area where these products differ most and change most.

5. MicroVM sandbox platforms (including ours)

The newest category, and the one built for exactly the second workload. The idea is that the runtime is not a container at all: each session gets a virtual machine with its own kernel under a hypervisor, created on demand and destroyed when the session ends. There are several credible products in this space, including E2B, Modal, Daytona and others, and the sensible way to evaluate any of them — mine included — is to ask the six criteria questions and make them answer in specifics.

Speaking for the one I build, since being vague about my own product would be a strange kind of modesty. PandaStack is an open-source Firecracker platform. The relevant properties for an OpenHands runtime are these. Creating an environment is a snapshot restore rather than a boot — around 179ms at p50 and 203ms at p99, with the roughly three-second cold boot paid once when the template is baked — which is what makes a fresh machine per session, or per task, affordable enough that nobody quietly starts reusing one to hide the latency. Each guest has its own kernel under KVM and its own Linux network namespace, with 16,384 pre-allocated network slots per host, so a default-deny egress policy with an allowlist for your registry and your git host is a property of the machine rather than a rule inside it that the agent could plausibly talk its way around. Billing is per second at $0.054 per active vCPU-hour and $0.0162 per GiB-hour, and idle scales to zero, which matters for a workload whose defining characteristic is waiting.

The property I would actually reach for first, though, is forking. Snapshot a workspace once the repo is cloned and dependencies are installed, and every later session starts from that frozen state instead of running npm install again. Better: fork the running environment to try the same task three different ways in parallel — 400 to 750ms on the same host, 1.2 to 3.5 seconds across hosts, with copy-on-write memory and a reflink rootfs so the third attempt does not cost three times the RAM. Agentic coding has a low per-attempt success rate on hard problems and a cheap verifier in the form of the test suite, which is the exact shape where running several attempts and keeping the winner beats thinking harder about one.

Where this is a bad fit, plainly. There is no first-party PandaStack runtime plugin in OpenHands; you write a small adapter against the runtime interface, which is a shape you own rather than a package you install. Firecracker is not a GPU story, so if your agent's job involves an accelerator this is the wrong layer. And the control app half is not differentiated here at all — it deploys as a git-driven app with a managed Postgres, which is a fine place for it and is also exactly what option four does well. If all you need is a home for the web process, buy that from whoever you already buy web hosting from.

Side by side

Everything about other people's products here is qualitative and moves on their schedule. The only numbers are PandaStack's, because they are the only ones I measured.

  • VM with Docker — Isolation: a container on a shared host kernel, with the control app holding a root-equivalent Docker socket. Idle cost: one always-on machine, small and honest. Workspace: a local volume, persistent and free. Concurrency: bounded by one machine's RAM, discovered empirically. Ops burden: low and legible. Best for: individuals and small teams running the agent against their own repos on their own hardware.
  • Kubernetes — Isolation: a pod per session on a shared node kernel; add a sandboxed runtime class if you need more. Idle cost: your cluster floor, plus any warm node pool you keep to hide pod start-up. Workspace: a PVC per session, and a garbage collector you write. Concurrency: genuinely elastic, which is the main reason to be here. Ops burden: inherits your cluster's. Best for: teams already running Kubernetes with working GitOps and a network-policy habit.
  • The project's hosted offering — Isolation: theirs; ask what it actually is for your tier rather than assuming. Idle cost: their pricing model, which you should read today rather than trust from a post. Workspace: theirs, and worth asking how long it lives. Concurrency: elastic and not your problem. Ops burden: lowest available by a wide margin. Best for: teams who want the agent, not the platform, and whose code may leave their perimeter.
  • Container PaaS — Isolation: excellent for the app, and for the runtime usually unavailable, because these platforms deliberately do not let a tenant container start containers. Idle cost: typically a per-service floor. Workspace: a disk or volume if the platform offers one. Concurrency: fine for the app, not the model this half needs. Ops burden: very low. Best for: hosting the control app while the runtime lives somewhere built for it.
  • MicroVM sandbox platforms (PandaStack among them) — Isolation: a hypervisor boundary; own guest kernel, own memory, own network namespace. Idle cost: zero between sessions; on PandaStack, per-second billing at $0.054/vCPU-hour and $0.0162/GiB-hour. Workspace: a snapshot you restore, so a warm repo and installed dependencies are the starting state rather than a six-minute prelude. Concurrency: a fresh machine per session at ~179ms p50, and same-host forks at 400-750ms for parallel attempts. Ops burden: an adapter you own, plus ordinary app hosting for the control half. Best for: anyone running sessions on behalf of other people, or against repos they did not audit.
All of the above is a shortlist-building tool, not a decision. OpenHands' runtime configuration surface, image names and remote-runtime options change between releases; every vendor's pricing, limits and isolation claims are theirs to change. Verify the specifics — especially session wall-clock ceilings, egress behaviour and what the isolation boundary actually is — against current documentation before you commit.

What the runtime seam looks like in code

Because OpenHands routes every action through a runtime interface, swapping the execution target is a small, contained piece of work rather than a fork of the project. Here is the shape, with our SDK standing in for whatever you point it at.

# A minimal OpenHands runtime shim: one Firecracker microVM per conversation.
# The integration point is the thing that currently talks to the Docker API;
# it instead talks to this. Method names on the OpenHands side move between
# releases -- the shape is stable even when the spelling is not.

import os

from pandastack import Sandbox

TEMPLATE = 'agent'   # baked with git, node, python, and your toolchain


class MicroVMRuntime:
    def __init__(self, conversation_id: str, repo_url: str) -> None:
        # A create is a snapshot restore, not a boot: ~179ms p50, ~203ms p99.
        # The ~3s cold boot was paid once, when the template was baked.
        self.sbx = Sandbox.create(
            template=TEMPLATE,
            ttl_seconds=8 * 3600,   # backstop: the guest reaps itself if we die
            metadata={'conversation_id': conversation_id},
        )

        # Credentials arrive as a 0400 file, never as argv. argv is readable by
        # every process in the guest, including whatever the agent decides to
        # shell out to in ninety seconds' time.
        self.sbx.filesystem.write('/run/agent/git-token', os.environ['GIT_TOKEN'])
        self.sbx.exec('chmod 0400 /run/agent/git-token', check=True)
        self.sbx.exec(
            f'git clone --depth 1 {repo_url} /workspace',
            timeout_seconds=300,
        )

    def run(self, cmd: str) -> tuple[int, str]:
        # The model chose this string. That sentence is the entire threat model,
        # and it is why this call crosses a hypervisor rather than a namespace.
        res = self.sbx.exec(cmd, timeout_seconds=600)
        return res.exit_code, (res.stdout + res.stderr)[-8000:]

    def checkpoint(self) -> str:
        # Freeze the workspace once the repo is cloned and dependencies are
        # installed. Tomorrow's session starts here instead of at npm install.
        return self.sbx.snapshot()

    def branch(self, n: int):
        # Same-host fork: 400-750ms, copy-on-write memory and a reflink rootfs,
        # so three parallel attempts at one issue do not cost three times the
        # RAM. Run them, diff them, keep whichever one made the tests pass.
        return self.sbx.fork_tree(n)

    def close(self) -> None:
        # Everything the agent installed, wrote, cached, forked, detached and
        # hoped nobody would notice stops existing at the same instant. There is
        # no cleanup script to get wrong because there is no cleanup.
        self.sbx.kill()

Three details there are load-bearing and none of them is the sandbox itself. The credential is a file with restrictive permissions rather than an environment variable in a command line. There are two independent stop conditions — a per-command timeout and a create-time TTL — so a hung build and a crashed orchestrator fail differently and neither depends on the other being correct. And output comes back as data your side reads, never as code your side runs, which is the rule people break the first time an agent helpfully generates a cleanup script.

Five things that actually bite

Independent of which option you pick.

  1. Runtime image version skew. The app and the runtime image are a matched pair, and a routine app bump that leaves the runtime image pinned produces sessions that fail to start with an error message about neither. Pin both, bump both, and treat it as one release rather than two.
  2. Disk, filled by node_modules. Every session clones a repo and installs a dependency tree. Nothing reclaims them unless you wrote the thing that reclaims them. The first outage is not a break-out or an OOM; it is a full disk at 3am on a host nobody put in the monitoring because it was "just the agent box".
  3. Memory sized from the CPU graph. The agent loop is I/O-bound and looks almost free. The runtime is not: a headless browser plus a build plus a test suite arrives all at once, and it arrives on every concurrent session simultaneously because agents do not stagger themselves politely. Size for concurrent runtimes, not for concurrent conversations.
  4. Credentials in the environment the agent can read. If a git token, a cloud key or an LLM API key is in the runtime's environment, then it is in the model's reach the moment it runs env. Sometimes that is necessary. It should always be deliberate, scoped to one repo, short-lived, and paired with an egress policy — because a stolen credential that cannot phone home is a much better Tuesday.
  5. No egress policy at all. The default network for a container or a VM usually reaches the whole internet and, more interestingly, your VPC. An agent following a plausible-looking instruction it read in a repository's README is a real category of incident now, and the cheapest mitigation by a wide margin is that the environment simply cannot reach anything but your registry, your git host and your model provider.

How to choose, in ten minutes

  1. Answer the two questions separately, on two lines: where does the control app live, and where do agent actions execute. If one answer covers both, you have not decided yet — you have accepted a default.
  2. Ask whose code the agent touches. Your own repos, on your own hardware, reviewed by you? A container is fine and you should stop over-thinking it. Someone else's repo, someone else's session, or a repo you did not audit? A shared kernel is now a security boundary you did not intend to draw, and that decision reaches into credentials, networking and concurrency, which makes it expensive to reverse later.
  3. Time your workspace setup. Clone plus install, measured honestly on your largest real repo. If it is under ten seconds, per-session environments are free and you have more options. If it is six minutes, workspace persistence or snapshot-restore stops being a nice-to-have and starts being the deciding criterion.
  4. Write down peak concurrent runtimes and the memory each one needs at its worst — with the browser tool open and a build running. That single number eliminates more options than any feature grid, and it is the number that decides whether one VM is a deployment or a countdown.
  5. Decide whether you want fan-out. If you plan to run the same task several ways in parallel and keep the winner, the cost of creating an environment is the thing that determines whether that strategy is affordable or theoretical. If you do not, ignore this entirely — it is a real capability and not everyone needs it.
  6. Then prove it with a spike, not a spreadsheet. Point the agent at your gnarliest real repo, run five concurrent sessions, and leave them for an afternoon. You will learn more about your hosting choice in those four hours than in a week of comparison pages, and you will learn it while nobody is depending on the answer.

The short version

A VM with Docker if it is your repos, your hardware and your risk — that configuration is popular because it is genuinely good at that job, and the security critique of it is a critique of using it for a different job. Kubernetes if the cluster already exists and someone already knows it, with a sandboxed runtime class if the sessions are not all yours. The project's hosted offering if you want the agent rather than the platform and your code may leave your perimeter, which for a lot of teams it can. A container PaaS for the control app, paired with something built for execution for the runtime, because the class of platform that makes web apps easy deliberately makes container-spawning hard. And a microVM per session — from us or from anyone credible in that category — when sessions run on behalf of other people, or against code nobody on your team read first.

Whichever you pick, the decisions that will still matter in a year are the same everywhere. Pin the app and runtime versions together. Garbage-collect workspaces before the disk teaches you to. Size memory for concurrent runtimes rather than concurrent conversations. Scope every credential the agent can see to one repo and a short life. And write down an honest answer to the question of whose code executes inside your runtime, because that answer — not the UI, not the model, not the benchmark score — is what your hosting choice is actually for.

Frequently asked questions

Can I run OpenHands without mounting the Docker socket?

Yes, and on a shared or multi-tenant host you should want to. The standard self-hosted configuration mounts the host Docker socket into the app container so it can start runtime containers for each session, and that mount is root-equivalent on the host: anything holding it can start a container with the host filesystem attached. On a laptop that is a non-issue. The alternative is the runtime abstraction — OpenHands treats the execution environment as a swappable component, and there is a remote-runtime path where the app talks to an execution service over the network instead of to a local Docker daemon. That is also the seam you use to point sessions at microVMs, a sandboxed container runtime, or any other execution backend. The configuration key names for selecting a runtime have changed more than once, so look them up for the release you are actually running.

How much memory does a self-hosted OpenHands deployment need?

Size it for concurrent runtimes, not concurrent conversations, because those two numbers are wildly different and only one of them shows up on a CPU graph. The agent loop is almost entirely I/O-bound — it spends its life waiting on a model provider's token stream — so a host running several conversations can look idle right up until the runtimes get busy. A single runtime doing real work can be holding a cloned repository, an installed dependency tree, a headless browser for the browsing tool, a build, and a test suite, all at once. Multiply that by peak concurrent sessions and add headroom, because agents do not politely stagger their expensive phases. The usual first production incident is not a security event, it is the OOM killer picking a victim that is not the agent.

Is a Docker container enough isolation for an OpenHands runtime?

It depends entirely on one question: whose code and whose sessions are involved. For your own repositories, on hardware you control, with only your team using it, a container gives you a clean filesystem per session and real resource limits, and that is a completely reasonable place to stop. It stops being reasonable when you run sessions on behalf of customers, when the agent works on repositories nobody on your team audited, or when several tenants share a host — because a container is namespaces and cgroups over a kernel that everyone shares, which is a packaging mechanism with some security properties rather than a security mechanism. When the commands are model-chosen and the code is not yours, the boundary you want is a hypervisor: a microVM with its own guest kernel, its own memory and its own network namespace, created for that session and destroyed after it.

How do I keep an OpenHands workspace between sessions without keeping a machine running?

Snapshot it. The expensive part of starting a session is not the machine, it is the workspace: cloning the repository and installing a dependency tree, which on a real front-end project is minutes rather than seconds. If your platform can freeze a prepared environment and restore it later, you can bake the clone and the install once and have every subsequent session start from that state, without paying for an idle machine between sessions. On PandaStack a restore is roughly 179ms at p50, and forking a prepared environment on the same host is 400-750ms with copy-on-write memory, so several sessions can branch from one warm workspace without multiplying the RAM. On container platforms the equivalent is a persistent volume plus an image with dependencies pre-installed, which works well but couples workspace lifetime to volume lifetime — and you will need to write the garbage collector yourself.

Should I restrict network egress from OpenHands sessions?

Yes, and it is probably the highest-value control on this entire list relative to its cost. An agent needs to reach a model provider, a package registry and a git host. It rarely needs to reach anything else, and the difference between a contained mess and a reportable incident is almost always whether something could leave the machine. Two failure modes make this concrete. An agent handed a credential can leak it — and a stolen token that cannot phone home is a much smaller problem. And an agent following a plausible-sounding instruction embedded in a repository's README or issue tracker is now an ordinary category of incident rather than an exotic one. Express the policy as a property of the environment, outside the guest, rather than as a rule the agent's own process could alter: a network policy on Kubernetes, or a per-session network namespace with a default-deny egress rule on a microVM platform.

Keep reading

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.