This guide covers deploying NodeTool on your own infrastructure.

Overview

Self-hosted deployment runs NodeTool in a Docker container — on localhost or on a remote host reached over SSH. Docker is the only supported deployment type (SUPPORTED_TYPES = ["docker"]).

Two paths:

  • Docker Compose — a single docker-compose.yml you run yourself. The fastest way to stand up one server. See below.
  • nodetool deploy CLI — a managed flow driven by deployment.yaml that also handles remote hosts over SSH, image transfer, and workflow sync. See Deployment Configuration.

Docker Compose (reference)

The repository ships a reference docker-compose.yml for running one server on a host you control.

cp .env.example .env      # fill in the provider keys you use
docker compose up -d
# open http://localhost:17777

The server binds to 0.0.0.0:7777 inside the container and is published on the host as ${NODETOOL_PORT:-17777}. All persistent state — SQLite database, assets, vector store, model cache, and the generated secret key — lives under /workspace, backed by the named nodetool-data volume, so it survives restarts and image upgrades.

Common overrides (set in .env or the shell):

Variable Default Purpose
NODETOOL_VERSION latest Image tag to pull (pin a release in production)
NODETOOL_PORT 17777 Host port mapped to the container’s 7777
NODETOOL_TRUST_LOCAL_NETWORKS 172.16.0.0/12 ⚠️ Source CIDRs trusted as user 1 without a login (Local mode). Docker bridge by default — never 0.0.0.0/0 on a public IP. Ignored in Supabase mode
SECRETS_MASTER_KEY auto-generated 32-byte base64 key encrypting stored secrets — set explicitly in production (openssl rand -base64 32)
OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, FAL_API_KEY, HF_TOKEN unset Model provider keys

Upgrade in place:

docker compose pull
docker compose up -d

To store data in a host directory instead of the named volume, replace the nodetool-data:/workspace mount with a bind mount (e.g. ./nodetool-data:/workspace) and make sure the host directory is writable by the container’s node user.

Authentication / login screen

Auth is configured entirely on the backend. The web UI fetches its auth mode and public Supabase credentials at runtime from GET /api/config (a public, non-secret endpoint), so the same frontend build works with or without login — no rebuild, and it works even when the frontend is served from a different origin.

  • Login off (default). With SUPABASE_URL/SUPABASE_KEY unset the server runs in Local mode: it trusts requests by source IP (loopback, plus any NODETOOL_TRUST_LOCAL_NETWORKS you set) and runs them as a single user; other requests are rejected, and the UI shows no login screen. In Docker the bundled Compose file trusts the Docker bridge (172.16.0.0/12) so a local install works out of the box — see the warning below.
  • Login on. Set these three on the server to switch to Supabase mode — the server requires a valid Supabase JWT on every request and the UI shows the login screen:

    SUPABASE_URL=https://your-project.supabase.co
    SUPABASE_KEY=your-service-role-key   # server-only, never sent to the browser
    SUPABASE_ANON_KEY=your-anon-key      # public key the login screen uses
    

    Optionally set AUTH_REDIRECT_URL when serving behind a domain/proxy (it must be in the Supabase project’s redirect allow list).

GET /api/config returns authMode, supabaseUrl, supabaseAnonKey, authRedirectUrl, and version — never the service-role key. See Authentication and Supabase Deployment.

🔒 Do not expose Local mode to the internet

Local mode has no login. NODETOOL_TRUST_LOCAL_NETWORKS trusts a set of source IPs as admin user "1" with no password — full access to your data, secrets, and API keys. Anyone who can reach the published port from a trusted range gets in.

  • Safe on a laptop or a private LAN/VPN behind a firewall.
  • The default 172.16.0.0/12 trusts only Docker’s bridge, not the wider internet. Never change it to 0.0.0.0/0 on a public IP.
  • Putting NodeTool on a public address or sharing it with untrusted users? Enable Supabase mode (above) so every request needs a real login, and terminate TLS in front of the server.

Serving the web UI from a different origin (e.g. a CDN)? Point the frontend at the backend with the build-time VITE_API_URL, and add that origin to the server’s NODETOOL_ALLOWED_ORIGINS so the cross-origin GET /api/config and API calls are permitted. Everything else still comes from /api/config.

Deployment Configuration

Deployments are configured via deployment.yaml.

Docker Deployment

deployments:
  my-server:
    type: docker
    enabled: true
    host: 192.168.1.10
    ssh:
      user: ubuntu
      key_path: ~/.ssh/id_rsa
    container:
      name: nodetool-server
      port: 8000
      gpu: "0"
    paths:
      workspace: /data/nodetool
      hf_cache: /data/hf-cache
    image:
      name: ghcr.io/nodetool-ai/nodetool
      tag: latest

