NodeTool has two orthogonal deployment concerns. Don’t conflate them:
- Server (publish) — long-lived NodeTool infrastructure that humans and the UI connect into. NodeTool self-hosts as a single Docker server. See Self-Hosted Deployment.
- Worker (attach) — an ephemeral, billing-sensitive GPU box that one NodeTool instance connects out to, runs Python nodes on, and tears down. See Worker Deployment.
| Server | Worker | |
|---|---|---|
| Direction | humans/UI connect in | a NodeTool instance connects out |
| Lifetime | long-lived, always-on | ephemeral — spin up, attach, tear down |
| Identity | a URL handed to people | a {wsUrl, token} an instance adopts |
| Cost | flat | bills while it exists, so teardown matters |
| How | nodetool deploy … (Docker) |
nodetool worker … (RunPod, Vast, Verda) |
For a walkthrough across desktop, public, private, and Docker/Podman self-hosting, see the End-to-End Deployment Guide.
Quick Reference: What do you want to do?
| I want to… | Go to |
|---|---|
| Run the NodeTool server on my own machine/host | Self-Hosted Deployment |
| Understand how api.nodetool.ai itself is deployed | Production Deploy (Docker) |
| Rent a GPU to run Python nodes (RunPod / Vast / Verda) | Worker Deployment |
| Tune GPU/memory/volumes for the server container | Docker Resource Management |
| Use Supabase for auth/storage | Supabase Deployment Integration |
| Set up TLS/HTTPS | Serving the Web UI and TLS |
| Connect Claude Code or another MCP client to a deployed server | MCP on a Production Server |
Server: self-host with Docker
The quickest path is the reference
docker-compose.yml
at the repo root — cp .env.example .env && docker compose up -d. See
Self-Hosted Deployment › Docker Compose.
For a managed flow (remote hosts over SSH, image transfer, workflow sync), the
nodetool deploy commands manage a single Docker server target driven by a
deployment.yaml file.
- Pull the base image:
docker pull ghcr.io/nodetool-ai/nodetool:latest - Initialize and add a target:
nodetool deploy init nodetool deploy add my-server --type dockerdeploy initcreatesdeployment.yamlin the NodeTool config directory (~/.config/nodetool/on Linux,~/Library/Application Support/nodetool/on macOS,%APPDATA%\nodetoolon Windows).deploy addprompts for the host, SSH user and key (remote hosts only), image name and tag, container name, and port. The single targettypeisdocker. Addcontainer.environment,container.gpu, orpathsafterwards withnodetool deploy edit, which opens the file in$EDITOR. - Review & plan (no remote mutation):
nodetool deploy list nodetool deploy show my-server nodetool deploy plan my-server # prints the plan as JSON nodetool deploy apply my-server --dry-run - Apply & monitor:
nodetool deploy apply my-server nodetool deploy status my-server nodetool deploy logs my-server --follow nodetool deploy destroy my-server
deploy apply does not pull images. A local host needs the image already
present. For a remote host, apply pushes the image from your local Docker daemon
only when the remote has no copy. See
Self-Hosted Deployment for the full server
walkthrough, deploy workflows and deploy users-* commands, and Supabase Deployment Integration to add
hosted auth and storage.
Heads up: NodeTool used to ship server-deploy targets for RunPod serverless, Google Cloud Run, Fly, Railway, and HuggingFace Spaces. Those are gone. Server self-hosting is Docker only; GPU access is now a worker concern, not a server target. To run on a GPU, rent a worker (below).
Worker: rent a GPU (RunPod, Vast, Verda)
When a graph needs a GPU you don’t have, provision a remote worker, attach to it, run your Python nodes there, and tear it down:
nodetool secrets store RUNPOD_API_KEY
nodetool worker profile add hf-a40 --target runpod \
--image ghcr.io/nodetool-ai/nodetool-worker:latest \
--gpu "NVIDIA A40" --idle-timeout 15
nodetool worker create --profile hf-a40 --attach
nodetool worker list # what's live, and what it's costing
nodetool worker stop --all # pause every live worker
GPU workers bill while they exist, so the worker subsystem ships a cost
guard: idle auto-pause, a hard TTL that destroys the worker, and orphan
reconcile. worker stop pauses a worker and keeps its volume, which still bills
a little. To destroy a worker and its volume, use Terminate in the Workers
panel. See Worker Deployment for profiles, attaching
from the UI or CLI, supported targets, and the cost guard in full.
Server configuration
deployment.yaml accepts these top-level keys per deployment (see
DockerDeployment in @nodetool-ai/deploy deployment-config.ts):
type– alwaysdockerenabled– whether the deployment is activehost– Docker host (IP/hostname, orlocalhost)ssh– SSH connection details for remote hosts (omit for local). The host key must already be in~/.ssh/known_hosts: runssh <user>@<host>once and check the fingerprint before the first deploypaths– host directories for the workspace (default~/nodetool_data/workspace, mounted at/workspace) and the HF cache (default~/nodetool_data/hf-cache, mounted at/hf-cache, read-only unlesspersistent_pathsis set)persistent_paths– optional container paths for the users file, database, vector store (vectorstore_db_path), HF home, assets, and logs. Setting it changes the injected environment, see belowimage– container image name/tag/registrycontainer–name,port,gpu(device ids such as0or0,1, passed as--gpus "device=…"), andenvironment(env vars injected into the container)server_auth_token– auto-generated bearer token for admin/sync calls. The server accepts it in Local mode (see Authentication)server_url– optional URL the CLI uses instead ofhttp://<host>:<port>. The CLI sendsserver_auth_tokenonly overhttps://or tolocalhost, so set it to your TLS proxy (https://nodetool.example.com) or to an SSH tunnel (http://localhost:8000afterssh -L 8000:localhost:8000 <host>).NODETOOL_ALLOW_INSECURE_HTTP=1lifts the checkstate– deployment state tracked by the deployer
The container is named nodetool-<container.name> and runs with
--restart unless-stopped. The server listens on 7777 inside the container
and the deployer publishes it on host port container.port. A container.port
of 7777 is published as 8000. The deployer also sets PORT,
NODETOOL_API_URL, DB_PATH (/workspace/nodetool.db), VECTORSTORE_DB_PATH
(/workspace/vectorstore.db), ASSET_FOLDER (/workspace/assets),
HF_HUB_CACHE (/hf-cache), and SERVER_AUTH_TOKEN in the container, on top
of container.environment. With persistent_paths set, the first three and
HF_HOME come from it instead.
Environment variables live under container.environment. When a deployment is
loaded, deploy generates SECRETS_MASTER_KEY in container.environment and a
server_auth_token if they are missing, and saves them to deployment.yaml.
Keep that file private.
Monitoring & health checks
# Health endpoint (no auth required). Use the published host port: 17777 for
# the bundled Compose file, container.port (default 8000) for `deploy apply`.
curl http://your-server:17777/health
# 200: {"status": "ok", "services": {...}, ...}
# 503: status "degraded" (database check failed) or "draining" (shutting down)
curl http://your-server:17777/ready # liveness only, always 200
nodetool deploy status <name>
nodetool deploy logs <name> --follow
| Indicator | What to watch | Action |
|---|---|---|
| Health endpoint | Should return 200. /api/health returns the version and uptime |
Restart service if unhealthy |
| Memory usage | Models consume significant RAM/VRAM | Scale up or use smaller models |
| Disk space | Model cache and assets grow over time | Periodic cleanup or larger volumes |
| Response time | First request after cold start is slow (model loading) | Warm up via health check |
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| Container exits immediately | Missing env vars or invalid config | Check nodetool deploy logs <name> |
deploy apply fails its health check |
The server did not answer /health after 10 probes, 2 seconds apart |
Read nodetool deploy logs <name>, fix the cause, and run apply again |
| 503 Service Unavailable | Overloaded or out of memory | Scale up resources or reduce concurrency |
| Port already in use | Another service on the same port | Change container.port in deployment.yaml |
| “Image not found” | Docker image not present | docker pull ghcr.io/nodetool-ai/nodetool:latest |
| Permission denied on volumes | Container user lacks access | Fix host directory permissions |
For more, see the Troubleshooting Guide.
Upgrading
docker pull ghcr.io/nodetool-ai/nodetool:latest # on the Docker host
nodetool deploy apply <name>
nodetool deploy status <name>
apply stops and removes the existing container, then starts a new one from the
image the host holds. It never pulls. For a remote host, pull on that host or
change image.tag, because an image already on the remote is not replaced.
Workflows, assets, and settings are preserved because /workspace is a host
directory.
Related
- Self-Hosted Deployment
- Worker Deployment
- Docker Resource Management
- Supabase Deployment Integration
- End-to-End Deployment Guide
</content>