Skip to content

TutorialsAI & LLM

How to self-host the OpenHands AI agent platform with Docker

Run OpenHands Agent Canvas with Docker on Ubuntu or Debian: API key login, HTTPS through Caddy, hosted or Ollama models, sandbox limits, backups and updates.

  • Advanced
  • 40 min read
  • Updated

Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

This guide is not available in your language yet, so it is shown in English.

On this page
  1. Prerequisites
  2. Step 1 — Create the folders and secrets
  3. Step 2 — Write the Compose file and fix ownership
  4. Step 3 — Start OpenHands and check it
  5. Step 4 — Publish Canvas over HTTPS with an IP allowlist
  6. Step 5 — Sign in and connect a model provider
  7. Step 6 — Use a local model through Ollama (optional)
  8. Step 7 — Keep the agent's sandbox tight
  9. Back up and restore
  10. Update OpenHands
  11. Troubleshooting
  12. Canvas keeps asking for the API key or the API returns 401
  13. Permission denied under /home/openhands/.openhands
  14. The agent with an Ollama model ignores instructions or stops early
  15. Connection refused to host.docker.internal:11434
  16. Caddy answers 403 Forbidden
  17. Next steps

An AI agent framework lets a language model plan multi-step tasks and act on them with tools, such as running shell commands, editing files or calling APIs, instead of only producing text. Most frameworks are libraries you build into your own application. OpenHands also offers that, as its Software Agent SDK for Python, but it additionally ships Agent Canvas: a self-hosted web application where coding agents work on your repositories, backed by an agent server with a REST API and scheduled automations. That makes it a natural fit for a server: one container, persistent state and a browser interface.

If you want to build your own agent application instead, these actively maintained open-source frameworks are common starting points (all had releases in the weeks before this guide was checked):

FrameworkWhat it isLanguage
LangGraphLow-level orchestration for stateful agentsPython (separate JavaScript version)
CrewAIFramework for teams of role-based agentsPython
Microsoft Agent FrameworkAgents and multi-agent workflows; the successor path for AutoGen, which is in maintenance modePython and .NET
PydanticAITyped agent framework from the Pydantic teamPython
MastraFramework for AI applications and agentsTypeScript

This guide runs the official ghcr.io/openhands/agent-canvas image with Docker Compose. You pin the version, set your own API key and secret key, keep the port on localhost, publish Canvas through Caddy with an IP allowlist, connect a hosted model provider or a local Ollama server, and limit what the agent can reach. Backups, updates and troubleshooting follow.

Prerequisites

  • A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13 with Docker Engine and the Compose plugin, see Install Docker on Ubuntu or Install Docker on Debian. The OpenHands VM guide uses Ubuntu 24.04 LTS as its default.
  • A non-root user with sudo rights and SSH key login, see Secure a new Linux server and Set up SSH keys.
  • A domain name such as agents.example.com with an A (and optionally AAAA) record pointing at the server, and Caddy from Caddy reverse proxy.
  • An API key for a capable model (OpenHands lists Anthropic, OpenAI, Mistral AI and its own provider as verified), or Ollama from Install Ollama on a server with a suitable GPU.
ResourceMinimum (official)Suggested starting point
CPU2 vCPU for a single user4 vCPU when agents build and test code
RAM4 GB for a single user8 GB for builds, tests and several conversations
DiskNot published40 GB for images, repositories and dependencies

The official figures come from the OpenHands VM guide. The suggested column is a conservative starting point, not a benchmark; local models need their own GPU memory on top.

Step 1 — Create the folders and secrets

Bash
sudo mkdir -p /opt/openhands/state /opt/openhands/projects
sudo chown -R $USER:$USER /opt/openhands
cd /opt/openhands
cat > .env <<EOF
OPENHANDS_VERSION=1.26.0
LOCAL_BACKEND_API_KEY=$(openssl rand -hex 32)
OH_SECRET_KEY=$(openssl rand -hex 32)
EOF
chmod 600 .env
  • state becomes /home/openhands/.openhands in the container: settings, stored secrets, conversation history, workspaces and the automation database.
  • projects becomes /projects, the only place for repositories you want the agent to work on.
  • LOCAL_BACKEND_API_KEY is the server's API key. Every API call must carry it, and you type it into the browser when you open Canvas. The documentation asks for a strong value whenever the server is reachable beyond localhost.
  • OH_SECRET_KEY protects stored settings and secrets. If you leave either key out, the image generates one and stores it in state/agent-canvas/ with mode 600, but setting both yourself keeps them in one known place.
  • OPENHANDS_VERSION pins the image. Releases are frequent; take the current number from the OpenHands releases page.

Step 2 — Write the Compose file and fix ownership

