# 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


