Rohit Malhotraandopenhands 979e64fe19 feat: add Docker CI to build all-in-one image with agent-server + automation + frontend (#634)
* feat: add Docker CI to build all-in-one image with agent-server + automation + frontend

Adds a GitHub Actions workflow (.github/workflows/docker.yml) that builds and
publishes ghcr.io/openhands/agent-canvas — a single Docker image combining:

  1. Agent Server (ghcr.io/openhands/agent-server base image from SDK repo)
  2. Automation server (pip-installed from openhands-automation)
  3. agent-canvas frontend (static build from this repo)

The automation server is pip-installed rather than copied from its Docker image
because both services share openhands-sdk, fastapi, uvicorn, pydantic, httpx
etc. — installing into the agent-server's Python 3.13 deduplicates all shared
packages. Only automation-specific deps (asyncpg, sqlalchemy, boto3, …) are
added on top.

An entrypoint script starts all three services and a static-server proxy that
unifies them behind a single port (default 8000):
  /api/automation/* → automation backend (:18001)
  /api/*            → agent-server (:18000)
  /*                → static frontend + SPA fallback

Workflow triggers:
  - Push to main: builds and pushes with branch + SHA tags
  - v* tags (releases): also pushes semver tags (1.2.3, 1.2, 1, latest)
  - PRs: builds, pushes SHA-tagged image, updates PR description with
    pull/run instructions (same pattern as the SDK repo)
  - workflow_dispatch: supports overriding base image and automation version

Files added:
  - docker/Dockerfile (multi-stage: frontend build + agent-server base)
  - docker/entrypoint.sh (process manager for all three services)
  - .dockerignore
  - .github/workflows/docker.yml

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: build multi-arch Docker images (amd64 + arm64)

Adds QEMU setup for cross-compilation and defaults the platform matrix
to linux/amd64,linux/arm64 so the image works on both Intel and Apple
Silicon machines.

Co-authored-by: openhands <openhands@all-hands.dev>

* refactor: rewrite Docker workflow to match SDK repo structure

Replace the single-job QEMU approach with the same architecture-matrix
pattern used by the SDK repo's server.yml:

  1. build-and-push-image — matrix over {amd64, arm64} with native runners
     (ubuntu-24.04 for amd64, ubuntu-24.04-arm for arm64). Each job pushes
     arch-suffixed tags (e.g. sha-abc1234-amd64) and uploads build-info
     artifacts.

  2. merge-manifests — downloads both arch build-infos, strips the -amd64
     suffix from amd64 tags to derive manifest tags, and creates multi-arch
     manifests via `docker buildx imagetools create`.

  3. consolidate-build-info — aggregates all build-info and manifest-info
     artifacts into a single JSON summary (PR-only).

  4. update-pr-description — renders the summary into the PR body between
     AGENT_CANVAS_DOCKER_START/END markers.

Native runners avoid the 3-5× slowdown of QEMU emulation for arm64
builds.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: sanitize branch names in Docker tags (/ is not allowed)

Branch names like 'feat/docker-ci' produce invalid Docker tags because
'/' is forbidden in tag names. Replace '/' with '-' so the tag becomes
'feat-docker-ci-amd64'.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: default automation to SQLite and fix wait blocking proxy startup

Two bugs:

1. The automation server defaults to PostgreSQL on localhost, which
   doesn't exist in the all-in-one container. Default AUTOMATION_DB_URL
   to sqlite+aiosqlite:// so it works out of the box. Users can override
   with a real Postgres URL for production.

2. The bare 'wait' command waited for ALL background children — including
   the long-running agent-server and automation processes — so the
   static-server/proxy on port 8000 never started. Fix by waiting only
   for the wait_for_port subshell PIDs.

Verified locally: all three services start, endpoints respond correctly,
no more scheduler ConnectionRefusedError.

Co-authored-by: openhands <openhands@all-hands.dev>

* feat: add VOLUME directives for persistence and project mounts

Declare /home/openhands/.openhands (settings, secrets, conversations,
automation SQLite DB) and /projects (user code) as Docker volumes so
data survives container restarts by default. Users should bind-mount
these for durable persistence:

  docker run -v ~/.openhands:/home/openhands/.openhands \
             -v ~/projects:/projects \
             -p 8000:8000 ghcr.io/openhands/agent-canvas

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: set OH_SECRET_KEY default and pre-create persistence dirs

Three issues fixed:

1. OH_SECRET_KEY was not set → agent-server refused to return encrypted
   secrets → conversation creation failed with 503. Set the same static
   default used by dev-safe.mjs / dev-docker.mjs.

2. Persistence dirs (conversations, bash_events, automation DB) were not
   pre-created → the openhands user got PermissionError when the VOLUME
   directive created them as root. Pre-create with correct ownership
   before the USER switch in the Dockerfile.

3. Set OH_PERSISTENCE_DIR, OH_CONVERSATIONS_PATH, OH_BASH_EVENTS_DIR
   defaults in the entrypoint (matching dev-docker.mjs) so data lands
   under the well-known ~/.openhands tree.

Verified locally: all three services start clean, no warnings about
OH_SECRET_KEY, SQLite migrations apply successfully.

Co-authored-by: openhands <openhands@all-hands.dev>

* chore: merge main and remove stale dev-docker.mjs references

Main removed scripts/dev-docker.mjs (Docker is no longer a dependency of
the npm package flow). Update comments in docker.yml, entrypoint.sh, and
AGENTS.md that referenced the deleted file.

Co-authored-by: openhands <openhands@all-hands.dev>

* feat: centralize config into config/defaults.json (single source of truth)

All version pins, port defaults, persistence paths, package names, and
the dev secret key now live in config/defaults.json. Consumers read from
it instead of hardcoding values:

- scripts/dev-safe.mjs: reads via JSON.parse(readFileSync(...))
- scripts/dev-with-automation.mjs: same
- scripts/check-sdk-version-sync.mjs: same (no longer regex-parses JS)
- docker/Dockerfile: config-gen build stage converts JSON to
  /opt/agent-canvas/defaults.env (shell-sourceable)
- docker/entrypoint.sh: sources defaults.env at startup; also adds
  session API key auto-generation so the image doesn't run wide-open
- .github/workflows/docker.yml: reads versions from JSON in a setup
  step (no more hardcoded env vars)

To bump a version, edit config/defaults.json only.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: address PR review feedback (#634)

- Fix PID tracking bug: move PIDS+=($!) inside if/elif branches so the
  else (automation-not-found) path doesn't add a stale PID
- chmod 600 session API key file to prevent credential leak
- Warn when using insecure default OH_SECRET_KEY in Docker entrypoint
- Add try/catch + field validation for config/defaults.json loading in
  check-sdk-version-sync.mjs
- Fix semver tag parsing: strip pre-release/build metadata, only create
  abbreviated tags (major.minor, major, latest) for stable releases
- Sanitize branch names for Docker tags (tr invalid chars, strip leading
  dot/dash) to handle branches with #, @, spaces, etc.
- Add arch validation before manifest merge (assert both amd64.json and
  arm64.json exist)
- Remove $schema reference to non-existent defaults.schema.json

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: remove hardcoded version defaults from Dockerfile

Replace hardcoded ARG defaults (AGENT_SERVER_IMAGE, AUTOMATION_VERSION)
with empty ARGs. Values are always derived from config/defaults.json:
- CI: reads JSON in the workflow config step, passes --build-arg
- Local: new scripts/docker-build.mjs helper reads JSON and invokes
  docker build with the correct --build-arg values

Added npm run build:docker convenience script.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: stabilize snapshot tests and auto-generate Docker secret key

Two fixes:

1. **Flaky snapshot tests**: The 'Local pagination fixture' mock conversation
   used a fixed absolute timestamp (PAGINATION_BASE_TIME = May 13, 2026) for
   its created_at/updated_at, while 'Errored Project' used a relative
   timestamp (now - 7d). As real time progressed past the crossover point,
   their sort order in the sidebar flipped, causing 30/73 snapshot diffs on
   every PR. Fix: use relative timestamps (now - 6d) for the pagination
   fixture's conversation listing fields. The internal event timestamps
   (used by pagination tests) still use PAGINATION_BASE_TIME — only the
   sidebar ordering is affected.

2. **Docker OH_SECRET_KEY**: The entrypoint used a static insecure default
   for OH_SECRET_KEY and warned about it. Now mirrors the session API key
   pattern: auto-generate a cryptographic random key on first run, persist
   it to ~/.openhands/agent-canvas/secret-key.txt, and reuse on restart.
   Users can still override via the OH_SECRET_KEY env var. Removed the
   now-unused CONFIG_SECRET_KEY from the Docker defaults.env generation.
   Also deduped STATE_DIR computation (was repeated for session key path).

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: update AGENTS.md with mock timestamp and Docker secret key notes

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: include canvas_ui tool in Docker image

The Docker image was missing the tools/ directory and OH_EXTRA_PYTHON_PATH,
so the agent-server couldn't import canvas_ui_tool.py when the frontend
sent canvas_ui in the conversation tools list. This caused:

  HTTP 500: ToolDefinition 'canvas_ui' is not registered

Fix: COPY tools/ into the image and set OH_EXTRA_PYTHON_PATH in the
entrypoint, matching what scripts/dev-safe.mjs already does for local dev.

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-20 04:24:45 +00:00
2026-04-24 17:33:22 -04:00

agent-canvas

Warning

This project is in alpha phase. It may be vibecoded, untested, or out of date. Learn more.

OpenHands is a platform for orchestrating coding agents across different environments. You can:

  • ⌨️ prompt agents manually
  • 🕐 run agents on a schedule
  • ⚡ trigger agents automatically — e.g. from Slack, GitHub, or Datadog.

Agents can run anywhere:

  • 🧑‍💻 on your laptop
  • 🖥️ on a remote virtual machine
  • ☁️ in our hosted cloud
  • 🏢 or inside your company’s infrastructure

The same Agent Canvas frontend can swap between each of these environments, so you can see everything in one place.

OpenHands works with any agent harness (e.g. Claude Code, Codex) or connect directly to an LLM (e.g. Anthropic, OpenAI, Gemini, Mistral, Minimax, Kimi).

If you have questions or feedback, please open a GitHub issue or join the #proj-agent-canvas channel in Slack

Screenshot 2026-05-11 at 10 13 19 AM

Quickstart

Direct Install

You can install OpenHands to run agents on any machine: on your laptop, on a dedicated computer like a Mac Mini, or on a server in the cloud.

The most powerful way to run OpenHands is on a server in the cloud. This allows your agents to continue running even when your laptop is shut, and makes it easier to trigger your agents through third-party services like Slack, GitHub, and Datadog. See SELF_HOSTING.md for details, especially with respect to security hardening.

Notably, you can run the backend in multiple different environments, and switch between them from the same Agent Canvas frontend. E.g. you can share an Agent Server with your team for agents doing code review and dependency updates, then have your personal agents running on your laptop.

Warning

This runs the agent-server directly on the machine you're installing on--the agent will have full access to your filesystem!

Prerequisites:

  • Node.js 22.12.x or later
  • npm
  • uv (for running the agent server via uvx)
git clone https://github.com/OpenHands/agent-canvas.git
cd agent-canvas
npm install
npm run dev

Access the UI at http://localhost:8000. You can add additional backends directly from the UI.

Architecture

Agent Canvas is powered by the OpenHands Agent Server, a REST API for running multiple agents on a single machine. Each Agent Server runs on a single host/port; the Agent Canvas can connect to multiple Agent Servers and easily flip between them.

You can run an Agent Server anywhere:

  • Directly on your laptop (be careful!)
  • On a dedicated machine like a Mac Mini
  • On a virtual machine in the cloud
  • Inside OpenHands Cloud (our commercial offering)

The Agent Server is often paired with an Automation Server, which lets you set up agents that run on a schedule or in response to events.

image

More documentation

For contributor and developer workflows, including frontend-only mode, mock mode, environment variables, and build/test commands, see DEVELOPMENT.md.

S
Languages
TypeScript 93.7%
JavaScript 4.7%
Python 0.9%
Shell 0.3%
CSS 0.2%
Other 0.1%