- 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
16 KiB
Docker Multi-Bot Setup Guide
Complete guide for running multiple nanobot instances with Docker, each with its own Telegram bot.
📋 Table of Contents
- Quick Start
- Architecture Overview
- Setup Instructions
- Running Bots
- Configuration Management
- Development Workflow
- Troubleshooting
- Reference
Quick Start
Start Only User1 Bot
docker compose -f docker-compose.multi.env.yml up -d nanobot-user1
Start All Bots
docker compose -f docker-compose.multi.env.yml up -d
Stop All Bots
docker compose -f docker-compose.multi.env.yml down
Stop Specific Bot
docker compose -f docker-compose.multi.env.yml stop nanobot-user1
View Logs
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
-
Docker Compose loads environment files:
- First:
.env.shared(shared settings) - Second:
.env.userX(bot-specific overrides) - Later files override earlier ones
- First:
-
Container mounts config directory:
- Host:
~/.nanobot-user1→ Container:/root/.nanobot
- Host:
-
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:
./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:
sudo chown -R "$(whoami):$(whoami)" ~/.nanobot/workspaces
Step 1: Create Environment Files
Run the setup script:
./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:
nano .env.shared
Example:
# 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:
nano .env.user1
Example:
# 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:
./create-bot-configs.sh
Or manually create ~/.nanobot-user1/config.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
docker compose -f docker-compose.multi.env.yml build
Running Bots
Start Commands
# 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
# 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
# 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
# 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
# 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:
nano .env.shared
Restart affected containers:
docker compose -f docker-compose.multi.env.yml restart
Updating Bot-Specific Settings
Edit .env.userX:
nano .env.user1
Restart that specific bot:
docker compose -f docker-compose.multi.env.yml restart nanobot-user1
Updating Config Files
Edit config file:
nano ~/.nanobot-user1/config.json
Restart container:
docker restart nanobot-user1
Adding Email to allowFrom
# 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.apiKeyNANOBOT_CHANNELS__TELEGRAM__TOKEN→channels.telegram.tokenNANOBOT_AGENTS__DEFAULTS__MODEL→agents.defaults.model
Development Workflow
Option 1: Development Mode (Recommended)
Use docker-compose.multi.dev.yml which mounts source code:
# 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
# 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)
# Activate venv
source venv/bin/activate
# Run directly
nanobot gateway
Note: Host command uses:
- Config:
~/.nanobot/config.json(original) - .env:
.envin current directory (NOT.env.shared)
Troubleshooting
Bot Not Responding
-
Check if container is running:
docker ps | grep nanobot-user1 -
Check logs:
docker logs nanobot-user1 --tail 50 -
Verify Telegram token:
grep TELEGRAM__TOKEN .env.user1 -
Check allowFrom:
cat ~/.nanobot-user1/config.json | jq '.channels.telegram.allowFrom'
Connection Error (Ollama)
Problem: "Connection error" when bot tries to use LLM
Solution:
-
Check Ollama is running:
curl http://localhost:11434/api/tags -
Verify API_BASE in
.env.shared:grep API_BASE .env.sharedShould be:
http://172.17.0.1:11434/v1(NOTlocalhost) -
Restart container:
docker restart nanobot-user1
Config Not Loading
-
Check volume mount:
docker inspect nanobot-user1 | grep -A 5 Mounts -
Verify config exists:
ls -lh ~/.nanobot-user1/config.json -
Check inside container:
docker exec nanobot-user1 cat /root/.nanobot/config.json
Environment Variables Not Applied
-
Check env files are loaded:
docker exec nanobot-user1 env | grep NANOBOT -
Recreate container (env files loaded at creation):
docker compose -f docker-compose.multi.env.yml up -d --force-recreate nanobot-user1
Port Already in Use
If port conflicts:
# 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 containernanobot-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
# === 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)
-
Create config directory:
mkdir -p ~/.nanobot-user4 -
Create config file:
cat > ~/.nanobot-user4/config.json << 'EOF' { "channels": { "telegram": { "enabled": true, "allowFrom": ["USERNAME"] } } } EOF -
Create env file:
cp .env.user1 .env.user4 nano .env.user4 # Edit with bot-specific settings -
Add to
docker-compose.multi.env.yml:nanobot-user4: # ... (copy from nanobot-user1, change port to 18793) -
Start:
docker compose -f docker-compose.multi.env.yml up -d nanobot-user4
Update Ollama API Base
If Ollama IP changes:
# 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
# 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 inconfig.json, NOT in env files - Ollama API_BASE must use
172.17.0.1(Docker bridge) notlocalhost
See Also
ENV_FILES_GUIDE.md- Detailed env file managementDEVELOPMENT_WITH_DOCKER.md- Development workflow detailsSETUP_SUMMARY.md- Initial setup summaryVERIFY_DOCKER_SETUP.md- Verification checklist