Skip to main content
Deploy the OpenHands agent server on Modal as a remote backend for Agent Canvas. Canvas runs locally on your machine while the agent server runs on Modal and executes code inside the container — same execution model as running npx @openhands/agent-canvas locally.
The agent server runs with full access to the container’s filesystem, environment, and network. Anyone with the API key can execute arbitrary code on your Modal container. Keep the API key secret and rotate it if it’s ever exposed.

When to Use It

A Modal backend is a good fit when you want to:
  • Offload agent execution to the cloud without managing your own VM or Docker host
  • Take advantage of Modal’s per-second billing and free-tier credits
  • Get a persistent, always-warm backend with minimal setup — or scale to zero when idle to reduce costs

Prerequisites

  • A Modal account (free tier includes $30/month credit)
  • Python 3.12+
  • Agent Canvas running locally — see Setup
  • An LLM API key (OpenAI, Anthropic, etc.)

1. Install the Modal CLI

modal setup opens a browser to authenticate. Your credentials are saved to \~/.modal.toml.

2. Create a Modal Secret

Generate an API key and encryption key, then store them as a Modal secret:
Copy the API_KEY value now. You’ll paste it into Agent Canvas in step 4. The encryption key (OH_SECRET_KEY) stays on Modal — you don’t need to save it separately.
This secret persists in your Modal account. You only need to create it once.

3. Deploy

Save the following as deploy.py:
Then deploy:
Modal builds the container image on first deploy (takes a few minutes), then prints the serving URL:
The agent server runs on 2 vCPU / 4 GB RAM with a persistent volume for conversations and settings. By default, the container is always warm (min_containers=1) so there’s no cold-start latency. To scale to zero when idle instead (lower cost, but ~10-30s cold start on first request):
See Cost for a comparison of the two modes.

4. Connect Agent Canvas

  1. Open Agent Canvas locally (npx @openhands/agent-canvas).
  2. Click the backend switcher → Manage BackendsAdd Backend.
  3. Fill in:
    • Name — e.g. Modal
    • Host / Base URL — the URL from step 3 (e.g. https://openhands-agent-server--agentserver-serve.modal.run)
    • API Key — the API_KEY value from step 2
  4. Save and select it as the active backend.
The URL must use https://, not http://. Modal redirects HTTP to HTTPS with a 308, which breaks CORS preflight requests.

5. Configure Your LLM

The agent server doesn’t come with LLM credentials — you provide them once through the Canvas UI:
  1. With the Modal backend selected, open Settings.
  2. Choose a provider (e.g. OpenAI, Anthropic).
  3. Enter your API key and select a model.
  4. Save.
Settings are stored server-side on the Modal volume (encrypted with OH_SECRET_KEY) and persist across redeploys.

Cost

Modal charges per-second for CPU and memory. The MODAL_ALWAYS_ON setting controls whether the container stays warm between requests: Hourly rate breakdown (2 vCPU / 4 GB): Always-on costs ~$3.40/day (~$102/month). Modal’s $30/month free credit covers about 9 days. Scale-to-zero costs only for the hours the container is running. At 8 hours/day on workdays, that’s roughly ~$1.12/day (~$25/month). The first request after an idle period takes ~10-30s while the container cold-starts; after that, the scaledown_window (10 min) keeps it warm between interactions. To stop the deployment entirely and avoid all charges: modal app stop openhands-agent-server. Your data on the Modal volume persists.
If you’re using scale-to-zero and find the container scaling down too quickly between interactions, increase SCALEDOWN_WINDOW in deploy.py. The default is 600 seconds (10 minutes); setting it to 1800 (30 minutes) keeps the container warm during longer breaks without paying for overnight idle time.

Limitations

  • No Docker-in-Docker. Modal containers don’t support nested Docker. The agent executes code directly on the container filesystem (same model as running npx @openhands/agent-canvas locally). Tools that require Docker won’t work.
  • Single-user only. Pinned to one container (max_containers=1) because the agent server uses SQLite and in-memory state that can’t be shared across containers.
  • Public URL. The *.modal.run endpoint is internet-reachable. All API endpoints require the API key, but the URL itself is public.

Security

The agent server is protected by the API key you created in step 2. Every REST and WebSocket request is rejected without it. Modal provides TLS on all *.modal.run endpoints automatically. The *.modal.run URL is not indexed or easily guessable, but treat it as sensitive — it appears in terminal output, browser history, and Canvas localStorage.

Rotating the API Key

If you suspect the API key has been leaked:
Then update the API key in Agent Canvas — click the backend switcher → Manage Backends → edit the Modal backend → paste the new key.

Upgrading

To update to a newer agent-server version, change AGENT_SERVER_IMAGE_TAG in deploy.py to the desired tag (e.g. 1.25.0-python) and redeploy:
Modal rebuilds the container image with the new version. Your data on the Modal volume (conversations, settings, LLM credentials) is preserved. Available tags are listed at ghcr.io/openhands/agent-server. Use the -python variant.

Troubleshooting

Check the server logs:
List running apps to confirm the deployment is active:
If the container is crashing or unresponsive, redeploy to force a fresh start:
Your data on the Modal volume persists across redeploys.

Tearing Down

To stop the deployment and stop incurring costs:
Your data on the Modal volume (openhands-data) is preserved. Redeploy later with modal deploy deploy.py and everything picks up where you left off. To permanently delete the volume: