# Docker Multi-Bot Setup Guide Complete guide for running multiple nanobot instances with Docker, each with its own Telegram bot. ## 📋 Table of Contents 1. [Quick Start](#quick-start) 2. [Architecture Overview](#architecture-overview) 3. [Setup Instructions](#setup-instructions) 4. [Running Bots](#running-bots) 5. [Configuration Management](#configuration-management) 6. [Development Workflow](#development-workflow) 7. [Troubleshooting](#troubleshooting) 8. [Reference](#reference) --- ## Quick Start ### Start Only User1 Bot ```bash docker compose -f docker-compose.multi.env.yml up -d nanobot-user1 ``` ### Start All Bots ```bash docker compose -f docker-compose.multi.env.yml up -d ``` ### Stop All Bots ```bash docker compose -f docker-compose.multi.env.yml down ``` ### Stop Specific Bot ```bash docker compose -f docker-compose.multi.env.yml stop nanobot-user1 ``` ### View Logs ```bash docker logs -f nanobot-user1 ``` --- ## Architecture Overview ### How It Works Each bot runs in its own Docker container with: - **Separate config directory**: `~/.nanobot-user1`, `~/.nanobot-user2`, etc. - **Shared env file**: `.env.shared` (common settings) - **Bot-specific env file**: `.env.user1`, `.env.user2`, etc. (overrides) - **Isolated environment**: No conflicts between bots ### File Structure ``` nanobot/ ├── .env.shared # Shared settings (API keys, model, etc.) ├── .env.user1 # Bot 1 (@ilia) overrides ├── .env.user2 # Bot 2 (@family) overrides ├── .env.user3 # Bot 3 (@wife) overrides ├── agent_workspaces/ # Templates copied by scripts/init-agent-workspaces.sh ├── scripts/init-agent-workspaces.sh ├── docker-compose.multi.env.yml # Production compose file ├── docker-compose.multi.dev.yml # Development compose file │ ├── ~/.nanobot/workspaces/ │ ├── ilia/ # Mounted as /workspace for user1 — AGENTS.md, memory/, … │ ├── family/ # user2 │ └── wife/ # user3 ├── ~/.nanobot-user1/ │ └── config.json # Bot 1 channel config (Telegram token, allowFrom) ├── ~/.nanobot-user2/ │ └── config.json # Bot 2 channel config └── ~/.nanobot-user3/ └── config.json # Bot 3 channel config ``` `./workspace` in the repo remains for **single-bot** `docker-compose.yml` only; multi-bot uses `~/.nanobot/workspaces/*` per container. ### Configuration Loading 1. **Docker Compose** loads environment files: - First: `.env.shared` (shared settings) - Second: `.env.userX` (bot-specific overrides) - Later files override earlier ones 2. **Container** mounts config directory: - Host: `~/.nanobot-user1` → Container: `/root/.nanobot` 3. **Nanobot** loads: - Environment variables (from Docker env files) - Config file: `/root/.nanobot/config.json` (mounted from host) --- ## Setup Instructions ### Step 0: Per-agent workspaces (personalities + isolated memory) Multi-bot compose mounts **separate** workspace directories so each bot has its own `AGENTS.md`, `SOUL.md`, `USER.md`, and `memory/` (no shared `./workspace`). On the host, from the repo root: ```bash ./scripts/init-agent-workspaces.sh ``` This creates: ``` ~/.nanobot/workspaces/ ilia/ # nanobot-user1 — dev / infra persona family/ # nanobot-user2 — household persona wife/ # nanobot-user3 — personal assistant persona ``` Templates live in-repo under `agent_workspaces/`. Re-run the script anytime: it **skips** files that already exist. Adjust ownership if Docker runs as root: ```bash sudo chown -R "$(whoami):$(whoami)" ~/.nanobot/workspaces ``` ### Step 1: Create Environment Files Run the setup script: ```bash ./env-files-setup.sh ``` This creates: - `.env.shared` - Shared settings - `.env.user1`, `.env.user2`, `.env.user3` - Bot-specific overrides ### Step 2: Edit `.env.shared` Add your shared settings: ```bash nano .env.shared ``` Example: ```bash # Provider Settings NANOBOT_PROVIDERS__CUSTOM__API_KEY=no-key NANOBOT_PROVIDERS__CUSTOM__API_BASE=http://172.17.0.1:11434/v1 # Agent Settings NANOBOT_AGENTS__DEFAULTS__MODEL=llama3.1:8b NANOBOT_AGENTS__DEFAULTS__WORKSPACE=/mnt/data/nanobot NANOBOT_AGENTS__DEFAULTS__TEMPERATURE=0.7 ``` **Important**: Use `172.17.0.1` (Docker bridge gateway) instead of `localhost` so containers can reach Ollama on the host. ### Step 3: Edit Bot-Specific Env Files Edit `.env.user1`, `.env.user2`, etc. with bot-specific settings: ```bash nano .env.user1 ``` Example: ```bash # Telegram Bot Token NANOBOT_CHANNELS__TELEGRAM__ENABLED=true NANOBOT_CHANNELS__TELEGRAM__TOKEN=your_bot_token_here # Email Credentials (if different per bot) NANOBOT_CHANNELS__EMAIL__IMAP_USERNAME=bot1@example.com NANOBOT_CHANNELS__EMAIL__IMAP_PASSWORD=password ``` ### Step 4: Create Config Files Run the config creation script: ```bash ./create-bot-configs.sh ``` Or manually create `~/.nanobot-user1/config.json`: ```json { "channels": { "telegram": { "enabled": true, "allowFrom": ["YOUR_TELEGRAM_USERNAME"] }, "email": { "enabled": true, "allowFrom": ["email@example.com"] } } } ``` **Note**: `allowFrom` arrays must be in `config.json`, NOT in env files. ### Step 5: Build Docker Image ```bash docker compose -f docker-compose.multi.env.yml build ``` --- ## Running Bots ### Start Commands ```bash # Start only user1 docker compose -f docker-compose.multi.env.yml up -d nanobot-user1 # Start only user2 docker compose -f docker-compose.multi.env.yml up -d nanobot-user2 # Start only user3 docker compose -f docker-compose.multi.env.yml up -d nanobot-user3 # Start all bots docker compose -f docker-compose.multi.env.yml up -d ``` ### Stop Commands ```bash # Stop only user1 docker compose -f docker-compose.multi.env.yml stop nanobot-user1 # Stop only user2 docker compose -f docker-compose.multi.env.yml stop nanobot-user2 # Stop only user3 docker compose -f docker-compose.multi.env.yml stop nanobot-user3 # Stop all bots docker compose -f docker-compose.multi.env.yml down ``` ### Restart Commands ```bash # Restart only user1 docker compose -f docker-compose.multi.env.yml restart nanobot-user1 # Restart all bots docker compose -f docker-compose.multi.env.yml restart ``` ### Status Commands ```bash # Check what's running docker compose -f docker-compose.multi.env.yml ps # View logs for user1 docker logs -f nanobot-user1 # View logs for all bots docker compose -f docker-compose.multi.env.yml logs -f # View logs for specific bot docker compose -f docker-compose.multi.env.yml logs -f nanobot-user1 ``` ### Remove Commands ```bash # Stop and remove user1 container docker compose -f docker-compose.multi.env.yml stop nanobot-user1 docker rm nanobot-user1 # Stop and remove all containers docker compose -f docker-compose.multi.env.yml down # Remove containers and volumes (keeps config files) docker compose -f docker-compose.multi.env.yml down -v ``` --- ## Configuration Management ### Updating Shared Settings Edit `.env.shared`: ```bash nano .env.shared ``` Restart affected containers: ```bash docker compose -f docker-compose.multi.env.yml restart ``` ### Updating Bot-Specific Settings Edit `.env.userX`: ```bash nano .env.user1 ``` Restart that specific bot: ```bash docker compose -f docker-compose.multi.env.yml restart nanobot-user1 ``` ### Updating Config Files Edit config file: ```bash nano ~/.nanobot-user1/config.json ``` Restart container: ```bash docker restart nanobot-user1 ``` ### Adding Email to allowFrom ```bash # Add email to user1's allowFrom jq '.channels.email.allowFrom += ["newemail@example.com"]' \ ~/.nanobot-user1/config.json > ~/.nanobot-user1/config.json.tmp && \ mv ~/.nanobot-user1/config.json.tmp ~/.nanobot-user1/config.json # Restart container docker restart nanobot-user1 ``` ### Environment Variable Format Nanobot uses Pydantic's `BaseSettings` with: - Prefix: `NANOBOT_` - Nested delimiter: `__` (double underscore) Examples: - `NANOBOT_PROVIDERS__CUSTOM__API_KEY` → `providers.custom.apiKey` - `NANOBOT_CHANNELS__TELEGRAM__TOKEN` → `channels.telegram.token` - `NANOBOT_AGENTS__DEFAULTS__MODEL` → `agents.defaults.model` --- ## Development Workflow ### Option 1: Development Mode (Recommended) Use `docker-compose.multi.dev.yml` which mounts source code: ```bash # Start in development mode docker compose -f docker-compose.multi.dev.yml up -d nanobot-user1 # Make changes in venv source venv/bin/activate nano nanobot/channels/telegram.py # Restart container (picks up changes) docker compose -f docker-compose.multi.dev.yml restart nanobot-user1 # View logs docker logs -f nanobot-user1-dev ``` ### Option 2: Rebuild After Changes ```bash # Make changes source venv/bin/activate nano nanobot/channels/telegram.py # Rebuild image docker compose -f docker-compose.multi.env.yml build nanobot-user1 # Recreate container docker compose -f docker-compose.multi.env.yml up -d --force-recreate nanobot-user1 ``` ### Option 3: Run on Host (Not Docker) ```bash # Activate venv source venv/bin/activate # Run directly nanobot gateway ``` **Note**: Host command uses: - Config: `~/.nanobot/config.json` (original) - .env: `.env` in current directory (NOT `.env.shared`) --- ## Troubleshooting ### Bot Not Responding 1. **Check if container is running**: ```bash docker ps | grep nanobot-user1 ``` 2. **Check logs**: ```bash docker logs nanobot-user1 --tail 50 ``` 3. **Verify Telegram token**: ```bash grep TELEGRAM__TOKEN .env.user1 ``` 4. **Check allowFrom**: ```bash cat ~/.nanobot-user1/config.json | jq '.channels.telegram.allowFrom' ``` ### Connection Error (Ollama) **Problem**: "Connection error" when bot tries to use LLM **Solution**: 1. Check Ollama is running: ```bash curl http://localhost:11434/api/tags ``` 2. Verify API_BASE in `.env.shared`: ```bash grep API_BASE .env.shared ``` Should be: `http://172.17.0.1:11434/v1` (NOT `localhost`) 3. Restart container: ```bash docker restart nanobot-user1 ``` ### Config Not Loading 1. **Check volume mount**: ```bash docker inspect nanobot-user1 | grep -A 5 Mounts ``` 2. **Verify config exists**: ```bash ls -lh ~/.nanobot-user1/config.json ``` 3. **Check inside container**: ```bash docker exec nanobot-user1 cat /root/.nanobot/config.json ``` ### Environment Variables Not Applied 1. **Check env files are loaded**: ```bash docker exec nanobot-user1 env | grep NANOBOT ``` 2. **Recreate container** (env files loaded at creation): ```bash docker compose -f docker-compose.multi.env.yml up -d --force-recreate nanobot-user1 ``` ### Port Already in Use If port conflicts: ```bash # Check what's using the port sudo lsof -i :18790 # Stop conflicting container docker stop # Or change port in docker-compose.multi.env.yml ``` --- ## Reference ### Docker Compose Files | File | Purpose | Use Case | Env Loading | |------|---------|----------|-------------| | `docker-compose.yml` | Single-bot baseline | One gateway + optional CLI, simplest setup | No `env_file`; uses mounted `~/.nanobot/config.json` and container environment | | `docker-compose.multi.yml` | Multi-bot baseline | Multiple bots with separate config dirs, minimal env indirection | No `env_file`; each bot uses mounted `~/.nanobot-userX/config.json` and container environment | | `docker-compose.multi.env.yml` | Multi-bot production | Stable multi-bot deployments with shared + per-bot overrides | Loads `.env.shared` first, then `.env.userX` (later file overrides earlier) | | `docker-compose.multi.dev.yml` | Multi-bot development | Active code development with source mounted into containers | Same env behavior as `multi.env` (`.env.shared` + `.env.userX`) | ### Container Names - `nanobot-user1` - Bot 1 container (production) - `nanobot-user1-dev` - Bot 1 container (development) - `nanobot-user2` - Bot 2 container - `nanobot-user3` - Bot 3 container ### Ports - User 1: `18790` (host) → `18790` (container) - User 2: `18791` (host) → `18790` (container) - User 3: `18792` (host) → `18790` (container) ### Config Locations | Location | Used By | Purpose | |----------|---------|---------| | `~/.nanobot/config.json` | Host `nanobot gateway` | Original config (not used by Docker) | | `~/.nanobot-user1/config.json` | Docker user1 | Bot 1 config (mounted into container) | | `~/.nanobot-user2/config.json` | Docker user2 | Bot 2 config | | `~/.nanobot-user3/config.json` | Docker user3 | Bot 3 config | ### Environment Files | File | Loaded By | Purpose | |------|-----------|---------| | `.env.shared` | All Docker containers | Shared settings (API keys, model, etc.) | | `.env.user1` | Docker user1 only | Bot 1 specific overrides | | `.env.user2` | Docker user2 only | Bot 2 specific overrides | | `.env.user3` | Docker user3 only | Bot 3 specific overrides | | `.env` | Host `nanobot gateway` | Host environment (not used by Docker) | ### Quick Command Reference ```bash # === START === docker compose -f docker-compose.multi.env.yml up -d nanobot-user1 # Start user1 docker compose -f docker-compose.multi.env.yml up -d # Start all # === STOP === docker compose -f docker-compose.multi.env.yml stop nanobot-user1 # Stop user1 docker compose -f docker-compose.multi.env.yml down # Stop all # === RESTART === docker compose -f docker-compose.multi.env.yml restart nanobot-user1 # Restart user1 docker compose -f docker-compose.multi.env.yml restart # Restart all # === LOGS === docker logs -f nanobot-user1 # View logs docker compose -f docker-compose.multi.env.yml logs -f nanobot-user1 # View logs # === STATUS === docker compose -f docker-compose.multi.env.yml ps # List containers docker ps | grep nanobot # List containers # === REBUILD === docker compose -f docker-compose.multi.env.yml build nanobot-user1 # Rebuild user1 docker compose -f docker-compose.multi.env.yml build # Rebuild all # === RECREATE (after config changes) === docker compose -f docker-compose.multi.env.yml up -d --force-recreate nanobot-user1 ``` --- ## Common Tasks ### Add a New Bot (User4) 1. Create config directory: ```bash mkdir -p ~/.nanobot-user4 ``` 2. Create config file: ```bash cat > ~/.nanobot-user4/config.json << 'EOF' { "channels": { "telegram": { "enabled": true, "allowFrom": ["USERNAME"] } } } EOF ``` 3. Create env file: ```bash cp .env.user1 .env.user4 nano .env.user4 # Edit with bot-specific settings ``` 4. Add to `docker-compose.multi.env.yml`: ```yaml nanobot-user4: # ... (copy from nanobot-user1, change port to 18793) ``` 5. Start: ```bash docker compose -f docker-compose.multi.env.yml up -d nanobot-user4 ``` ### Update Ollama API Base If Ollama IP changes: ```bash # Edit .env.shared sed -i 's|http://172.17.0.1:11434|http://NEW_IP:11434|g' .env.shared # Restart all containers docker compose -f docker-compose.multi.env.yml restart ``` ### Backup Configurations ```bash # Backup all configs tar -czf nanobot-configs-backup-$(date +%Y%m%d).tar.gz \ ~/.nanobot-user* \ .env.shared .env.user* # Restore tar -xzf nanobot-configs-backup-YYYYMMDD.tar.gz ``` --- ## Notes - **Original config** (`~/.nanobot/config.json`) is NOT used by Docker containers - **Host venv** is NOT needed to run Docker commands (Docker builds its own environment) - **Environment variables** override config file settings - **Arrays** (like `allowFrom`) must be in `config.json`, NOT in env files - **Ollama API_BASE** must use `172.17.0.1` (Docker bridge) not `localhost` --- ## See Also - `ENV_FILES_GUIDE.md` - Detailed env file management - `DEVELOPMENT_WITH_DOCKER.md` - Development workflow details - `SETUP_SUMMARY.md` - Initial setup summary - `VERIFY_DOCKER_SETUP.md` - Verification checklist