nanobot/DOCKER_MULTI_BOT_GUIDE.md
tanyar09 7901f090f9 Add per-agent workspaces and MCP/skills backlog doc
- Add agent_workspaces/{ilia,family,wife} skeletons (AGENTS, USER, SOUL, memory)
- Add scripts/init-agent-workspaces.sh to populate ~/.nanobot/workspaces/
- Mount ~/.nanobot/workspaces/{ilia,family,wife} in multi compose as /workspace
- Document Step 0 and layout in DOCKER_MULTI_BOT_GUIDE.md
- Track docs/mcp_and_skills_backlog.md (force-add; docs/ is gitignored)

Made-with: Cursor
2026-03-30 13:20:38 -04:00

648 lines
16 KiB
Markdown

# 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 <container_name>
# 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