Skip to content

Docker Deployment

JoKeks2023 edited this page Sep 28, 2026 · 2 revisions

Docker Deployment

Use the Docker image when the MCP server must be reachable over the network (remote MCP clients, a shared server). Local clients on your own machine don't need it. They launch the server via npx over stdio (see Getting Started).

Image

Docker Hub jokeks2023/huly-mcp
GHCR ghcr.io/jokeks2023/huly-mcp (same image)
Tags latest (built from main), <version> for v* git tags, short commit SHA
Built by .github/workflows/docker-publish.yml on push to main, v* tags, manual dispatch

latest is built from main and includes the multi-workspace release, so HULY_WORKSPACE is no longer needed. Images built before PR #1 (e.g. tag 55fd94f) still use the old single-workspace model. After pulling a new image, remove HULY_WORKSPACE from .env if you want all workspaces. If your docker-compose.yml still references ${HULY_WORKSPACE}, remove that line too; otherwise Compose warns "The HULY_WORKSPACE variable is not set". The warning is harmless, because an empty value means no restriction.

How the container works

Dockerfile, two stages:

  1. Builder (node:22-slim): npm ci, npm run build → dist/.
  2. Runtime (node:22-slim):
    • installs Python + pinned mcp==1.29.0 and mcp-proxy==0.12.0
    • installs production npm dependencies
    • runs as user node
    • exposes port 8000

The entrypoint:

mcp-proxy --host=0.0.0.0 --port=8000 --stateless --pass-environment -- node dist/index.js
  • mcp-proxy starts node dist/index.js once as a stdio child process at startup and bridges it to HTTP. All HTTP clients share this one process. They share the account session and the per-workspace connection cache. Every tool call still names its own workspace, so clients don't influence each other's workspace choice.
  • --pass-environment passes the container environment (your HULY_* variables) to the child.
  • --stateless: streamable-HTTP requests don't need a server-side MCP session.

Endpoints served by mcp-proxy 0.12:

Path Transport
/mcp Streamable HTTP (current MCP transport)
/sse (+ /messages/) Server-Sent Events (older MCP transport)
/status Health/status

.env

Copy .env.example to .env next to docker-compose.yml and fill in credentials. No workspace is needed:

HULY_EMAIL=you@example.com
HULY_PASSWORD=your-password
# self-hosted:
HULY_ACCOUNTS_URL=https://huly.example.com/_accounts
HULY_FRONT_URL=https://huly.example.com
# optional, legacy: restrict to one workspace
# HULY_WORKSPACE=acmeteam

.env is excluded from the image build (.dockerignore) and from git (.gitignore). It is only read at runtime via env_file.

Run

Compose

docker-compose.yml: image jokeks2023/huly-mcp:latest, restart: unless-stopped, env_file: .env, port 8000:8000, no-new-privileges.

docker compose up -d
docker compose logs -f
docker compose pull && docker compose up -d                        # update to the latest image
docker compose build --no-cache && docker compose up -d --force-recreate   # with `build: .` enabled

docker run

docker run -d --name huly-mcp -p 8000:8000 --env-file .env jokeks2023/huly-mcp:latest

Remote MCP

Point a client that supports remote MCP servers at:

https://mcp.example.com/mcp     # streamable HTTP
https://mcp.example.com/sse     # SSE, for clients that only support SSE

Example for Claude Code:

claude mcp add --transport http huly https://mcp.example.com/mcp

Put authentication in front of it

The container does not authenticate MCP clients. Anyone who can reach port 8000 can use every tool as the configured Huly account, in all of its workspaces. Therefore:

  • Don't publish port 8000 on a public interface. Bind it to localhost or a private network (e.g. 127.0.0.1:8000:8000).
  • Expose it only through a reverse proxy with authentication (OAuth2 proxy, basic auth, mTLS), a VPN, or a private network.
  • For SSE through a reverse proxy, disable response buffering (e.g. nginx proxy_buffering off;).
  • Consider a dedicated Huly account, and HULY_WORKSPACE to limit the server to one workspace.

See Security.

Networking to Huly

The container must reach:

  • HULY_ACCOUNTS_URL
  • the transactor WebSocket endpoint returned by selectWorkspace(…, 'external')
  • HULY_FRONT_URL

For a self-hosted Huly on the same host, localhost inside the container is the container itself. Use the public hostname, a shared Docker network, or host.docker.internal. See Self-Hosted Huly.

Clone this wiki locally