Skip to content

Docker Deployment

Get an API key at console.anthropic.com, then:

The repo includes a docker-compose.yml with production-ready defaults (read-only root, tmpfs, no-new-privileges):

Terminal window
export ANTHROPIC_API_KEY=sk-ant-...
docker compose up

Enable features by uncommenting environment variables in docker-compose.yml. That’s it.

Terminal window
docker run -it --rm \
-e ANTHROPIC_API_KEY=sk-ant-... \
-v ~/.nodyn:/home/nodyn/.nodyn \
ghcr.io/nodyn-ai/nodyn:latest

Type your task, press Enter.

Terminal window
alias nodyn='docker run -it --rm -e ANTHROPIC_API_KEY -v ~/.nodyn:/home/nodyn/.nodyn ghcr.io/nodyn-ai/nodyn:latest'
nodyn "What can you do?"

All features are enabled by adding environment variables. Add any of these -e flags to your docker run command.

Use nodyn from your phone. Create a bot via @BotFather/newbot → copy token.

Terminal window
docker run -d \
-e ANTHROPIC_API_KEY \
-e TELEGRAM_BOT_TOKEN=123456789:ABCdef... \
-v ~/.nodyn:/home/nodyn/.nodyn \
ghcr.io/nodyn-ai/nodyn:latest

Restrict to your chat (recommended): message your bot, open https://api.telegram.org/bot<TOKEN>/getUpdates, find your chat.id:

Terminal window
-e TELEGRAM_ALLOWED_CHAT_IDS=123456789

Encrypt secrets, run history, and OAuth tokens at rest:

Terminal window
-e NODYN_VAULT_KEY=$(openssl rand -base64 48)

Save this key in a password manager. If lost, encrypted data becomes unrecoverable.

Terminal window
# Tavily (free: 1K/month) — https://tavily.com/
-e TAVILY_API_KEY=tvly-...
# OR Brave Search — https://brave.com/search/api/
-e BRAVE_API_KEY=BSA...

Gmail, Sheets, Drive, Calendar, Docs. Create OAuth credentials at GCP ConsoleAPIs & ServicesCredentials → OAuth 2.0 Client ID (Desktop).

Terminal window
-e GOOGLE_CLIENT_ID=123456789.apps.googleusercontent.com \
-e GOOGLE_CLIENT_SECRET=GOCSPX-...

Expose nodyn as a tool server for Claude Desktop, Cursor, or other MCP clients.

Terminal window
# stdio (for Claude Desktop, Cursor)
docker run -i --rm -e ANTHROPIC_API_KEY ghcr.io/nodyn-ai/nodyn:latest --mcp-server
# HTTP (for web apps, Slack)
docker run -d \
-e ANTHROPIC_API_KEY \
-e NODYN_MCP_SECRET=$(openssl rand -hex 32) \
-p 127.0.0.1:3042:3042 \
-v ~/.nodyn:/home/nodyn/.nodyn \
ghcr.io/nodyn-ai/nodyn:latest --mcp-server --transport sse

The ghcr.io/nodyn-ai/nodyn image contains everything — CLI, Telegram bot, MCP server, voice transcription, embeddings. There are no separate images to choose from. The mode is determined by environment variables and flags:

What you setWhat runs
(nothing)Interactive CLI REPL
TELEGRAM_BOT_TOKENTelegram bot (auto-starts alongside CLI)
--mcp-serverMCP server (stdio or HTTP)
All of the aboveAll modes run in the same container

This keeps things simple: one image to pull, one image to update. The docker-compose.yml in the repo root is all you need — enable features by setting environment variables.


When nodyn runs on a server, there are several ways to interact with it:

ChannelBest forSetup
TelegramDaily use from phone/desktopAdd TELEGRAM_BOT_TOKEN env var
MCP HTTPIntegrations (Slack, web apps)Add NODYN_MCP_SECRET, expose port 3042
SSH + CLIAdmin/debuggingdocker exec -it nodyn node /app/dist/index.js
Web UIEveryone (planned)@nodyn/web-ui — not yet available

Telegram is currently the easiest remote interface — works from any device, no setup beyond the bot token. See Telegram Bot for features and differences from CLI.

SSH + CLI gives full access to all 30+ commands, changeset review, and continuous conversation. Useful for administration or advanced tasks that need the full CLI.


For always-on deployments (server, VPS, homelab):

Terminal window
docker run -d \
--name nodyn \
--restart unless-stopped \
--read-only \
--tmpfs /tmp:size=512M \
--security-opt no-new-privileges \
--memory 2g \
--cpus 2.0 \
-e ANTHROPIC_API_KEY \
-e NODYN_VAULT_KEY \
-e NODYN_MCP_SECRET \
-e TELEGRAM_BOT_TOKEN \
-e TELEGRAM_ALLOWED_CHAT_IDS \
-v ~/.nodyn:/home/nodyn/.nodyn \
ghcr.io/nodyn-ai/nodyn:latest
FlagWhy
--read-onlyContainer filesystem can’t be modified
--tmpfs /tmp:size=512MTemp files in memory only, capped
--no-new-privilegesPrevents privilege escalation
--memory 2g / --cpus 2.0Resource limits
--restart unless-stoppedAuto-restart on crash
VariableWithout it
ANTHROPIC_API_KEYnodyn won’t start
NODYN_VAULT_KEYData stored in plaintext
NODYN_MCP_SECRETAnyone on the network can run agent tasks
TELEGRAM_ALLOWED_CHAT_IDSAnyone on Telegram can use your bot

All users on the same instance share the same knowledge, memory, and history. This is by design — a team should share business context.

For separate businesses, deploy separate instances. Each gets its own Telegram bot, its own data volume, and its own knowledge — completely isolated.

For multi-service and multi-instance deployment, see nodyn-pro/packages/deploy/.

See Security — Production Deployment for vault key rotation and full hardening details.


MountPurpose
~/.nodyn/home/nodyn/.nodynConfig, history, vault, memory, profiles
/workspaceAgent workspace sandbox
~/.cache/huggingfaceEmbedding model cache (~118MB, downloaded once)
VariableRequiredPurpose
ANTHROPIC_API_KEYYesAnthropic API key
ANTHROPIC_BASE_URLNoCustom API endpoint (proxies)
NODYN_VAULT_KEYRecommendedEncrypt data at rest
NODYN_MCP_SECRETProductionMCP HTTP bearer token
NODYN_MCP_PORTNoMCP port (default: 3042)
NODYN_WORKSPACENoWorkspace root (default: /workspace)
NODYN_EMBEDDING_PROVIDERNoonnx / voyage / local
TELEGRAM_BOT_TOKENNoEnable Telegram bot
TELEGRAM_ALLOWED_CHAT_IDSRecommendedRestrict bot access
GOOGLE_CLIENT_IDNoGoogle OAuth
GOOGLE_CLIENT_SECRETNoGoogle OAuth
GOOGLE_SERVICE_ACCOUNT_KEYNoGoogle service account (headless)
TAVILY_API_KEYNoWeb search (Tavily)
BRAVE_API_KEYNoWeb search (Brave)

Pin to a specific version instead of latest for reproducible deployments:

Terminal window
ghcr.io/nodyn-ai/nodyn:1.0.0 # semver — recommended for production
ghcr.io/nodyn-ai/nodyn:latest # always the newest release
Terminal window
git clone https://github.com/nodyn-ai/nodyn.git && cd nodyn
docker build -t nodyn .
docker run -it --rm -e ANTHROPIC_API_KEY nodyn

The image is hardened for production by default:

  • Non-root — runs as nodyn:1001, not root
  • No package managernpm/npx removed from production image
  • Multi-stage build — no source code, no build tools, production deps only
  • Pinned dependencies — base image digest, whisper.cpp release tag, model checksum verified
  • Read-only root — filesystem can’t be modified (use --read-only)
  • SIGTERM handling — graceful shutdown for agent runs and database writes
  • Trivy scanned — CI blocks push on CRITICAL/HIGH vulnerabilities
  • OCI labels — image metadata (version, source, license) for auditability
  • Minimal attack surface — no bash, no perl, no SUID binaries, only sh (dash)
  • Network egress — see below for required domains and firewall rules

See Security for the full model.

nodyn blocks private IP ranges and non-HTTP(S) protocols at the application level. For defense-in-depth, restrict outbound traffic at your firewall to only the domains nodyn needs.

Always required:

DomainPortPurpose
api.anthropic.com443Claude API (or custom ANTHROPIC_BASE_URL)

Per feature (only needed if the feature is enabled):

DomainPortFeatureEnv var that enables it
api.telegram.org443Telegram botTELEGRAM_BOT_TOKEN
api.tavily.com443Web search (Tavily)TAVILY_API_KEY
api.search.brave.com443Web search (Brave)BRAVE_API_KEY
huggingface.co443Embedding model download (first run, cached after)always
accounts.google.com443Google OAuthGOOGLE_CLIENT_ID
oauth2.googleapis.com443Google token exchangeGOOGLE_CLIENT_ID
*.googleapis.com443Gmail, Sheets, Drive, Calendar, DocsGOOGLE_CLIENT_ID

User-initiated: The web_research and http_request tools can reach any public HTTPS URL. These are SSRF-protected (private IPs blocked, DNS validated, exfiltration detection). To restrict further, set enforce_https: true in config and use the built-in network policy (deny-all or allow-list mode).

Always blocked (app-level): 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, non-HTTP(S) protocols, redirects to private IPs.

Terminal window
# Allow established connections + DNS
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
# Allow required domains (resolve IPs first, or use ipset)
iptables -A OUTPUT -p tcp --dport 443 -d api.anthropic.com -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -d api.telegram.org -j ACCEPT
# ... add per feature as needed
# Block everything else
iptables -A OUTPUT -j DROP

For Kubernetes, use a NetworkPolicy with egress rules matching the domains above.