Harbor
AI-first deployment manager for KAMAL and Coolify. Manage multiple Rails apps across VPS instances from one CLI, and give LLM agents (Claude Code, Cursor, etc.) full deploy/rollback/logs/exec access to your fleet via MCP.
Harbor is backend-agnostic. Use KAMAL for lightweight deploys with no platform dependency, Coolify for a managed dashboard experience, or mix both in the same fleet. Same tools, same slash commands, different engines.
Why
You have 4-10 Rails apps deployed across multiple servers. Today, managing them means context-switching between project directories, remembering which server runs what, and manually relaying information to your AI coding assistant.
Harbor fixes this:
harbor logs myapp
harbor deploy api
harbor rollback admin abc123
And more importantly, your LLM agent can do it too:
You: "deploy the api and check if it's healthy"
Agent: harbor_deploy(project: "api")
Agent: harbor_get_logs(project: "api", lines: 20, grep: "ERROR")
Agent: "Deployed successfully. No errors in the last 20 log lines."
Install
gem install harbor-deploy
Requires Ruby >= 3.1. For the KAMAL backend, you also need KAMAL >= 2.11. The Coolify backend only needs an API token.
Quickstart
1. Register your projects
# KAMAL-deployed projects
harbor add myapp ~/src/myapp
harbor add api ~/src/api-service
# Or auto-detect KAMAL projects in common directories
harbor setup
harbor add validates that the project has a config/deploy.yml and auto-detects destinations (staging, production) from config/deploy.*.yml files.
2. Verify
harbor list
Projects:
myapp (production, staging) - Main customer app
/home/cole/src/myapp
api (production)
/home/cole/src/api-service
3. Configure MCP for your AI agent
Add to your Claude Code settings (.claude/settings.json):
{
"mcpServers": {
"harbor": {
"command": "harbor",
"args": ["mcp"]
}
}
}
For Cursor, add to .cursor/mcp.json. Any MCP-compatible client works.
4. Use it
From the CLI:
harbor status myapp
harbor deploy myapp -d production
harbor logs api -n 50 --grep ERROR
harbor runner myapp "puts User.count"
From your LLM agent (it calls these automatically):
"deploy myapp to production"
"check the api logs for errors"
"how many users signed up today?"
"roll back admin to the previous version"
Backends
Harbor supports two deployment backends. Configure per-project in ~/.harbor/config.yml.
KAMAL (default)
Deploys via SSH using Docker containers and kamal-proxy. No platform dependency, no external service. Uses local Docker registry (no Docker Hub needed).
projects:
myapp:
path: ~/src/myapp
backend: kamal # default, can omit
Coolify
Deploys via the Coolify API. Works with Coolify Cloud or self-hosted instances. Get your API token from Coolify dashboard under Keys & Tokens.
projects:
cloud-app:
path: ~/src/cloud-app
backend: coolify
coolify:
coolify_url: https://app.coolify.io
coolify_token: "your-api-token"
coolify_app_uuid: "your-app-uuid"
Find your app UUID in Coolify dashboard URL or via:
curl -s https://app.coolify.io/api/v1/applications \
-H "Authorization: Bearer YOUR_TOKEN" | python3 -m json.tool
Mixed fleet
Run both backends in the same fleet. Same MCP tools, same slash commands:
projects:
fleet-app:
path: ~/src/fleet
backend: kamal
description: "KAMAL deploy, local registry"
cloud-app:
path: ~/src/cloud
backend: coolify
coolify:
coolify_url: https://app.coolify.io
coolify_token: "your-token"
coolify_app_uuid: "app-uuid"
description: "Coolify managed"
harbor deploy fleet-app # → kamal deploy (SSH + Docker)
harbor deploy cloud-app # → Coolify API deploy
harbor logs fleet-app # → kamal app logs
harbor logs cloud-app # → Coolify API /applications/{uuid}/logs
Your team doesn't need to know which backend is running. They just type /deploy.
CLI Reference
Project Management
harbor add NAME PATH # Register a KAMAL project
harbor add myapp ~/src/myapp -d production,staging
harbor remove NAME # Unregister a project
harbor list # List all registered projects
harbor setup # Interactive quickstart: auto-detect projects,
# validate config, output MCP settings
Deploy & Rollback
harbor deploy PROJECT # Deploy (uses default destination)
harbor deploy myapp -d staging # Deploy to specific destination
harbor deploy myapp --skip-push # Deploy without rebuilding image
harbor rollback PROJECT VERSION # Rollback to a git SHA
harbor rollback myapp abc1234 -d production
Logs
harbor logs PROJECT # Last 100 lines
harbor logs myapp -n 500 # Last 500 lines
harbor logs myapp --grep ERROR # Filter by pattern
harbor logs myapp -r workers # Specific role
harbor logs myapp -f # Follow (stream live, KAMAL only)
Execute Commands
harbor exec PROJECT "CMD" # Run a command in the container
harbor exec myapp "cat log/production.log | tail -50"
harbor exec api "printenv" -r workers
Rails Commands
harbor rails PROJECT console # Interactive rails console (TTY, KAMAL only)
harbor rails PROJECT dbconsole # Interactive database console (TTY, KAMAL only)
harbor rails PROJECT migrate # Run migrations
harbor rails PROJECT migrate:status
harbor rails PROJECT routes # Show routes
harbor rails PROJECT routes users # Grep routes
harbor rails PROJECT seed # Seed database
harbor rails PROJECT rollback # Rollback last migration
harbor runner PROJECT "SCRIPT" # Run Ruby code via rails runner
harbor runner myapp "puts User.count"
harbor runner myapp "pp Order.last(5).pluck(:id, :status)"
Status & Details
harbor status # Status of all projects
harbor status myapp # Status of one project
harbor details myapp # Full container + proxy details
harbor audit # Deployment history (all projects)
harbor audit myapp -n 50 # Last 50 entries for one project
Maintenance Mode
harbor maintenance myapp on # Enable (stops routing / stops app)
harbor maintenance myapp off # Disable (resume traffic)
MCP Server
harbor mcp # Start MCP server (stdio, for AI agents)
MCP Tools
When connected via MCP, your AI agent has access to 19 tools:
| Tool | What it does | Safety |
|---|---|---|
harbor_add_project |
Register a project with Harbor | write |
harbor_remove_project |
Unregister a project | write |
harbor_setup_kamal |
Initialize KAMAL config in a project | write |
harbor_setup_autodeploy |
Set up deploy-on-merge (webhook on server) | write |
harbor_list_projects |
List all registered projects | read-only |
harbor_project_status |
Container status for a project | read-only |
harbor_deploy |
Deploy a project | write, audited, locked |
harbor_rollback |
Rollback to a specific version | write, audited, locked |
harbor_get_logs |
Get recent app logs | read-only |
harbor_exec |
Execute a command in the container | write, audited |
harbor_runner |
Execute Ruby code via rails runner |
write, audited |
harbor_rails |
Run Rails commands (migrate, routes, seed, etc.) | write, audited |
harbor_details |
Container and proxy details | read-only |
harbor_audit |
View deployment history | read-only |
harbor_list_servers |
List servers across projects | read-only |
harbor_maintenance |
Toggle maintenance mode | write, audited |
harbor_app_version |
Get current deployed version | read-only |
harbor_ci |
Run local CI suite (bin/ci) | read-only |
harbor_ship |
Full pipeline: CI, review, PR, merge, deploy | write, audited |
All tools work with both KAMAL and Coolify backends.
Ship Pipeline
The harbor_ship tool (or /ship slash command) runs the full pipeline:
Step 1: Local CI → bin/ci (tests, rubocop, brakeman, bundler-audit)
Step 2: Local review → CodeRabbit CLI or Codex (seconds, no push needed)
Step 3: Push + create PR
Step 4: Async review → CodeRabbit cloud / teammates approve
Step 5: Merge PR
Step 6: Deploy → KAMAL or Coolify (based on backend config)
Step 7: Verify → check logs for errors
By default, deploy_on_merge is true. Merge to main triggers deployment automatically via GitHub Actions (KAMAL) or Coolify's built-in auto-deploy.
Auto-Deploy on Merge
With Coolify
Coolify handles this natively. Enable auto-deploy in your Coolify app settings or via API. No extra setup needed.
With KAMAL
Two options:
Option A: GitHub Actions (add 3 secrets to your repo)
Harbor generates a .github/workflows/deploy.yml that runs kamal deploy on push to main. Requires SSH_PRIVATE_KEY, DEPLOY_HOST, and RAILS_MASTER_KEY as repo secrets.
Option B: Server webhook (zero secrets, Coolify-style)
harbor_setup_autodeploy(project: "myapp")
One MCP call. SSHes into your server, clones the repo, installs a webhook service, and configures the GitHub webhook. The server deploys to itself on push to main. No GitHub secrets needed.
Configuration
Harbor stores its config at ~/.harbor/config.yml:
version: 1
projects:
# KAMAL backend
myapp:
path: /home/cole/src/myapp
destinations:
- production
- staging
default_destination: production
description: "Main customer-facing app"
allowed_operations: # optional, omit to allow all
- deploy
- rollback
- logs
- exec
allowed_exec_commands: # optional, omit to allow all
- "rails runner *"
- "rails db:migrate"
- "cat log/*"
# Coolify backend
cloud-app:
path: /home/cole/src/cloud-app
backend: coolify
coolify:
coolify_url: https://app.coolify.io
coolify_token: "your-api-token"
coolify_app_uuid: "your-app-uuid"
settings:
lock_timeout: 1800 # 30 min, seconds before deploy lock auto-expires
deploy_timeout: 600 # 10 min, max seconds for a deploy operation
exec_timeout: 120 # 2 min, max seconds for exec/runner
logs_timeout: 30 # 30 sec, max seconds for log retrieval
audit_db: ~/.harbor/audit.db
lock_dir: ~/.harbor/locks
Operation Allowlists
Restrict what an AI agent can do per project:
projects:
production-app:
path: /home/cole/src/prod
allowed_operations:
- logs
- status
- details
- audit
# No deploy, rollback, exec, or maintenance — read-only for this project
Exec Command Safety
Commands are parsed into argv arrays. Shell metacharacters are always rejected:
; | & ` $ ( ) { } > < \n \
This prevents injection regardless of allowlist configuration. When allowed_exec_commands is set, only matching commands are permitted.
Safety
Harbor is designed for AI agents deploying to production. Safety is not optional.
Audit Log
Every write operation (deploy, rollback, exec, runner, maintenance) is logged in a SQLite database at ~/.harbor/audit.db:
harbor audit
Timestamp Project Operation Dest Status Duration
-------------------- ------------ ------------ ---------- -------- --------
2026-04-06T02:30:00 myapp deploy production success 45200ms
2026-04-06T02:28:00 api runner production success 1200ms
2026-04-06T02:25:00 myapp rollback production success 12000ms
Each entry records whether it was triggered by cli or mcp, so you always know if a human or an agent took the action.
Deploy Locks
Concurrent deploys to the same project and destination are prevented. The lock includes:
- PID of the process holding the lock
- Timestamp (auto-expires after 30 minutes)
- Initiator (cli or mcp)
- Stale lock detection (checks if PID is still alive)
Shell Injection Prevention
The exec and runner tools reject all shell metacharacters at the parsing layer. Commands are split into argv arrays, never passed through a shell interpreter. This applies to both CLI and MCP usage.
Architecture
┌─────────────────────────────────────────────────────────┐
│ harbor (gem) │
│ │
│ ┌──────────┐ ┌───────────────┐ ┌───────────────────┐ │
│ │ CLI │ │ MCP Server │ │ Audit Log │ │
│ │ (Thor) │ │ (stdio) │ │ (SQLite) │ │
│ └────┬─────┘ └───────┬───────┘ └────────┬──────────┘ │
│ │ │ │ │
│ └────────┬───────┘ │ │
│ │ │ │
│ ┌───────▼──────────┐ │ │
│ │ project.adapter ├──────────────────┘ │
│ └──┬──────────┬────┘ │
│ │ │ │
│ ┌───────▼──────┐ ┌▼──────────────┐ │
│ │ KamalAdapter │ │ CoolifyAdapter│ │
│ │ │ │ │ │
│ │ SSH + Docker │ │ REST API │ │
│ │ subprocess │ │ HTTPS │ │
│ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │
└──────────┼─────────────────┼──────────────────────────────┘
│ │
┌───────▼──────┐ ┌──────▼───────┐
│ Remote Hosts │ │ Coolify API │
│ (SSH+Docker) │ │ (Cloud/Self) │
└──────────────┘ └──────────────┘
How the adapters work
KAMAL backend:
- Read operations (status, logs, details): KAMAL Ruby API for structured data
- Write operations (deploy, rollback):
kamalCLI subprocess (battle-tested orchestration) - Exec/runner:
kamal app execsubprocess
Coolify backend:
- All operations go through the Coolify REST API
- Deploy:
GET /api/v1/deploy?uuid=X - Logs:
GET /api/v1/applications/{uuid}/logs - Exec:
POST /api/v1/servers/{uuid}/command(viadocker exec) - Status:
GET /api/v1/applications/{uuid}
Slash Commands (Claude Code Skills)
Add these skills to your project's .claude/skills/ directory for zero-friction team adoption:
| Command | What it does |
|---|---|
/harbor-ship |
Full pipeline: CI, review, PR, merge, deploy, verify |
/harbor-ci |
Run local CI suite (bin/ci) |
/harbor-deploy |
Deploy current version to production |
/harbor-logs |
Check production logs |
/harbor-status |
Check what's running in production |
/harbor-setup |
First-time setup for new teammates |
New teammate onboarding
# 1. Clone the repo (skills come with it)
git clone <repo> && cd myapp
# 2. Install Harbor
gem install harbor-deploy
# 3. Open Claude Code, type /harbor-setup
# It configures everything automatically
Development
git clone https://github.com/cole-robertson/harbor
cd harbor
bundle install
bundle exec rspec # 136 tests
Project Structure
lib/harbor/
├── version.rb # Version constant
├── config.rb # ~/.harbor/config.yml management
├── project.rb # Project model + backend selection + exec safety
├── kamal_adapter.rb # KAMAL CLI wrapper with ENV/Dir safety
├── coolify_adapter.rb # Coolify REST API adapter
├── audit_log.rb # SQLite WAL audit trail
├── deploy_lock.rb # File-based deploy locking
├── cli.rb # Thor CLI entry point
├── cli/
│ ├── projects.rb # add, remove, list, setup
│ ├── deploy.rb # deploy, rollback, exec, maintenance
│ ├── logs.rb # logs with follow support
│ ├── rails.rb # Rails commands + interactive console
│ ├── servers.rb # status, details, audit
│ └── mcp_command.rb # harbor mcp entry point
└── mcp/
├── server.rb # JSON-RPC over stdio
└── tools.rb # 19 MCP tool definitions + handlers
Not Supported (v1)
- KAMAL accessories (Redis, Postgres) management
- Web dashboard or UI
- Multi-user RBAC / policy engine
- Notification hooks (Slack, webhook)
- Auto-rollback on health check failure
- Project auto-discovery scanning
These are tracked for v2 based on real usage.
License
MIT