Blacknode includes a Docker Compose path for local, cloud VM, and on-prem demos. It starts three services. Local development can build from source, while published deployments can use prebuilt GHCR images.
| Service | Port | Purpose |
|---|---|---|
editor |
3000 |
React visual editor. |
editor-server |
7777 |
FastAPI backend, workflow store, run store, cook API. |
blacknode-mcp |
9901 |
Streamable HTTP MCP server at /mcp. |
The dev stack (docker-compose.yml) publishes these ports for local access. The
production stack (docker-compose.published.yml) does not: editor-server
is internal-only, editor/blacknode-mcp bind to loopback, and public access
goes through the Caddy proxy profile. See
Production deployment.
On Windows, use the helper. It starts Docker Desktop when needed, bounds every engine readiness check, and prevents two Blacknode Compose launchers from running at the same time:
.\docker-up.ps1If Docker Desktop's WSL engine remains stuck while another WSL distribution is running, save that distribution's work and request a full WSL repair:
.\docker-up.ps1 -RepairWslCheck Docker readiness without starting the Compose services:
.\docker-up.ps1 -CheckOnlyStop and remove the Blacknode Compose containers and network:
.\docker-down.ps1Add -Volumes only when the Compose volumes should also be removed.
On Linux, macOS, or after confirming docker info works:
docker compose up --buildhttp://127.0.0.1:3000
Load a template, click Cook on an Output node, and inspect the Runs tab.
http://127.0.0.1:9901/mcp
MCP clients connect to that endpoint with streamable-http. A browser GET can
show a protocol response instead of a page; use an MCP client to list tools.
To use hosted NVIDIA NIM, create a local .env from the example and set
NVIDIA_API_KEY:
cp .env.example .envCopy-Item .env.example .envThen edit .env and start the stack:
.\docker-up.ps1Local state mounted from the checkout:
workflows/editor-server/runs/
This Compose stack is the local and self-hosted evaluation path for the visual editor, backend, persisted run history, saved workflows, and streamable HTTP MCP endpoint.
After the images are published, use:
docker compose -f docker-compose.published.yml upSee Docker Publishing for GHCR publishing and image tags.
Security boundary — read first. The
editor-serverexecutes user-authored Python during a cook (PythonFnand the Script editor) and has no built-in authentication. Anyone who can reach it can run arbitrary code on the host and read any key typed into the UI. Treat it as a trusted-operator tool: never publish7777,3000, or9901to a public interface.docker-compose.published.ymlbinds the editor and MCP to loopback only and exposes the stack to real users solely through an authenticating TLS reverse proxy (Caddy, theproxyprofile).
cp .env.example .envSet, in .env:
-
Model keys (
NVIDIA_API_KEY,OPENAI_API_KEY,ANTHROPIC_API_KEY, …). Provide keys here via env, not through the editor UI key panel — env keys are never written toeditor-server/api_keys.jsonand are never returned by the/settings/api-keysendpoint. -
BLACKNODE_IMAGE_TAG— pin to a published 12-char commit SHA instead oflatestfor reproducible deploys. -
BLACKNODE_DOMAIN— the public hostname (a real domain gets an automatic Let's Encrypt certificate;localhostuses Caddy's internal CA for testing). -
BLACKNODE_BASICAUTH_USERandBLACKNODE_BASICAUTH_HASH. Generate the hash:docker run --rm caddy:2.8-alpine caddy hash-password --plaintext 'your-password'
docker compose -f docker-compose.published.yml --profile proxy up -dCaddy terminates TLS, enforces Basic Auth on every request, and reverse-proxies
to the internal editor (/) and MCP (/mcp) services. Only Caddy binds the
public 80/443 ports; the editor (3000) and MCP (9901) stay on loopback,
and editor-server (7777) is never published.
Open https://<your-domain>/; MCP clients connect to https://<your-domain>/mcp.
Without --profile proxy, the stack still starts but is reachable only at
http://127.0.0.1:3000 (and :9901) on the host — use that for a single
trusted operator on the box, never for remote users.
restart: unless-stoppedand healthchecks on every service.editor-serverruns as a non-root user (appuser, uid 10001).- Per-service CPU/memory limits (
deploy.resources.limits). - An isolated
blacknodebridge network.
State lives in named Docker volumes (survives down/up and reboots):
| Volume | Holds |
|---|---|
blacknode_workflows |
Saved workflow JSON. |
blacknode_runs |
Run history / replay records. |
caddy_data |
TLS certificates and keys. |
The live editing canvas (editor-server/blacknode_graph.json) is scratch state
in the container layer, not a volume — it resets when the container is
recreated (e.g. on an image update). Durable artifacts are the saved
Workflows and Runs above, so save work to a workflow before redeploying.
Back one up:
docker run --rm -v blacknode_runs:/data -v "$PWD:/backup" alpine \
tar czf /backup/runs-backup.tar.gz -C /data .The proxy adds TLS + a shared Basic Auth credential — enough for a small trusted group. It does not add per-user accounts, RBAC, or workspace isolation, and the cook surface still runs arbitrary code under that one credential. For untrusted or multi-tenant users, put Blacknode behind SSO/an identity-aware proxy and run one isolated instance per trust boundary.