A modular, persistent Docker environment for running AI agent CLI tools (GitHub Copilot CLI, Claude Code, Gemini) with SSH access, webhook API for automation, and headless Model Context Protocol (MCP) server support.
Create a docker-compose.yml file:
curl -O https://raw.githubusercontent.com/markoria/agent-cli-tools/main/docker-compose.yml
Start the container:
docker-compose up -d
Clone the repository:
git clone https://github.com/markoria/agent-cli-tools.git
cd agent-cli-tools
Build and start:
docker-compose up -d --build
For passwordless login, add your SSH public key:
Option A: Using .env file (Recommended)
Copy the example environment file:
cp .env.example .env
Edit .env and uncomment the SSH key line:
# Windows (PowerShell - use forward slashes)
SSH_PUBLIC_KEY_PATH=C:/Users/YourName/.ssh/id_rsa.pub
# Linux/macOS
SSH_PUBLIC_KEY_PATH=~/.ssh/id_rsa.pub
Uncomment the volume mount in docker-compose.yml:
volumes:
- ${SSH_PUBLIC_KEY_PATH}:/home/agent/.ssh/authorized_keys:ro
Restart the container:
docker-compose down
docker-compose up -d
Option B: Direct path in docker-compose.yml
Edit docker-compose.yml directly:
volumes:
- ~/.ssh/id_rsa.pub:/home/agent/.ssh/authorized_keys:ro
Generate SSH key if you don't have one:
ssh-keygen -t rsa -b 4096 -C "[email protected]"
With SSH key (passwordless):
ssh agent@localhost -p 2222
With password:
ssh agent@localhost -p 2222
# Password: agent
Once inside the container:
# GitHub Copilot (requires GitHub CLI authentication)
gh auth login
# Claude Code (requires API key)
export ANTHROPIC_API_KEY=sk-ant-xxxxx
# Or run: claude login
# Gemini CLI (Google OAuth or API key)
gemini auth login
# Or set: export GOOGLE_API_KEY=your-api-key
# GitHub Copilot CLI
github-copilot-cli
# Claude Code
claude
# Gemini CLI
gemini
docker pull markoria/agent-cli-tools:latest
If you prefer using docker run instead of docker-compose:
Basic usage (SSH only):
docker run -d \
--name agent-cli \
-p 2222:22 \
-e INSTALL_PACKAGES="@github/copilot @anthropic-ai/claude-code @google/gemini-cli" \
-v agent_data:/home/agent \
markoria/agent-cli-tools:latest
With webhook support:
docker run -d \
--name agent-cli \
-p 2222:22 \
-p 8080:8080 \
-e INSTALL_PACKAGES="@github/copilot @anthropic-ai/claude-code @google/gemini-cli" \
-e WEBHOOK_SECRET="$(openssl rand -hex 32)" \
-v agent_data:/home/agent \
markoria/agent-cli-tools:latest
Edit the INSTALL_PACKAGES and INSTALL_PIP_PACKAGES in docker-compose.ymlβ :
environment:
# NPM packages
- INSTALL_PACKAGES=@github/copilot @anthropic-ai/claude-code @google/gemini-cli
# Python packages (optional)
- INSTALL_PIP_PACKAGES=openai anthropic
Restart the container:
docker-compose restart agent-cli
The webhook server allows HTTP-based automation and integration with CI/CD pipelines, chatbots, and external services.
1. Generate a secure webhook secret:
# Linux/macOS
openssl rand -hex 32
# Windows (PowerShell)
-join ((1..32) | ForEach-Object { '{0:X2}' -f (Get-Random -Maximum 256) })
2. Add to your .env file:
WEBHOOK_SECRET=your_generated_secret_here
3. Restart the container:
docker-compose down
docker-compose up -d
The webhook server will be available at http://localhost:8080 with these endpoints:
POST /webhook/copilot - GitHub Copilot CLIPOST /webhook/claude - Claude CodePOST /webhook/gemini - Google Gemini CLIGET /health - Health check (no auth required)Example webhook request:
curl -X POST http://localhost:8080/webhook/copilot \
-H "Authorization: Bearer your_generated_secret_here" \
-H "Content-Type: application/json" \
-d '{
"prompt": "explain async/await in JavaScript",
"timeout": 30
}'
Response:
{
"success": true,
"output": "Async/await is a modern way to handle asynchronous operations...",
"agent": "copilot"
}
See Webhook Usage Examplesβ below for more use cases.
For tools requiring system-level installation, edit the Dockerfileβ :
# Example: Add Tailscale
RUN curl -fsSL https://tailscale.com/install.sh | sh
# Example: Add Docker CLI
RUN curl -fsSL https://get.docker.com | sh
Then rebuild:
docker-compose up -d --build
Add MCP servers to your environment in docker-compose.ymlβ :
environment:
- MCP_FILESYSTEM_URL=http://mcp-filesystem:8000/sse
- MCP_DATABASE_URL=http://mcp-database:8000/sse
- MCP_CUSTOM_URL=http://my-mcp-server:3000/sse
The entrypoint script will automatically generate the configuration for both GitHub Copilot and Claude.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Agent CLI Container β
β β
β βββββββββββββββββ ββββββββββββββ ββββββββββββββββββββββββ β
β β Webhook Serverβ β SSH Server β β Agent CLI Tools β β
β β (Go, :8080) β β (:22) β β - Copilot - Claude β β
β β β β β β - Gemini β β
β βββββββββ¬ββββββββ βββββββ¬βββββββ ββββββββββββ¬ββββββββββββ β
β β β β β
β β HTTP/REST β SSH β MCP/SSE β
β β β β β
ββββββββββββΌββββββββββββββββββΌββββββββββββββββββββββΌββββββββββββββββ
β β β
β β β
ββββββββββββββββ ββββββββββββ ββββββββββββββββββββ
β CI/CD β β Terminal β β MCP Servers β
β Webhooks β β User β β (Filesystem, DB) β
β Automation β β β β β
ββββββββββββββββ ββββββββββββ ββββββββββββββββββββ
Access Methods:
ssh agent@localhost -p 2222POST http://localhost:8080/webhook/{agent}
### Directory Structure
agent-cli-tools/ βββ Dockerfile # Multi-stage build (Go + Node.js) βββ webhook-server.go # Go HTTP server for webhooks βββ entrypoint.sh # Dynamic setup script βββ docker-compose.yml # Service orchestration βββ .env.example # Environment variables template βββ .dockerignore # Build optimization βββ README.md # This file
### Persistent Data
The `agent_data` volume is mounted to `/home/agent` and contains:
/home/agent/ βββ .npm-global/ # NPM packages (installed tools) βββ .local/ # Python packages βββ .config/ β βββ gh/ # GitHub CLI authentication β βββ github-copilot/ # Copilot CLI config & MCP settings β βββ claude-code/ # Claude Code config & MCP settings βββ .anthropic/ # Claude API credentials βββ .ssh/ # SSH keys and authorized_keys
## Advanced Usage
### Webhook Usage Examples
The webhook API enables powerful automation scenarios for AI agents.
#### 1. CI/CD Integration - Automated Code Reviews
**GitHub Actions workflow:**
```yaml
# .github/workflows/ai-review.yml
name: AI Code Review
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Get PR diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > changes.diff
DIFF=$(cat changes.diff)
echo "diff<<EOF" >> $GITHUB_OUTPUT
echo "$DIFF" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: AI Code Review
run: |
curl -X POST https://your-server.com:8080/webhook/copilot \
-H "Authorization: Bearer ${{ secrets.WEBHOOK_SECRET }}" \
-H "Content-Type: application/json" \
-d "{\"prompt\": \"Review this code and suggest improvements: ${{ steps.diff.outputs.diff }}\", \"timeout\": 120}"
Slash command bot (Node.js example):
// Slack bot that forwards requests to AI agents
app.command('/ask-copilot', async ({ command, ack, respond }) => {
await ack();
const response = await fetch('http://agent-cli:8080/webhook/copilot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.WEBHOOK_SECRET}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: command.text,
timeout: 60
})
});
const result = await response.json();
await respond({
text: result.success ? result.output : `Error: ${result.error}`
});
});
Daily code quality reports:
# crontab entry: Run every day at 9 AM
0 9 * * * curl -X POST http://localhost:8080/webhook/claude \
-H "Authorization: Bearer $(cat /secrets/webhook_token)" \
-H "Content-Type: application/json" \
-d '{"prompt":"Analyze /project/src for code quality issues and generate a report","timeout":300}' \
| jq '.output' | mail -s "Daily Code Quality Report" [email protected]
Auto-generate documentation when issues are closed:
# Flask webhook handler
@app.route('/jira-webhook', methods=['POST'])
def jira_webhook():
event = request.json
if event['webhookEvent'] == 'jira:issue_updated':
if event['issue']['fields']['status']['name'] == 'Done':
# Generate docs for completed feature
response = requests.post(
'http://agent-cli:8080/webhook/copilot',
headers={'Authorization': f'Bearer {WEBHOOK_SECRET}'},
json={
'prompt': f"Generate user documentation for: {event['issue']['fields']['summary']}",
'timeout': 120
}
)
# Post docs back to Jira
docs = response.json()['output']
update_jira_comment(event['issue']['key'], docs)
return '', 200
Expose AI agents to microservices:
# docker-compose.yml - Add nginx reverse proxy
services:
nginx:
image: nginx:alpine
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- agent-cli
agent-cli:
# ... existing config
ports:
- "8080" # Internal only, accessed via nginx
Nginx config with rate limiting:
# nginx.conf
http {
limit_req_zone $binary_remote_addr zone=ai_agents:10m rate=10r/m;
server {
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
location /api/ai/ {
limit_req zone=ai_agents burst=5;
rewrite ^/api/ai/(.*)$ /webhook/$1 break;
proxy_pass http://agent-cli:8080;
proxy_set_header Authorization "Bearer ${WEBHOOK_SECRET}";
}
}
}
AWS Lambda + EventBridge:
# lambda_function.py
import boto3
import requests
import os
def lambda_handler(event, context):
"""Trigger AI agent on S3 file upload"""
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
if key.endswith('.py'):
# Analyze newly uploaded Python file
response = requests.post(
os.environ['AGENT_WEBHOOK_URL'] + '/webhook/claude',
headers={'Authorization': f"Bearer {os.environ['WEBHOOK_SECRET']}"},
json={
'prompt': f'Review this Python file for security issues: s3://{bucket}/{key}',
'timeout': 90
}
)
result = response.json()
# Send to SNS if issues found
if 'security' in result['output'].lower():
sns = boto3.client('sns')
sns.publish(
TopicArn=os.environ['ALERT_TOPIC'],
Subject='Security Review Alert',
Message=result['output']
)
return {'statusCode': 200}
Basic request:
curl -X POST http://localhost:8080/webhook/copilot \
-H "Authorization: Bearer your_secret_token" \
-H "Content-Type: application/json" \
-d '{"prompt":"explain docker multi-stage builds"}'
With custom timeout:
curl -X POST http://localhost:8080/webhook/claude \
-H "Authorization: Bearer your_secret_token" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Refactor this 1000-line function",
"timeout": 180
}'
Health check:
curl http://localhost:8080/health
# Response: {"status":"healthy","service":"agent-webhook-server"}
Error handling:
# Invalid token
curl -X POST http://localhost:8080/webhook/copilot \
-H "Authorization: Bearer wrong_token" \
-H "Content-Type: application/json" \
-d '{"prompt":"test"}'
# Response: 401 Unauthorized
# Missing prompt
curl -X POST http://localhost:8080/webhook/copilot \
-H "Authorization: Bearer your_secret_token" \
-H "Content-Type: application/json" \
-d '{}'
# Response: {"success":false,"error":"Prompt is required","agent":"copilot"}
environment:
- CHECK_UPDATES=true
Create a startup.sh script and mount it:
volumes:
- ./startup.sh:/home/agent/startup.sh
Then modify entrypoint.sh to execute it before starting SSH.
Edit Dockerfile:
RUN curl -fsSL https://tailscale.com/install.sh | sh
Edit docker-compose.yml:
cap_add:
- NET_ADMIN
environment:
- TAILSCALE_AUTH_KEY=tskey-auth-xxxxx
WEBHOOK_SECRET| Webhook authentication token |$(openssl rand -hex 32)| |WEBHOOK_PORT| Webhook server port |8080(default) | |Edit entrypoint.sh (add before SSH start):
if [ ! -z "$TAILSCALE_AUTH_KEY" ]; then
tailscaled --tun=userspace-networking &
sleep 2
tailscale up --authkey=$TAILSCALE_AUTH_KEY
fi
| Variable | Purpose | Example |
|---|---|---|
INSTALL_PACKAGES | NPM packages to install | @github/copilot @anthropic-ai/claude-code @google/gemini-cli |
INSTALL_PIP_PACKAGES | Python packages to install | openai anthropic |
CHECK_UPDATES | Auto-update on startup | true or false |
MCP_*_URL | MCP server endpoints | MCP_FILESYSTEM_URL=http://mcp-fs:8000/sse |
ANTHROPIC_API_KEY | Claude API key | sk-ant-xxxxx |
GITHUB_TOKEN | GitHub token (alternative to gh auth) | ghp_xxxxx |
| `GWebhook returns 401 Unauthorized? |
Verify your WEBHOOK_SECRET matches:
# Check if secret is set
docker exec -it agent-cli env | grep WEBHOOK_SECRET
# Test with correct token
curl -X POST http://localhost:8080/webhook/copilot \
-H "Authorization: Bearer YOUR_SECRET_HERE" \
-H "Content-Type: application/json" \
-d '{"prompt":"test"}'
Check webhook server logs:
docker exec -it agent-cli cat /var/log/webhook-server.log
If WEBHOOK_SECRET is not set, the webhook server will not start (this is intentional).
The agent_data volume persists between restarts but not rebuilds. To preserve:
# Don't use --build flag when restarting
docker-compose restart
Verify the MCP configuration was generated:
docker exec -it agent-cli cat /home/agent/.config/github-copilot/mcp-config.json
Increase the timeout in your request payload:
{
"prompt": "complex task here",
"timeout": 180
}
Maximum timeout is 300 seconds (5 minutes).ure the container is running:
docker-compose ps
The agent_data volume persists between restarts but not rebuilds. To preserve:
# Don't use --build flag when restarting
docker-compose restart
Verify the MCP configuration was generated:
docker exec -it agent-cli cat /home/agent/.config/github-copilot/mcp-config.json
MIT
Feel free to submit issues or pull requests for improvements!
Content type
Image
Digest
sha256:b6469fc38β¦
Size
457.3 MB
Last updated
10 months ago
docker pull markoria/agent-cli-tools