ChatOps Bot 🤖
A production-quality ChatOps bot that lets your engineering team manage infrastructure directly from Telegram — no VPN, no terminal needed.
What is ChatOps and why does it matter?
ChatOps is the practice of running operational commands through a chat interface. Instead of SSHing into a server or opening a dashboard, you type a human-readable message and the bot executes the right command.
Benefits:
- Visibility — everyone in the channel sees what's happening
- Auditability — every action is logged with who did it and when
- Speed — faster than opening a browser, navigating to a dashboard, clicking through menus
- Safety — destructive operations require explicit confirmation; only allowlisted users can act
Architecture
┌──────────────────────────────────────────────────────────────┐
│ Telegram App │
│ User types: "restart deployment api in prod" │
└─────────────────────────┬────────────────────────────────────┘
│ HTTPS (long-polling)
▼
┌──────────────────────────────────────────────────────────────┐
│ Telegram Bot API │
│ (api.telegram.org) │
└─────────────────────────┬────────────────────────────────────┘
│ python-telegram-bot
▼
┌──────────────────────────────────────────────────────────────┐
│ bot.py │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ is_allowed │ │ check_rate │ │ parse_intent │ │
│ │ (allowlist)│ │ _limit │ │ (NL → struct) │ │
│ └─────────────┘ └──────────────┘ └────────┬─────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────▼──────────┐ │
│ │ Confirmation Gate (destructive ops) │ │
│ └─────────────────────────────────────────────┬──────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────▼─────────┐ │
│ │ Command Executors │ │
│ │ kubectl get pods │ kubectl rollout restart │ df -h │ │
│ │ docker ps │ deploy.sh │ free -h │ │
│ └────────────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌────────────────────────────▼───────────────────────────┐ │
│ │ Audit Logger │ │
│ │ logs/audit.log (JSONL) │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
│
┌───────────────┼──────────────────┐
▼ ▼ ▼
Kubernetes Docker Engine Host OS
(kubectl) (/var/run/docker.sock) (df, free, top)
Security Model
| Layer | Mechanism |
|---|
| Identity | Static allowlist of Telegram user IDs in config.yaml |
| Rate limiting | Max N commands per user per hour (configurable) |
| Confirmation gate | Destructive ops (restart, deploy) require replying YES |
| Audit log | Every command attempt written to logs/audit.log as JSONL |
| Least privilege | Bot runs as non-root botuser inside Docker |
| Read-only mounts | config.yaml and kubeconfig mounted :ro |
Audit log format (one JSON object per line):
{"timestamp": "2024-01-15T12:34:56Z", "user_id": 123456789, "username": "alice",
"action": "restart_deployment", "command": "kubectl rollout restart deployment/api -n prod",
"result": "deployment.apps/api restarted", "success": true}
Supported Commands
All commands are natural language — no exact syntax required.
Kubernetes
| Example message | What it does |
|---|
show pods in production | kubectl get pods -n production -o wide |
list pods | kubectl get pods -n default -o wide |
list nodes | kubectl get nodes -o wide |
restart deployment api in staging ⚠️ | kubectl rollout restart deployment/api -n staging |
show logs for my-service | kubectl logs deployment/my-service --tail=50 --since=1h |
Docker
| Example message | What it does |
|---|
show docker containers | docker ps --format table ... |
list containers | same as above |
docker ps | same as above |
System
| Example message | What it does |
|---|
check disk usage | df -h |
check memory | free -h |
check cpu | top -bn1 |
Deployment
| Example message | What it does |
|---|
deploy version v1.2.3 to staging ⚠️ | Runs deploy_scripts/deploy.sh staging v1.2.3 |
deploy 2.0.0 to production ⚠️ | Runs deploy_scripts/deploy.sh production 2.0.0 |
⚠️ Destructive operations prompt for confirmation. Reply YES to proceed or anything else to cancel.
Setup Guide
1. Create a Telegram Bot
- Open Telegram and message @BotFather
- Send
/newbot
- Follow prompts — choose a name and username
- Copy the HTTP API token (looks like
110201543:AAHdqTcvCH1vGWJxfSeofSs4tq0ZEB1J6vfI)
2. Find Your Telegram User ID
Message @userinfobot on Telegram. It replies with your numeric user ID (e.g. 123456789).
3. Configure the Bot
cp config.yaml.example config.yaml
Edit config.yaml:
allowed_user_ids:
- 123456789 # replace with your real user ID
4. Set the Token
export TELEGRAM_BOT_TOKEN="your-token-here"
Or create a .env file:
echo "TELEGRAM_BOT_TOKEN=your-token-here" > .env
5. Run with Docker Compose
docker compose up -d
docker compose logs -f
6. Run Locally (without Docker)
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
TELEGRAM_BOT_TOKEN=your-token python bot.py
7. Test It
Open Telegram, message your bot:
/start
/help
check disk usage
show pods in default
Configuration Reference (config.yaml)
# List of Telegram user IDs permitted to use the bot.
# Everyone else gets "⛔ Unauthorized." All attempts are logged.
allowed_user_ids:
- 123456789
# How many commands a single user can run in a rolling 1-hour window.
rate_limit:
max_per_hour: 10
# Path to kubectl binary (default: kubectl, assumes it's in $PATH)
kubectl_path: kubectl
# kubectl context to use. Leave empty string "" to use the active context.
kubectl_context: ""
# Default namespace used when one isn't specified in the command.
default_namespace: default
# Map environment names to deploy script paths.
# The script receives: <script> <environment> <version>
deploy_scripts:
staging: "./deploy_scripts/deploy.sh"
production: "./deploy_scripts/deploy.sh"
default: "./deploy_scripts/deploy.sh"
# Directory for bot.log and audit.log
log_dir: ./logs
Extending with New Commands
Step 1 — Add a pattern to parse_intent() in bot.py:
# "check redis" / "redis info"
if re.search(r'(?:check\s+)?redis\s*(?:info|status)?', text_lower):
return {"action": "redis_info"}
Step 2 — Add an executor function:
def exec_redis_info() -> tuple[str, int]:
return run_command(["redis-cli", "info", "server"])
Step 3 — Add a handler branch in handle_message():
elif action == "redis_info":
await update.message.reply_text("🔴 Checking Redis...")
output, rc = exec_redis_info()
await update.message.reply_text(
f"🔴 *Redis Info:*\n```\n{truncate(output)}\n```",
parse_mode="Markdown",
)
audit_log(user_id, username, action, "redis-cli info", output, rc == 0)
Step 4 — Document it in cmd_help():
• `check redis` — Redis server info
That's it. No framework to learn, no registration step.
Running Tests
pip install pytest pytest-asyncio
pytest tests/ -v
Project Structure
chatops-bot/
├── bot.py # Core bot logic
├── config.yaml.example # Configuration template
├── config.yaml # Your config (git-ignored)
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── deploy_scripts/
│ └── deploy.sh # Deployment hook script
├── tests/
│ └── test_intent.py # Intent parser unit tests
├── logs/ # Created at runtime
│ ├── bot.log
│ └── audit.log
└── .github/
└── workflows/
└── ci.yml
License
MIT