This guide explains how to deploy NodeTool in self-hosted environments using the
built-in FastAPI proxy. It covers the proxy architecture, configuration,
container images, deployment model (deployment.yaml), persistent storage, TLS,
and common operational tasks.
Overview
Self-hosted deployments now manage two key containers:
- Proxy container (
nodetool-proxy) – the only public-facing component.- Terminates HTTP/HTTPS traffic.
- Authenticates every request (Bearer token).
- Talks to the Docker socket to start/stop service containers on demand.
- Service container(s) – e.g. the NodeTool worker image (
nodetool), which run only when traffic arrives.
nodetool deploy apply <deployment> orchestrates everything: it renders the
proxy config, ensures the Docker network, starts/restarts the proxy container,
and performs health checks before reporting success.
Repository Layout
src/nodetool/proxy/
├── __init__.py # exported helpers for python users
├── __main__.py # `python -m nodetool.proxy` entrypoint
├── config.py # Pydantic schema for proxy.yaml
├── docker_manager.py # async Docker lifecycle helpers
├── filters.py # hop-by-hop header filtering
└── server.py # FastAPI app + uvicorn runners
src/nodetool/proxy/requirements.txt # proxy-only runtime dependencies
docker/proxy/Dockerfile # slim runtime image
examples/proxy-config.yaml # reference configuration
Proxy Runtime & Dependencies
The proxy installs only a small dependency set (see
src/nodetool/proxy/requirements.txt):
fastapi
uvicorn
httpx
PyYAML
pydantic
docker
docker/proxy/Dockerfile builds a small image from python:3.11-slim, installs
those requirements, copies the proxy package into /app/nodetool/proxy, creates
an unprivileged proxy user, and sets the entrypoint to python -m
nodetool.proxy.
Building the Image
docker build -f docker/proxy/Dockerfile -t nodetool-proxy:latest .
The built image exposes ports 80 and 443; however, the deployer publishes the
actual host ports defined in proxy.listen_http and proxy.listen_https.
Proxy Configuration (proxy.yaml)
The config rendered by the deployer lives at <workspace>/proxy/proxy.yaml. It
follows the schema defined in src/nodetool/proxy/config.py.
Global Settings
| Field | Type | Default | Description |
|---|---|---|---|
domain |
str | required | Public hostname used for redirects/status URLs |
email |
str | required | Let’s Encrypt contact email |
bearer_token |
str | required | Token required for all non-ACME endpoints |
idle_timeout |
int | 300 | Seconds of inactivity before stopping a service |
listen_http |
int | 80 | Proxy HTTP listener (ACME + optional HTTP mode) |
listen_https |
int | 443 | Proxy HTTPS listener (enabled when TLS files present) |
acme_webroot |
str | /var/www/acme |
Directory shared with certbot for HTTP-01 |
tls_certfile |
str|None | — | Fullchain cert path on the host (mounted into the container) |
tls_keyfile |
str|None | — | Private key path on the host |
local_tls_certfile |
str|None | — | Optional local file copied to the host before deployment |
local_tls_keyfile |
str|None | — | Optional local file copied to the host before deployment |
auto_certbot |
bool | False |
When true, run certbot on the host to obtain/renew certificates |
log_level |
str | INFO |
Proxy logging level |
docker_network |
str | nodetool-net |
Docker network shared by proxy + services |
connect_mode |
docker_dns|host_port |
docker_dns |
How the proxy reaches services |
http_redirect_to_https |
bool | True |
Redirect HTTP (non-ACME) to HTTPS |
Services
All service containers must listen on port 8000 internally. The proxy starts them on demand.
| Field | Type | Description |
|---|---|---|
name |
str | Docker container name (unique per deployment) |
path |
str | URL prefix used for longest-prefix routing |
image |
str | Docker image (with tag) |
environment |
dict[str,str] | Environment variables for the container |
volumes |
dict[str, str | dict] | Volume mounts. Values can be "/host:/container[:mode]" or {bind: "/container", mode: "rw"} |
mem_limit |
str | Optional memory limit (e.g. "1g") |
cpus |
float | Optional CPU limit (converted to nano_cpus) |
host_port |
int | Only used if connect_mode is host_port |
Environment Overrides
Override globals with env vars at runtime:
export PROXY_GLOBAL_DOMAIN=example.internal
export PROXY_GLOBAL_BEARER_TOKEN=$(openssl rand -hex 32)
python -m nodetool.proxy --config /etc/nodetool/proxy.yaml
Service-specific overrides use PROXY_SERVICE_<SERVICE_NAME>_<KEY>.
Deployment Model (deployment.yaml)
Self-hosted deployments declare both the worker container and the proxy. Example:
deployments:
localhost:
type: self-hosted
host: localhost
paths:
workspace: /tmp/nodetool-workspace
hf_cache: /Users/you/.cache/huggingface
image:
name: nodetool
tag: latest
container:
name: nodetool-localhost
port: 9001
worker_auth_token: <optional>
proxy:
image: nodetool-proxy:latest
listen_http: 80
listen_https: 443
domain: localhost
email: admin@localhost
tls_certfile: /etc/letsencrypt/live/localhost/fullchain.pem
tls_keyfile: /etc/letsencrypt/live/localhost/privkey.pem
local_tls_certfile: ./certs/localhost/fullchain.pem
local_tls_keyfile: ./certs/localhost/privkey.pem
auto_certbot: true
docker_network: nodetool-net-localhost
connect_mode: docker_dns
http_redirect_to_https: true
services:
- name: nodetool-localhost
path: /
image: nodetool:latest
environment:
PORT: "8000"
NODETOOL_API_URL: "http://localhost:9001"
DB_PATH: "/workspace/nodetool.db"
HF_HOME: "/hf-cache"
NODETOOL_WORKFLOWS: "586143fea92c11f0964b000065b1ad28"
volumes:
/tmp/nodetool-workspace:
bind: /workspace
mode: rw
/Users/you/.cache/huggingface:
bind: /hf-cache
mode: ro
Key points:
worker_auth_tokenis reused as the proxy bearer token ifproxy.bearer_tokenis omitted. Otherwise the deployer generates aproxy_bearer_tokenand stores it in the deployment state.- Volume mappings support either string or dict syntax. Dicts allow explicit
access modes (
rw/ro). - The proxy network is created if missing (
docker network inspect/create). - The deployer writes
<workspace>/proxy/proxy.yamlfor visibility/debugging.
Persistent Storage
- Workspace (
paths.workspace) – mounted read/write into the worker as/workspace. Place your SQLite database here (e.g.DB_PATH=/workspace/nodetool.db). - Hugging Face cache (
paths.hf_cache) – mounted read-only into the worker as/hf-cache. UpdateHF_HOMEaccordingly. - Add additional storage by extending
volumesper service.
Deployment Steps
-
Build images (proxy + worker):
docker build -f docker/proxy/Dockerfile -t nodetool-proxy:latest . docker build -t nodetool:latest . -
Apply the deployment:
nodetool deploy apply localhost
This will:
- Create workspace, cache, proxy, and acme directories.
- Ensure the Docker network exists.
- Stop/remove any existing proxy container.
- Start the new proxy container with the rendered config.
- Health-check
/healthzand/status.
-
Verify status (using the rendered token):
TOKEN=$(yq '.deployments.localhost.proxy.bearer_token' ~/.config/nodetool/deployment.yaml) curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/status -
Run workflows through the proxy:
```bash
nodetool deploy workflows run localhost
The proxy logs show cold-start/start/stop messages; `/status` reports service
state (`running`, `exited`, etc.).
5. (Optional) Verify HTTPS if enabled:
```bash
curl -vk -H "Authorization: Bearer $TOKEN" https://localhost:443/status
You should see a 200 OK response and the proxy will list the managed
services.
TLS & ACME
- If you populate
local_tls_certfile/local_tls_keyfile, the deployer copies those files totls_certfile/tls_keyfileon the host before starting the proxy (permissions are set to600). - When
auto_certbotis enabled, the deployer runscertbot certonly --webrooton the host to obtain/renew certificates automatically (Certbot must already be installed on the host). Certificates are written to the paths specified bytls_certfileandtls_keyfile. - Ensure port 80 (or the value of
listen_httpif different) is reachable from the public internet so Let’s Encrypt can complete the HTTP-01 validation. - Install certbot on the target host ahead of time (for example,
sudo apt install certbot). The deployer reuses the existing certbot timers for renewal; you can check status withsystemctl status certbot.timer. - Without TLS the proxy serves plain HTTP on
listen_http. - ACME HTTP-01 uses
acme_webroot; ensure the directory is writable on both host and proxy container (for certbot + proxy to share challenges). - Renewed certificates require restarting the proxy (
nodetool deploy apply ...).
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Proxy container exits immediately | Config parse failure or missing /var/run/docker.sock |
Inspect container logs and <workspace>/proxy/proxy.yaml; ensure the socket is mounted |
/status always not_created |
Service container failed to start (bad image/env) | Check proxy logs for Docker errors, and docker logs <service> |
401 Unauthorized when running workflows |
Bearer token mismatch | Ensure proxy.bearer_token matches worker_auth_token; re-apply deployment to sync |
| Curl reset or 500 during long responses | Upstream closed stream mid-response | Inspect worker logs; proxy now buffers entire response but upstream must complete the body |
| ACME challenge fails | ACME webroot not shared/mounted | Mount the same host directory into the proxy container and certbot |
| Need different cache/DB location | Adjust paths.workspace / paths.hf_cache and update volume bindings + env vars (e.g. DB_PATH, HF_HOME) |
Useful Commands
python -m nodetool.proxy --config <proxy.yaml>– run the proxy manually (HTTP-only or HTTPS + ACME).nodetool deploy plan <name>– preview directories, networks, and containers the deployer will touch.nodetool deploy logs <name>– tail proxy container logs.nodetool deploy workflows list <name>– enumerate available workflows before running them.
With these steps your self-hosted environment deploys a slim proxy container as the public entrypoint, keeps the worker private, and persists all important data on host volumes.
Related Documentation
- Deployment Guide – end-to-end deploy CLI workflow.
- Proxy Reference – proxy configuration schema, TLS, and status endpoints.
- Storage Guide – configuring asset and cache volumes.
- Configuration Guide – environment variables and secrets.