Create /opt/openhands/compose.yaml. It is the documented docker run command for the official image, expressed as a Compose service with a restart policy, a localhost-only port and your keys:

YAML
services:
  openhands:
    image: ghcr.io/openhands/agent-canvas:${OPENHANDS_VERSION}
    container_name: openhands
    restart: unless-stopped
    ports:
      - "127.0.0.1:8000:8000"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - LOCAL_BACKEND_API_KEY=${LOCAL_BACKEND_API_KEY}
      - OH_SECRET_KEY=${OH_SECRET_KEY}
      - AGENT_CANVAS_DISABLE_TELEMETRY=true
    volumes:
      - ./state:/home/openhands/.openhands
      - ./projects:/projects

There is deliberately no privileged: true and no /var/run/docker.sock mount. The image runs as the non-root openhands user, so the two folders must belong to that user's UID and GID. Ask the image for them and set the ownership:

Bash
docker compose pull
OH_UID=$(docker compose run --rm -T --entrypoint id openhands -u)
OH_GID=$(docker compose run --rm -T --entrypoint id openhands -g)
echo "$OH_UID:$OH_GID"
sudo chown -R "$OH_UID:$OH_GID" state projects

The echo prints the numbers, for example 1000:1000. To place repositories in projects yourself later, use sudo or let the agent clone them.

Step 3 — Start OpenHands and check it

Bash
docker compose up -d
docker compose ps
docker compose logs --tail 50 openhands
curl -I http://127.0.0.1:8000/canvas

docker compose ps should show the container running with 127.0.0.1:8000->8000/tcp, and curl should return HTTP/1.1 200 OK or a redirect. Inside the container, an ingress on port 8000 routes the web interface, /api and the WebSocket endpoint to the agent server and automation backend, which listen on internal ports only.

Step 4 — Publish Canvas over HTTPS with an IP allowlist

Add a site block to /etc/caddy/Caddyfile. Restrict it to the addresses you work from by replacing 198.51.100.24; the API key is the second lock, not the only one:

Caddyfile
agents.example.com {
    @outside not remote_ip 198.51.100.24
    respond @outside 403
    reverse_proxy 127.0.0.1:8000
}
Bash
sudo systemctl reload caddy
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
curl -I https://agents.example.com/canvas

The OpenHands Nginx example sets WebSocket headers and one-hour timeouts for live agent events. Caddy proxies WebSockets automatically and has no read timeout by default, so no extra settings are needed. If you use Nginx instead, copy those settings from the OpenHands VM guide or Nginx with Certbot.

Step 5 — Sign in and connect a model provider

Open https://agents.example.com/canvas. Because the Docker image does not inject its key into the page, Canvas asks for it first; print it on the server with grep LOCAL_BACKEND_API_KEY /opt/openhands/.env and paste it in.

Then open the LLM settings. Choose an LLM Provider and LLM Model, enter the provider's API Key and click Save Changes; new conversations use the setting, existing ones must be restarted. Under Advanced you can enter any model LiteLLM supports, with the provider as a prefix, and a custom base URL. Saved configurations become LLM profiles (up to 10), which you can switch per conversation.

The documentation says OpenHands needs a powerful model to work properly. Create a separate API key for OpenHands with a spending limit at your provider, so a runaway task cannot run up unlimited usage.

Step 6 — Use a local model through Ollama (optional)

Agent workloads need long contexts. The OpenHands documentation asks for an Ollama context length of at least 22000 tokens and warns that the default of 4096 is far too small. Set it together with the listen address in a systemd drop-in:

Bash
sudo mkdir -p /etc/systemd/system/ollama.service.d
sudo tee /etc/systemd/system/ollama.service.d/override.conf <<'EOF'
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
Environment="OLLAMA_CONTEXT_LENGTH=32768"
EOF
sudo systemctl daemon-reload
sudo systemctl restart ollama
docker network inspect openhands_default | grep Subnet
sudo ufw allow from 172.18.0.0/16 to any port 11434 proto tcp
docker compose exec openhands curl -s http://host.docker.internal:11434/v1/models

Use the subnet the inspect command prints instead of 172.18.0.0/16. This rule only protects Ollama while ufw is active with its default deny policy, so confirm that sudo ufw status verbose shows Status: active and deny (incoming), and that nc -vz your-server-ip 11434 from another machine fails. Ollama has no authentication, so never open port 11434 to everyone. The last command should list your models. In the LLM settings, switch to Advanced and enter:

  • Custom Model: openai/ followed by the model name from ollama list, for example openai/qwen3.6:35b-a3b, the model the OpenHands guide uses.
  • Base URL: http://host.docker.internal:11434/v1
  • API Key: any placeholder such as local-llm.

