rdev/docs/reference.md
jordan 17aeb1c25b Initial commit: rdev v0.1 base case
- Dockerfile for claudebox with Claude Code CLI
- Kustomize manifests for k3s deployment
- Scripts for credentials, deploy, and verify
- README with quick start guide

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 19:24:07 -07:00

1613 lines
40 KiB
Markdown

# Multi-monorepo Claude Code infrastructure: Complete reference guide
This guide covers deploying claudebox across multiple monorepos with Discord control, using Claude Pro or Team subscription authentication. This architecture provides complete project isolation, parallel AI development, and team-based access control without API keys.
## Architecture overview
**Pattern: Multi-claudebox with single-bot routing**
This architecture runs separate Docker containers for each monorepo, managed by a single Discord bot that routes commands based on channel context. Each container maintains independent dependencies, git state, network policies, and Claude authentication sessions.
```
Physical Layout:
├── Remote VM (Ubuntu 22.04+)
│ ├── Docker Container: claudebox-project-a
│ ├── Docker Container: claudebox-project-b
│ └── Docker Container: claudebox-project-c
│ └── Discord Bot Process (routes to containers)
Discord Server:
├── #project-a-dev → claudebox-project-a
├── #project-b-dev → claudebox-project-b
└── #project-c-dev → claudebox-project-c
```
**Why this pattern:**
- Dependency isolation (Project A: Node 18, Project B: Node 20, Project C: Python 3.11)
- Parallel execution (Claude works on all projects simultaneously)
- Security boundaries (network policies and file access per project)
- Resource allocation (CPU/memory limits per project)
- Independent git state (no cross-contamination)
---
## Subscription requirements and authentication
**Required subscriptions:**
You need ONE of the following:
- **Claude Pro** ($20/month per developer) - individual use
- **Claude Team** ($30/month per developer, 5 minimum) - team collaboration with centralized billing
- **Claude Enterprise** - contact Anthropic sales for multi-team deployments
**Authentication model:**
Claude Code authenticates via OAuth to your claude.ai account. Each developer on your team needs their own subscription, and each will authenticate their claudebox instances with their personal credentials. This is fundamentally different from API-based usage—you're using the web subscription's usage limits, not paying per token.
**Per-developer or shared authentication:**
You have two deployment options:
1. **Per-developer claudebox** (Recommended for teams):
- Each developer has their own VM with their own claudebox instances
- Each authenticates with their personal Claude subscription
- Usage tracked per developer
- No credential sharing
2. **Shared claudebox with team account** (Single shared development server):
- One VM running all claudebox instances
- Authenticate with a shared Claude Team account
- All developers use same Discord bot
- Usage pooled across team
This guide assumes **shared claudebox** for simplicity, but the architecture works for both models.
---
## Initial VM setup and prerequisites
**Provision your remote VM:**
Minimum specifications:
- **CPU**: 8 cores (for 3 parallel containers)
- **RAM**: 16GB (allocate 4GB per container minimum)
- **Storage**: 100GB SSD (monorepos + Docker images + build artifacts)
- **OS**: Ubuntu 22.04 LTS or Debian 12
Cloud provider recommendations:
- AWS: t3.2xlarge or c6i.2xlarge
- GCP: n2-standard-8
- Azure: Standard_D8s_v3
- DigitalOcean: CPU-Optimized 8GB/4vCPU droplet
**Install system dependencies:**
```bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
newgrp docker
# Install Node.js (for Claude Code CLI)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Install Deno (for Discord bot)
curl -fsSL https://deno.land/install.sh | sh
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Install Git and development tools
sudo apt install -y git build-essential curl wget vim
# Install Docker Compose
sudo apt install -y docker-compose-plugin
```
**Clone your monorepos:**
```bash
mkdir -p ~/projects
cd ~/projects
# Clone your monorepos
git clone https://github.com/yourorg/monorepo-a.git
git clone https://github.com/yourorg/monorepo-b.git
git clone https://github.com/yourorg/monorepo-c.git
```
---
## Claude Code CLI installation and authentication
**Install Claude Code globally:**
```bash
npm install -g @anthropic-ai/claude-code
```
**Authenticate with your subscription:**
```bash
claude /login
```
This opens a browser window where you'll sign in with your Claude Pro/Team account. The authentication token is saved to `~/.claude/.credentials.json` and will be mounted into each container.
**Verify authentication:**
```bash
claude --version
ls -la ~/.claude/.credentials.json
```
The credentials file should exist and contain your authentication state. This single authentication covers all three claudebox instances since they'll mount the same credentials directory.
**Important: Credential persistence**
Your authentication lasts approximately 30 days. When it expires:
1. Run `claude /login` again on the host VM
2. Restart all claudebox containers to pick up new credentials
3. No need to re-authenticate inside containers
---
## Installing and configuring claudebox
**Download and install claudebox:**
```bash
cd ~
wget https://github.com/RchGrav/claudebox/releases/latest/download/claudebox.run
chmod +x claudebox.run
./claudebox.run
# Add to PATH
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Verify installation
claudebox info
```
**Initialize claudebox for each project:**
```bash
# Project A
cd ~/projects/monorepo-a
claudebox init project-a
# Project B
cd ~/projects/monorepo-b
claudebox init project-b
# Project C
cd ~/projects/monorepo-c
claudebox init project-c
```
This creates isolated configuration directories:
```
~/.claudebox/
├── project-a/
│ ├── .claude/ # Will mount host credentials
│ ├── firewall/ # Network allowlist
│ └── workspace/ # Symlink to ~/projects/monorepo-a
├── project-b/
│ └── ...
└── project-c/
└── ...
```
**Install profiles for each project:**
Profiles define the development environment for each container.
```bash
# Project A: JavaScript/TypeScript monorepo with Turborepo
claudebox profile javascript typescript
claudebox profile project-a --install
# Project B: Python monorepo with Poetry
claudebox profile python ml
claudebox profile project-b --install
# Project C: Rust monorepo
claudebox profile rust
claudebox profile project-c --install
```
**Configure project-specific profiles:**
Create custom profile files for monorepo tooling:
```ini
# ~/.claudebox/profiles/project-a.ini
[base]
name = JavaScript Monorepo (Turborepo)
extends = javascript,typescript
[packages]
# System packages
apt = build-essential python3 git
# Node.js tools
npm = turbo nx pnpm typescript eslint prettier
[environment]
NODE_VERSION = 20
PNPM_HOME = /root/.local/share/pnpm
PATH = $PNPM_HOME:$PATH
[startup]
# Install dependencies on container start
commands =
cd /workspace && pnpm install
turbo daemon start
[firewall]
# Allow access to package registries
allow = registry.npmjs.org,github.com,*.github.com
[resource_limits]
cpus = 2.0
memory = 4g
```
```ini
# ~/.claudebox/profiles/project-b.ini
[base]
name = Python Monorepo (Poetry)
extends = python,ml
[packages]
apt = python3.11 python3-pip python3-venv
pip = poetry pytest black mypy pandas numpy torch
[environment]
PYTHON_VERSION = 3.11
POETRY_VIRTUALENVS_IN_PROJECT = true
[startup]
commands =
cd /workspace && poetry install
poetry run python --version
[firewall]
allow = pypi.org,files.pythonhosted.org,github.com
[resource_limits]
cpus = 4.0
memory = 8g
```
```ini
# ~/.claudebox/profiles/project-c.ini
[base]
name = Rust Monorepo
extends = rust
[packages]
apt = build-essential pkg-config libssl-dev
cargo = cargo-workspaces cargo-watch cargo-edit
[environment]
RUST_VERSION = stable
CARGO_HOME = /usr/local/cargo
[startup]
commands =
cd /workspace && cargo fetch
cargo build --workspace --release
[firewall]
allow = crates.io,static.crates.io,github.com
[resource_limits]
cpus = 3.0
memory = 6g
```
---
## Docker compose orchestration
Create a centralized orchestration file for all containers.
**Create infrastructure directory:**
```bash
mkdir -p ~/claude-infra
cd ~/claude-infra
```
**Create docker-compose.yml:**
```yaml
# ~/claude-infra/docker-compose.yml
version: '3.8'
services:
claudebox-project-a:
image: ghcr.io/rchgrav/claudebox:latest
container_name: claudebox-project-a
hostname: project-a-dev
volumes:
# Mount monorepo
- ~/projects/monorepo-a:/workspace:rw
# Mount shared Claude credentials (READ-ONLY)
- ~/.claude:/root/.claude:ro
# Mount project-specific config
- ~/.claudebox/project-a/firewall:/etc/claudebox/firewall:rw
# Shared caches for faster builds
- npm-cache:/root/.npm
- pnpm-store:/root/.local/share/pnpm/store
environment:
- PROJECT_NAME=project-a
- WORKSPACE=/workspace
- CLAUDEBOX_PROFILE=project-a
working_dir: /workspace
deploy:
resources:
limits:
cpus: '2.0'
memory: 4G
reservations:
cpus: '1.0'
memory: 2G
networks:
- claude-network-a
restart: unless-stopped
# Keep container running
command: tail -f /dev/null
claudebox-project-b:
image: ghcr.io/rchgrav/claudebox:latest
container_name: claudebox-project-b
hostname: project-b-dev
volumes:
- ~/projects/monorepo-b:/workspace:rw
- ~/.claude:/root/.claude:ro
- ~/.claudebox/project-b/firewall:/etc/claudebox/firewall:rw
- pip-cache:/root/.cache/pip
- poetry-cache:/root/.cache/pypoetry
environment:
- PROJECT_NAME=project-b
- WORKSPACE=/workspace
- CLAUDEBOX_PROFILE=project-b
- PYTHON_VERSION=3.11
working_dir: /workspace
deploy:
resources:
limits:
cpus: '4.0'
memory: 8G
reservations:
cpus: '2.0'
memory: 4G
networks:
- claude-network-b
restart: unless-stopped
command: tail -f /dev/null
claudebox-project-c:
image: ghcr.io/rchgrav/claudebox:latest
container_name: claudebox-project-c
hostname: project-c-dev
volumes:
- ~/projects/monorepo-c:/workspace:rw
- ~/.claude:/root/.claude:ro
- ~/.claudebox/project-c/firewall:/etc/claudebox/firewall:rw
- cargo-registry:/usr/local/cargo/registry
- cargo-git:/usr/local/cargo/git
environment:
- PROJECT_NAME=project-c
- WORKSPACE=/workspace
- CLAUDEBOX_PROFILE=project-c
- RUST_VERSION=stable
working_dir: /workspace
deploy:
resources:
limits:
cpus: '3.0'
memory: 6G
reservations:
cpus: '1.5'
memory: 3G
networks:
- claude-network-c
restart: unless-stopped
command: tail -f /dev/null
networks:
claude-network-a:
driver: bridge
claude-network-b:
driver: bridge
claude-network-c:
driver: bridge
volumes:
npm-cache:
pnpm-store:
pip-cache:
poetry-cache:
cargo-registry:
cargo-git:
```
**Container lifecycle management:**
```bash
# Start all containers
cd ~/claude-infra
docker compose up -d
# View status
docker compose ps
# View logs
docker compose logs -f claudebox-project-a
# Stop all containers
docker compose stop
# Restart specific container
docker compose restart claudebox-project-b
# Remove all containers (keeps volumes)
docker compose down
# Full cleanup including volumes
docker compose down -v
```
---
## Discord bot setup and configuration
**Create Discord application:**
1. Go to https://discord.com/developers/applications
2. Click **New Application**, name it "Claude-MultiProject"
3. Navigate to **Bot** section
4. Click **Add Bot**
5. Enable **Message Content Intent**
6. Copy the **Bot Token** (save securely)
7. Copy the **Application ID** from General Information
**Invite bot to your server:**
1. Go to **OAuth2****URL Generator**
2. Select scopes: `bot`, `applications.commands`
3. Select permissions:
- Send Messages
- Use Slash Commands
- Read Message History
- Embed Links
- Attach Files
- Manage Messages (for cleanup)
4. Copy generated URL and open in browser
5. Select your server and authorize
**Clone and configure the Discord bot:**
```bash
cd ~/claude-infra
git clone https://github.com/zebbern/claude-code-discord.git discord-bot
cd discord-bot
```
**Create environment configuration:**
```bash
# ~/claude-infra/discord-bot/.env
DISCORD_TOKEN=your_bot_token_here
APPLICATION_ID=your_application_id_here
# Project routing configuration
PROJECT_A_CONTAINER=claudebox-project-a
PROJECT_B_CONTAINER=claudebox-project-b
PROJECT_C_CONTAINER=claudebox-project-c
# Channel mapping (will configure in code)
# This is just documentation
CHANNEL_PROJECT_A=project-a-dev
CHANNEL_PROJECT_B=project-b-dev
CHANNEL_PROJECT_C=project-c-dev
```
**Create enhanced routing configuration:**
```typescript
// ~/claude-infra/discord-bot/config.ts
export interface ProjectConfig {
container: string;
workdir: string;
profile: string;
allowedRoles: string[];
description: string;
monorepo: {
tool: 'turbo' | 'nx' | 'poetry' | 'cargo';
packages: string[];
};
}
export const PROJECT_CONFIGS: Record<string, ProjectConfig> = {
'project-a-dev': {
container: 'claudebox-project-a',
workdir: '/workspace',
profile: 'javascript typescript',
allowedRoles: ['project-a-devs', 'admin', 'engineering'],
description: 'JavaScript/TypeScript monorepo with Turborepo',
monorepo: {
tool: 'turbo',
packages: ['@myorg/api', '@myorg/web', '@myorg/shared']
}
},
'project-b-dev': {
container: 'claudebox-project-b',
workdir: '/workspace',
profile: 'python ml',
allowedRoles: ['project-b-devs', 'admin', 'data-science'],
description: 'Python monorepo with Poetry for ML workflows',
monorepo: {
tool: 'poetry',
packages: ['ml-pipeline', 'data-processing', 'inference-service']
}
},
'project-c-dev': {
container: 'claudebox-project-c',
workdir: '/workspace',
profile: 'rust',
allowedRoles: ['project-c-devs', 'admin', 'systems'],
description: 'Rust monorepo with Cargo workspaces',
monorepo: {
tool: 'cargo',
packages: ['core', 'cli', 'server']
}
}
};
// Monorepo context templates
export const MONOREPO_CONTEXT = {
turbo: `This is a Turborepo monorepo. When making changes:
- Use 'turbo run <task>' for builds/tests
- Changes may affect multiple packages
- Check package.json workspaces for dependencies
- Use 'turbo run build --filter=<package>' for specific packages`,
nx: `This is an Nx monorepo. When making changes:
- Use 'nx run <project>:<target>' for tasks
- Check nx.json for task configuration
- Use 'nx affected:build' for changed packages`,
poetry: `This is a Poetry monorepo. When making changes:
- Use 'poetry run <command>' for scripts
- Update pyproject.toml for dependencies
- Run 'poetry install' after dependency changes`,
cargo: `This is a Cargo workspace. When making changes:
- Use 'cargo build --workspace' for all packages
- Update Cargo.toml in workspace root
- Use 'cargo build -p <package>' for specific crates`
};
```
**Create enhanced bot with routing:**
```typescript
// ~/claude-infra/discord-bot/bot.ts
import { Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder } from 'discord.js';
import { exec } from 'child_process';
import { promisify } from 'util';
import { PROJECT_CONFIGS, MONOREPO_CONTEXT } from './config.ts';
const execAsync = promisify(exec);
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
// Helper: Check user permissions
function hasPermission(member: any, allowedRoles: string[]): boolean {
return member.roles.cache.some((role: any) =>
allowedRoles.includes(role.name.toLowerCase())
);
}
// Helper: Execute command in container
async function execInContainer(
container: string,
command: string,
cwd: string = '/workspace'
): Promise<string> {
const dockerCmd = `docker exec -w ${cwd} ${container} bash -c "${command.replace(/"/g, '\\"')}"`;
try {
const { stdout, stderr } = await execAsync(dockerCmd, {
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs
timeout: 300000 // 5 minute timeout
});
return stdout || stderr;
} catch (error: any) {
throw new Error(`Container execution failed: ${error.message}`);
}
}
// Helper: Get project config from channel
function getProjectConfig(channelName: string) {
const config = PROJECT_CONFIGS[channelName];
if (!config) {
throw new Error(`No configuration found for channel: ${channelName}`);
}
return config;
}
// Command: /claude
const claudeCommand = new SlashCommandBuilder()
.setName('claude')
.setDescription('Send a prompt to Claude Code')
.addStringOption(option =>
option.setName('prompt')
.setDescription('Your prompt for Claude')
.setRequired(true)
)
.addStringOption(option =>
option.setName('mode')
.setDescription('Execution mode')
.addChoices(
{ name: 'normal', value: 'normal' },
{ name: 'auto-accept', value: 'auto' },
{ name: 'plan-only', value: 'plan' }
)
);
// Command: /status
const statusCommand = new SlashCommandBuilder()
.setName('status')
.setDescription('Check container and project status');
// Command: /shell
const shellCommand = new SlashCommandBuilder()
.setName('shell')
.setDescription('Execute shell command in project container')
.addStringOption(option =>
option.setName('command')
.setDescription('Shell command to execute')
.setRequired(true)
);
// Command: /git
const gitCommand = new SlashCommandBuilder()
.setName('git')
.setDescription('Execute git command')
.addStringOption(option =>
option.setName('args')
.setDescription('Git arguments (e.g., "status", "diff HEAD")')
.setRequired(true)
);
// Command: /monorepo-info
const monorepoInfoCommand = new SlashCommandBuilder()
.setName('monorepo-info')
.setDescription('Show monorepo structure and packages');
// Register commands
const commands = [
claudeCommand,
statusCommand,
shellCommand,
gitCommand,
monorepoInfoCommand,
].map(cmd => cmd.toJSON());
// Handle interactions
client.on('interactionCreate', async interaction => {
if (!interaction.isCommand()) return;
try {
const channelName = interaction.channel?.name || '';
const config = getProjectConfig(channelName);
// Check permissions
if (!hasPermission(interaction.member, config.allowedRoles)) {
await interaction.reply({
content: `⛔ You don't have permission to use Claude Code on ${config.description}. Required roles: ${config.allowedRoles.join(', ')}`,
ephemeral: true
});
return;
}
await interaction.deferReply();
switch (interaction.commandName) {
case 'claude': {
const prompt = interaction.options.getString('prompt', true);
const mode = interaction.options.getString('mode') || 'normal';
// Add monorepo context to prompt
const monorepContext = MONOREPO_CONTEXT[config.monorepo.tool];
const enhancedPrompt = `${monorepContext}\n\nUser request: ${prompt}`;
const modeFlag = mode === 'auto' ? '--dangerously-skip-permissions' :
mode === 'plan' ? '--plan-only' : '';
const output = await execInContainer(
config.container,
`claude "${enhancedPrompt}" ${modeFlag}`,
config.workdir
);
// Split long responses
const chunks = output.match(/[\s\S]{1,1900}/g) || [];
for (const chunk of chunks) {
await interaction.followUp({
content: `\`\`\`\n${chunk}\n\`\`\``
});
}
break;
}
case 'status': {
const containerStatus = await execAsync(`docker inspect ${config.container} --format '{{.State.Status}}'`);
const diskUsage = await execInContainer(config.container, 'df -h /workspace | tail -1');
const gitStatus = await execInContainer(config.container, 'git status -s', config.workdir);
await interaction.editReply({
content: `📊 **Project Status: ${config.description}**\n\n` +
`Container: ${containerStatus.stdout.trim()}\n` +
`Disk: ${diskUsage}\n` +
`\`\`\`\n${gitStatus || 'No changes'}\n\`\`\``
});
break;
}
case 'shell': {
const command = interaction.options.getString('command', true);
// Safety check for destructive commands
const dangerous = ['rm -rf', 'dd if=', 'mkfs', '> /dev/'];
if (dangerous.some(d => command.includes(d))) {
await interaction.editReply('⛔ Potentially destructive command blocked. Use with caution.');
return;
}
const output = await execInContainer(config.container, command, config.workdir);
await interaction.editReply({
content: `\`\`\`bash\n$ ${command}\n${output.slice(0, 1900)}\n\`\`\``
});
break;
}
case 'git': {
const args = interaction.options.getString('args', true);
const output = await execInContainer(config.container, `git ${args}`, config.workdir);
await interaction.editReply({
content: `\`\`\`\n${output.slice(0, 1900)}\n\`\`\``
});
break;
}
case 'monorepo-info': {
const packages = config.monorepo.packages.join('\n- ');
const tree = await execInContainer(config.container, 'tree -L 2 -d', config.workdir);
await interaction.editReply({
content: `📦 **Monorepo Structure**\n\n` +
`Tool: ${config.monorepo.tool}\n` +
`Packages:\n- ${packages}\n\n` +
`\`\`\`\n${tree.slice(0, 1500)}\n\`\`\``
});
break;
}
}
} catch (error: any) {
console.error('Command error:', error);
await interaction.editReply({
content: `❌ Error: ${error.message}`
});
}
});
// Bot ready
client.on('ready', async () => {
console.log(`✅ Bot logged in as ${client.user?.tag}`);
// Register slash commands
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN!);
try {
await rest.put(
Routes.applicationCommands(process.env.APPLICATION_ID!),
{ body: commands }
);
console.log('✅ Slash commands registered');
} catch (error) {
console.error('Failed to register commands:', error);
}
});
// Start bot
client.login(process.env.DISCORD_TOKEN);
```
**Start the Discord bot:**
```bash
cd ~/claude-infra/discord-bot
deno run --allow-all bot.ts
```
**Run bot as systemd service:**
```ini
# /etc/systemd/system/claude-discord-bot.service
[Unit]
Description=Claude Code Discord Bot (Multi-Project)
After=network.target docker.service
[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser/claude-infra/discord-bot
EnvironmentFile=/home/youruser/claude-infra/discord-bot/.env
ExecStart=/home/youruser/.deno/bin/deno run --allow-all bot.ts
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable claude-discord-bot
sudo systemctl start claude-discord-bot
sudo systemctl status claude-discord-bot
```
---
## Discord server organization
**Create structured channel layout:**
```
Your Discord Server
├── 📁 INFRASTRUCTURE
│ ├── #system-status (bot health, container stats)
│ ├── #announcements (maintenance windows, updates)
│ └── #admin-commands (restricted admin-only channel)
├── 📁 PROJECT A - JavaScript Monorepo
│ ├── #project-a-dev (Claude commands)
│ ├── #project-a-logs (git commits, CI/CD)
│ └── #project-a-discuss (team chat)
├── 📁 PROJECT B - Python ML
│ ├── #project-b-dev
│ ├── #project-b-logs
│ └── #project-b-discuss
├── 📁 PROJECT C - Rust Systems
│ ├── #project-c-dev
│ ├── #project-c-logs
│ └── #project-c-discuss
└── 📁 RESOURCES
├── #documentation (setup guides, workflows)
└── #troubleshooting (common issues, solutions)
```
**Configure role-based permissions:**
Create Discord roles:
- `admin` - Full access to all projects
- `project-a-devs` - Access to Project A
- `project-b-devs` - Access to Project B
- `project-c-devs` - Access to Project C
- `engineering` - Read access to all projects
- `observers` - View-only access
Set channel permissions:
1. Each `#project-X-dev` channel: Only respective role can send messages
2. Each `#project-X-logs` channel: Read-only for all, bot can post
3. `#admin-commands`: Admin-only
4. `#system-status`: Bot posts, all can view
---
## Management scripts and automation
**Create container management wrapper:**
```bash
#!/bin/bash
# ~/claude-infra/manage.sh
set -e
COMPOSE_FILE="$HOME/claude-infra/docker-compose.yml"
function usage() {
cat << EOF
Claude Multi-Project Manager
Usage: ./manage.sh <command> [options]
Commands:
start [project] Start all containers or specific project
stop [project] Stop all containers or specific project
restart [project] Restart containers
status Show status of all containers
logs <project> View logs for project
exec <project> <cmd> Execute command in project container
shell <project> Open shell in project container
rebuild [project] Rebuild container images
backup Backup all container state
restore <file> Restore from backup
health Run health checks on all projects
update Update claudebox and bot
Projects: project-a, project-b, project-c
Examples:
./manage.sh start project-a
./manage.sh logs project-b
./manage.sh exec project-c "git status"
./manage.sh shell project-a
EOF
}
function get_container() {
case "$1" in
project-a) echo "claudebox-project-a" ;;
project-b) echo "claudebox-project-b" ;;
project-c) echo "claudebox-project-c" ;;
*) echo "Unknown project: $1" >&2; exit 1 ;;
esac
}
case "$1" in
start)
if [ -z "$2" ]; then
docker compose -f "$COMPOSE_FILE" up -d
echo "✅ All containers started"
else
CONTAINER=$(get_container "$2")
docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER"
echo "✅ Started $CONTAINER"
fi
;;
stop)
if [ -z "$2" ]; then
docker compose -f "$COMPOSE_FILE" stop
echo "✅ All containers stopped"
else
CONTAINER=$(get_container "$2")
docker compose -f "$COMPOSE_FILE" stop "$CONTAINER"
echo "✅ Stopped $CONTAINER"
fi
;;
restart)
if [ -z "$2" ]; then
docker compose -f "$COMPOSE_FILE" restart
echo "✅ All containers restarted"
else
CONTAINER=$(get_container "$2")
docker compose -f "$COMPOSE_FILE" restart "$CONTAINER"
echo "✅ Restarted $CONTAINER"
fi
;;
status)
docker compose -f "$COMPOSE_FILE" ps
echo ""
echo "Resource usage:"
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" \
claudebox-project-a claudebox-project-b claudebox-project-c
;;
logs)
if [ -z "$2" ]; then
echo "Error: Specify project" >&2
exit 1
fi
CONTAINER=$(get_container "$2")
docker compose -f "$COMPOSE_FILE" logs -f --tail=100 "$CONTAINER"
;;
exec)
if [ -z "$2" ] || [ -z "$3" ]; then
echo "Error: Specify project and command" >&2
exit 1
fi
CONTAINER=$(get_container "$2")
shift 2
docker exec -it "$CONTAINER" bash -c "$@"
;;
shell)
if [ -z "$2" ]; then
echo "Error: Specify project" >&2
exit 1
fi
CONTAINER=$(get_container "$2")
docker exec -it "$CONTAINER" bash
;;
rebuild)
if [ -z "$2" ]; then
docker compose -f "$COMPOSE_FILE" build --no-cache
docker compose -f "$COMPOSE_FILE" up -d
else
CONTAINER=$(get_container "$2")
docker compose -f "$COMPOSE_FILE" build --no-cache "$CONTAINER"
docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER"
fi
echo "✅ Rebuild complete"
;;
backup)
BACKUP_DIR="$HOME/claude-backups/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
# Backup container state
for project in project-a project-b project-c; do
CONTAINER=$(get_container "$project")
echo "Backing up $CONTAINER..."
docker export "$CONTAINER" | gzip > "$BACKUP_DIR/${CONTAINER}.tar.gz"
done
# Backup configurations
cp -r ~/.claudebox "$BACKUP_DIR/config"
echo "✅ Backup saved to $BACKUP_DIR"
;;
health)
echo "Running health checks..."
echo ""
for project in project-a project-b project-c; do
CONTAINER=$(get_container "$project")
echo "Checking $project..."
# Container running?
if docker ps --filter "name=$CONTAINER" --format '{{.Names}}' | grep -q "$CONTAINER"; then
echo " ✅ Container running"
else
echo " ❌ Container not running"
continue
fi
# Claude authenticated?
if docker exec "$CONTAINER" bash -c "[ -f /root/.claude/.credentials.json ]"; then
echo " ✅ Claude authenticated"
else
echo " ⚠️ Claude not authenticated"
fi
# Workspace accessible?
if docker exec "$CONTAINER" bash -c "[ -d /workspace ]"; then
echo " ✅ Workspace mounted"
else
echo " ❌ Workspace not mounted"
fi
# Git repository?
if docker exec "$CONTAINER" bash -c "cd /workspace && git status >/dev/null 2>&1"; then
echo " ✅ Git repository valid"
else
echo " ⚠️ Not a git repository"
fi
echo ""
done
;;
update)
echo "Updating Claude Code CLI..."
npm install -g @anthropic-ai/claude-code
echo "Updating Discord bot..."
cd ~/claude-infra/discord-bot
git pull
echo "Updating containers..."
docker compose -f "$COMPOSE_FILE" pull
docker compose -f "$COMPOSE_FILE" up -d
echo "✅ Update complete"
;;
*)
usage
exit 1
;;
esac
```
**Make executable:**
```bash
chmod +x ~/claude-infra/manage.sh
# Add alias for convenience
echo "alias claude-manage='~/claude-infra/manage.sh'" >> ~/.bashrc
source ~/.bashrc
```
**Create automatic checkpoint script:**
```bash
#!/bin/bash
# ~/claude-infra/auto-checkpoint.sh
# Commits work in progress for all projects every hour
for project in project-a project-b project-c; do
CONTAINER="claudebox-$project"
echo "Checkpointing $project..."
docker exec "$CONTAINER" bash -c "
cd /workspace && \
git add -A && \
git commit -m 'Auto-checkpoint: $(date +"%Y-%m-%d %H:%M:%S")' >/dev/null 2>&1 || true
"
done
echo "Checkpoints created at $(date)"
```
**Schedule with cron:**
```bash
chmod +x ~/claude-infra/auto-checkpoint.sh
# Add to crontab
crontab -e
# Add line:
0 * * * * /home/youruser/claude-infra/auto-checkpoint.sh >> /home/youruser/claude-infra/checkpoint.log 2>&1
```
**Create health monitoring:**
```bash
#!/bin/bash
# ~/claude-infra/health-monitor.sh
WEBHOOK_URL="your_discord_webhook_url"
function send_alert() {
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"content\": \"🚨 $1\"}"
}
# Check each container
for container in claudebox-project-a claudebox-project-b claudebox-project-c; do
if ! docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
send_alert "Container $container is not running!"
# Attempt restart
docker start "$container"
sleep 5
if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
send_alert "Container $container restarted successfully"
else
send_alert "Failed to restart container $container"
fi
fi
done
# Check disk space
DISK_USAGE=$(df -h /home | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 85 ]; then
send_alert "Disk usage is at ${DISK_USAGE}% - cleanup recommended"
fi
# Check memory
MEM_USAGE=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100}')
if [ "$MEM_USAGE" -gt 90 ]; then
send_alert "Memory usage is at ${MEM_USAGE}% - may need to restart containers"
fi
```
**Schedule health checks:**
```bash
chmod +x ~/claude-infra/health-monitor.sh
# Add to crontab (every 5 minutes)
crontab -e
# Add:
*/5 * * * * /home/youruser/claude-infra/health-monitor.sh
```
---
## Operational workflows
**Daily developer workflow:**
1. **Check system status** (via Discord):
```
/status
```
2. **Start working on a feature**:
```
/git checkout -b feature/new-api-endpoint
/claude Create a new REST endpoint for user profile updates in the api package. Follow our existing patterns.
```
3. **Review changes**:
```
/git diff
/git status
```
4. **Run tests**:
```
/shell turbo run test --filter=@myorg/api
```
5. **Commit work**:
```
/git add .
/git commit -m "feat(api): add user profile update endpoint"
/git push origin feature/new-api-endpoint
```
**Cross-project coordination:**
When changes in one project affect another:
1. Make changes in Project A:
```
In #project-a-dev:
/claude Update the API client types to match the new endpoint schema
```
2. Export the changes:
```
/git diff HEAD~1 src/types/api.ts > /tmp/api-changes.patch
```
3. Apply to Project B:
```
In #project-b-dev:
/shell cat /tmp/api-changes.patch
/claude Review this API change from project-a and update our integration accordingly: [paste diff]
```
**Emergency rollback:**
If Claude makes unwanted changes:
```
/git status
/git diff HEAD
/git checkout .
/git clean -fd
```
Or restore from auto-checkpoint:
```
/git reflog
/git reset --hard HEAD@{1}
```
**Subscription usage monitoring:**
Since you're using claude.ai subscriptions (not API), monitor usage via:
1. Go to https://claude.ai/settings
2. Check "Usage" tab for your subscription tier limits
3. Usage resets monthly based on your subscription date
Each container shares the same subscription, so total usage is aggregate across all three projects.
**Credential refresh:**
When your 30-day authentication expires:
```bash
# On the VM
claude /login
# Restart containers to pick up new credentials
claude-manage restart
```
---
## Troubleshooting
**Container won't start:**
```bash
# Check logs
claude-manage logs project-a
# Common issues:
# 1. Port conflict
docker ps -a | grep claudebox
# 2. Volume mount permissions
ls -la ~/projects/monorepo-a
# 3. Credentials missing
ls -la ~/.claude/.credentials.json
```
**"Not authenticated" error in container:**
```bash
# Re-authenticate on host
claude /login
# Verify credentials exist
cat ~/.claude/.credentials.json
# Restart containers
claude-manage restart
# Verify mount inside container
docker exec claudebox-project-a ls -la /root/.claude/
```
**Discord bot not responding:**
```bash
# Check bot process
sudo systemctl status claude-discord-bot
# View bot logs
sudo journalctl -u claude-discord-bot -f
# Common issues:
# 1. Invalid token
grep DISCORD_TOKEN ~/claude-infra/discord-bot/.env
# 2. Missing permissions
# Check bot has "Use Slash Commands" in Discord server settings
# 3. Commands not registered
# Wait 1 hour or restart bot
sudo systemctl restart claude-discord-bot
```
**Claude Code command hangs:**
```bash
# Check container CPU/memory
docker stats claudebox-project-a
# Kill hung process inside container
docker exec claudebox-project-a pkill -f claude
# Or restart container
claude-manage restart project-a
```
**Monorepo build failures:**
```bash
# Clear caches
claude-manage exec project-a "rm -rf node_modules .turbo && pnpm install"
claude-manage exec project-b "poetry cache clear --all pypi"
claude-manage exec project-c "cargo clean"
# Rebuild container from scratch
claude-manage rebuild project-a
```
**Disk space issues:**
```bash
# Check usage
df -h
# Clean Docker
docker system prune -a --volumes
docker volume prune
# Clean build artifacts
claude-manage exec project-a "turbo run clean"
claude-manage exec project-b "find /workspace -type d -name __pycache__ -exec rm -rf {} +"
claude-manage exec project-c "cargo clean"
```
---
## Security considerations
**Credential security:**
- Never commit `.env` files to git
- Restrict `~/.claude/.credentials.json` permissions: `chmod 600 ~/.claude/.credentials.json`
- Use SSH keys for git operations, not passwords
- Rotate Discord bot token if exposed
**Container isolation:**
- Each container has separate network namespace
- Firewall rules prevent unauthorized egress
- Containers run as non-root user (UID 1000)
- No privileged mode
**Discord permissions:**
- Use role-based access control
- Audit channel permissions monthly
- Restrict `/shell` and destructive commands to admins
- Enable 2FA for all Discord accounts
**Git security:**
- Always work on feature branches
- Require code review for main/master
- Use GPG signing for commits
- Never commit secrets or API keys
**VM hardening:**
```bash
# Setup UFW firewall
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw enable
# Disable password authentication
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# Setup fail2ban
sudo apt install fail2ban
sudo systemctl enable fail2ban
```
**Backup strategy:**
```bash
# Weekly full backup
0 2 * * 0 /home/youruser/claude-infra/manage.sh backup
# Keep last 4 weeks
find ~/claude-backups -type d -mtime +28 -exec rm -rf {} +
```
---
## Subscription usage optimization
**Usage limits by tier:**
- **Claude Pro**: Approximately 5x more usage than free tier, resets monthly
- **Claude Team**: Higher limits, usage pooled across team members
- **Usage tracked per subscription**, not per container
**Strategies to optimize usage:**
1. **Use planning mode first**:
```
/claude --mode plan "Design the new API endpoint structure"
```
Reviews design without making changes.
2. **Batch related tasks**:
```
/claude "Update all API endpoints to use the new error handling pattern, then update tests, then update documentation"
```
Single conversation vs. three separate ones.
3. **Use shell commands for simple operations**:
```
/shell grep -r "TODO" src/
/git log --oneline -10
```
Don't waste Claude on simple lookups.
4. **Enable auto-checkpoints**:
Prevents needing to regenerate work if you hit usage limits mid-task.
5. **Schedule heavy operations**:
Run large refactors at the start of your billing cycle when usage resets.
**Monitor usage per project:**
Create a tracking webhook:
```typescript
// Add to bot.ts
async function logUsage(project: string, prompt: string) {
await fetch('YOUR_TRACKING_ENDPOINT', {
method: 'POST',
body: JSON.stringify({
project,
timestamp: new Date(),
promptLength: prompt.length,
user: interaction.user.id
})
});
}
```
This helps identify which projects consume the most usage.
---
## Conclusion
This infrastructure provides production-grade multi-project Claude Code access with strong isolation, team collaboration, and subscription-based authentication. The multi-container architecture scales horizontally—add more projects by extending the docker-compose file and updating the bot's PROJECT_CONFIGS.
**Quick reference:**
```bash
# Management
claude-manage start # Start all containers
claude-manage status # Check system status
claude-manage logs project-a # View logs
claude-manage shell project-b # Open shell
claude-manage health # Run health checks
# Discord commands
/claude <prompt> # Main interaction
/status # Project status
/shell <command> # Execute shell
/git <args> # Git operations
/monorepo-info # Show structure
# Maintenance
claude /login # Re-authenticate (host)
claude-manage restart # Restart containers
claude-manage update # Update everything
```
For advanced configurations, refer to the individual project documentation:
- claudebox: https://github.com/RchGrav/claudebox
- claude-code-discord: https://github.com/zebbern/claude-code-discord