For a local host, set host: localhost and omit the ssh block.

Apply Flow

  1. Directory Creation: Ensures workspace and hf_cache directories exist.
  2. Image Check: Verifies the configured image exists locally/remote. deploy apply does not auto-pull.
  3. Image Transfer: For remote hosts, copies image to remote runtime if needed.
  4. Container Management: Restarts container with new configuration.
  5. Health Check: Verifies HTTP endpoint.

End-to-End: Local Docker Deployment

This walkthrough matches a common local setup flow:

  1. Pull the Docker image.
  2. Add a docker deployment interactively.
  3. Review the generated deployment.
  4. Apply deployment and validate health.
  5. Sync workflows.
  6. Run a synced workflow on the deployed instance.

0. Pull the Image First

docker pull ghcr.io/nodetool-ai/nodetool:latest

1. Add Local Docker Deployment

nodetool deploy add local --type docker

--type docker is required. The command then prompts for the rest:

  • Host address: localhost
  • Docker image name: ghcr.io/nodetool-ai/nodetool
  • Docker image tag: latest
  • Container name: nodetool
  • Port: 8000
  • GPU/workflows assignment: optional
  • Workspace folder: $HOME/.nodetool-workspace
  • HF cache folder: canonical local HF cache (auto-detected, usually $HOME/.cache/huggingface/hub)

Path meanings:

  • Workspace: stores assets and temporary runtime data.
  • HF cache: stores downloaded Hugging Face artifacts/models.

2. Review Deployment Config

nodetool deploy show local

This dumps the deployment entry as YAML. You should see something like:

local:
  type: docker
  host: localhost
  image:
    name: ghcr.io/nodetool-ai/nodetool
    tag: latest
  container:
    name: nodetool
    port: 8000
  paths:
    workspace: <your workspace path>
    hf_cache: <your HF cache path>

The server endpoint is at http://localhost:8000. The container EXPOSEs 7777; when container.port is 7777 the deployer maps it to host port 8000 (any other container.port is used as-is).

You can also inspect the raw config:

cat ~/.config/nodetool/deployment.yaml

To change a field deploy add did not prompt for, open the file in $EDITOR:

nodetool deploy edit

deploy edit takes no deployment name — it opens the whole deployment.yaml. Without one on disk it stops and tells you to run nodetool deploy init first.

3. Apply Deployment

nodetool deploy apply local

Expected successful output includes:

  • directories created
  • image check passed
  • app container started
  • health checks passing for http://127.0.0.1:8000/health
  • Deployment successful
  • secrets synced

If the first apply fails (for example due to container/port conflicts), run apply again:

nodetool deploy apply local

Then confirm runtime state:

nodetool deploy status local
docker ps --filter name=nodetool --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
curl http://127.0.0.1:8000/health

4. Sync Workflows to the Deployment

List local workflows first:

nodetool workflows list

Sync one workflow by ID to the deployed instance:

nodetool deploy workflows sync local <workflow_id>

This sync command also:

  • uploads referenced assets
  • downloads referenced models (HuggingFace/Ollama) on the target

Verify remote workflows:

nodetool deploy workflows list local

5. Test Workflow Execution on Deployed Instance

Run a synced workflow remotely:

nodetool deploy workflows run local <workflow_id>

Optional params example:

nodetool deploy workflows run local <workflow_id> -p prompt="hello"

If a run fails, inspect logs:

nodetool deploy logs local --tail 200

API Users

A deployment that serves more than one person needs API users. Four nodetool deploy subcommands manage them over the running server’s admin API, so the deployment must already be applied and reachable.

Every one of them needs an admin bearer token. It comes from --token, else from NODETOOL_ADMIN_TOKEN, else from an interactive prompt — with no TTY and neither set, the command exits with Admin token required. Provide --token or set NODETOOL_ADMIN_TOKEN.

  • users-add <deployment> <username> — create a user and print its token. --role <admin|user> defaults to user; any other value is refused. The token is printed once and never again.
  • users-list <deployment> — username, user id, role, a 16-character preview of the stored token hash, and creation time. --json prints the raw records.
  • users-remove <deployment> <username> — delete a user. Prompts first; --force skips the prompt.
  • users-reset-token <deployment> <username> — issue a new token and invalidate the old one. Like users-add, the new token is shown once.

Examples:

export NODETOOL_ADMIN_TOKEN=<admin token>

nodetool deploy users-add local alice --role admin
nodetool deploy users-list local
nodetool deploy users-list local --json

nodetool deploy users-reset-token local alice
nodetool deploy users-remove local alice --force

Manual Troubleshooting

Container Logs

nodetool deploy logs local --tail 200

For a remote host you can also read the container logs directly:

ssh user@host "docker logs nodetool-server"