The documentation's example model needs a GPU with at least 24 GB of memory for quantized variants and notes that local models may have limited functionality. On CPU-only servers, use a hosted provider; for local inference, look at GPU servers.

Step 7 — Keep the agent's sandbox tight

In this setup the container is the sandbox: the agent runs commands as the openhands user inside it and reaches only what you mount and what the network allows. Keep it that way:

  • No Docker inside. The documentation explains that letting the agent run Docker requires --privileged, which gives broad access to the host kernel, and that there is no safer middle ground. Do not add it, and never mount the Docker socket.
  • Mount little. Put only the repositories the agent should touch into /opt/openhands/projects. The agent can also read the state folder, including stored provider keys, so never keep unrelated secrets there.
  • Scope credentials. Give the agent fine-grained, repository-scoped access tokens with the minimum permissions and an expiry date, plus the separate model key with a spending limit from Step 5.
  • Limit who can reach it. Keep the Caddy allowlist or a VPN, and rotate LOCAL_BACKEND_API_KEY (edit .env, then docker compose up -d) if it was shared too widely.
  • Review before you merge. Treat the agent's commits like a contributor's pull request.

Back up and restore

Everything is in /opt/openhands: state (settings, secrets, conversations, automation database), projects, .env and compose.yaml. Stop the container for a consistent copy:

Bash
sudo mkdir -p /opt/backups
cd /opt/openhands
docker compose stop
sudo tar czf /opt/backups/openhands-$(date +%F).tar.gz -C /opt openhands
docker compose start

To restore, unpack the archive to /opt on a server with Docker and start it; tar keeps the numeric ownership:

Bash
sudo tar xzf /opt/backups/openhands-2026-10-09.tar.gz -C /opt
cd /opt/openhands
docker compose up -d

The archive contains your API keys and provider credentials, so encrypt it before it leaves the server and keep copies off the server. Push the agent's work in projects to your Git hosting as well; a repository remote is the better backup for code.

Update OpenHands

OpenHands publishes releases often. Read the release notes between your version and the target, take a backup, then change the tag and recreate the container:

Bash
cd /opt/openhands
nano .env
docker compose pull
docker compose up -d
docker compose logs --tail 50 openhands

In nano, set OPENHANDS_VERSION to the new release number. Finish or stop running conversations before you update, because recreating the container interrupts them.

Troubleshooting

Canvas keeps asking for the API key or the API returns 401

The key you entered does not match LOCAL_BACKEND_API_KEY. Print it with grep LOCAL_BACKEND_API_KEY /opt/openhands/.env, and after changing it run docker compose up -d so the container picks up the new value.

Permission denied under /home/openhands/.openhands

The state or projects folder belongs to the wrong user. Repeat the ownership commands from Step 2 and restart with docker compose up -d.

The agent with an Ollama model ignores instructions or stops early

The context is too short. Confirm OLLAMA_CONTEXT_LENGTH with systemctl show ollama --property=Environment, restart Ollama, and start a new conversation. Smaller models may simply not be capable enough for agent work.

Connection refused to host.docker.internal:11434

Ollama still listens on 127.0.0.1, or ufw blocks the Docker subnet. Check ss -tln | grep 11434 and sudo ufw status, then repeat the test command from Step 6.

Caddy answers 403 Forbidden

Your public IP address is not in the remote_ip line. Update it and run sudo systemctl reload caddy.

Next steps

Frequently asked questions

What is the difference between OpenHands and frameworks like LangGraph or CrewAI?

LangGraph, CrewAI and similar projects are libraries you build into your own code. OpenHands also ships a Python SDK, but its Agent Canvas is a ready-made, self-hosted web application with an agent server, so you can run agents on your repositories without writing an application first.

Is it safe to let the agent run commands on my server?

The agent runs arbitrary shell commands inside the container and can read everything mounted into it, including stored settings. Run it on a server dedicated to this purpose, mount only the projects it needs, use narrowly scoped tokens and never give the container privileged mode or the Docker socket.

Can OpenHands use a local model through Ollama?

Yes, through Ollama's OpenAI-compatible endpoint: model openai/ plus the Ollama model name, base URL http://host.docker.internal:11434/v1 and any placeholder API key. The documentation asks for a context length of at least 22000 tokens, and capable local models need a GPU with plenty of memory.

Older guides use docker.openhands.dev/openhands/openhands with the Docker socket. Which is right?

That image is the older Local GUI, which the OpenHands documentation now describes as deprecated. Agent Canvas, published as ghcr.io/openhands/agent-canvas, is the current self-hosted application and does not need the Docker socket.

Sources

Passwort generieren

Please confirm