basecamp/kamal
Deploy web apps anywhere.
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 2w ago
- ✓14 active contributors
- ✓MIT licensed
Show 3 more →Show less
- ✓CI configured
- ✓Tests present
- ⚠Single-maintainer risk — top contributor 80% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/basecamp/kamal)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/basecamp/kamal on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: basecamp/kamal
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/basecamp/kamal shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 2w ago
- 14 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Single-maintainer risk — top contributor 80% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live basecamp/kamal
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/basecamp/kamal.
What it runs against: a local clone of basecamp/kamal — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in basecamp/kamal | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 41 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of basecamp/kamal. If you don't
# have one yet, run these first:
#
# git clone https://github.com/basecamp/kamal.git
# cd kamal
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of basecamp/kamal and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "basecamp/kamal(\\.git)?\\b" \\
&& ok "origin remote is basecamp/kamal" \\
|| miss "origin remote is not basecamp/kamal (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "lib/kamal/cli.rb" \\
&& ok "lib/kamal/cli.rb" \\
|| miss "missing critical file: lib/kamal/cli.rb"
test -f "lib/kamal/configuration.rb" \\
&& ok "lib/kamal/configuration.rb" \\
|| miss "missing critical file: lib/kamal/configuration.rb"
test -f "lib/kamal/commander.rb" \\
&& ok "lib/kamal/commander.rb" \\
|| miss "missing critical file: lib/kamal/commander.rb"
test -f "lib/kamal/commands/builder.rb" \\
&& ok "lib/kamal/commands/builder.rb" \\
|| miss "missing critical file: lib/kamal/commands/builder.rb"
test -f "lib/kamal/commands/app.rb" \\
&& ok "lib/kamal/commands/app.rb" \\
|| miss "missing critical file: lib/kamal/commands/app.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 41 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~11d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/basecamp/kamal"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Kamal is a zero-downtime deployment tool written in Ruby that orchestrates containerized web applications across any infrastructure—bare metal, cloud VMs, or multiple servers. It uses kamal-proxy to seamlessly switch traffic between containers during deployments and coordinates execution via SSHKit, making it language-agnostic despite being built for Rails. CLI-first monolith structure: lib/kamal/cli/ contains command hierarchy (app, build, proxy, server, accessory, etc.) with shared base in lib/kamal/cli/base.rb, health check logic in lib/kamal/cli/healthcheck/, and a binary entrypoint at bin/kamal. Configuration uses deploy.yml templates in lib/kamal/cli/templates/. Hooks system supports pre/post-deploy scripts.
👥Who it's for
DevOps engineers and full-stack developers deploying web applications (especially Rails apps) who need a self-hosted, infrastructure-agnostic deployment solution without the complexity of Kubernetes, and who want to manage deployments from a single CLI tool via SSH.
🌱Maturity & risk
Actively developed and production-ready. The codebase shows 699KB of Ruby code with comprehensive test coverage implied by CI workflows (.github/workflows/ci.yml), a detailed contributing guide, and regular maintenance. Originally built by Basecamp for their own infrastructure needs, indicating real-world battle-testing.
Risk is moderate and well-managed. The project is single-maintainer-focused (Basecamp-driven) but published MIT-licensed and community-contributed-to. Dependency surface area is small (minimal Gemfile apparent), and the relatively specialized domain (deployment tooling) means breaking changes are managed carefully. Main risk: relies on external kamal-proxy service for zero-downtime switching.
Active areas of work
Active maintenance cycle with CI/CD automation via GitHub Actions (docker-publish.yml, ci.yml), Dependabot integration for dependency updates, and ongoing development on core deployment features (app boot, proxy management, healthchecks, SSL certificates handling). No visible breaking changes indicated in file structure.
🚀Get running
git clone https://github.com/basecamp/kamal.git
cd kamal
bundle install
bundle exec bin/kamal --version
Daily commands:
Kamal is a deployment tool, not a runnable service. Execute via: bundle exec bin/kamal deploy (requires configured deploy.yml with target servers). Local development testing uses bin/test for test suite.
🗺️Map of the codebase
lib/kamal/cli.rb— Main CLI entry point that delegates to subcommands; essential for understanding how all user interactions flow through the system.lib/kamal/configuration.rb— Core configuration loader and validator; every deployment depends on parsing and validating the deploy.yml structure.lib/kamal/commander.rb— Orchestrates multi-server deployments by executing commands via SSHKit; the central deployment execution engine.lib/kamal/commands/builder.rb— Abstracts Docker build strategies (local, remote, cloud, hybrid); critical for understanding the build pipeline.lib/kamal/commands/app.rb— Generates Docker and container management commands for the main app; foundational for app lifecycle operations.lib/kamal/cli/base.rb— Base class for all CLI commands with common configuration and logging; defines the command execution pattern.lib/kamal/commands/proxy.rb— Manages kamal-proxy configuration and zero-downtime traffic switching; core to the unique deployment strategy.
🛠️How to make changes
Add a new app-level CLI command
- Create a new command class inheriting from
Kamal::Cli::Baseinlib/kamal/cli/app/subdirectory (lib/kamal/cli/app/*.rb) - Register the command in the app command group definition (
lib/kamal/cli/app.rb) - Implement the command method that calls underlying
Kamal::Commands::Appmethods or generates custom commands (lib/kamal/commands/app.rb)
Add a new Docker build strategy
- Create a new builder strategy class inheriting from
Kamal::Commands::Builder::Base(lib/kamal/commands/builder/*.rb) - Implement
build_commandmethod that returns shell commands for the strategy (lib/kamal/commands/builder/*.rb) - Register the strategy in the builder dispatcher logic (
lib/kamal/commands/builder.rb) - Add configuration validation in builder configuration class (
lib/kamal/configuration/builder.rb)
Add support for a new deployment hook point
- Define the hook name constant and add it to the hook execution flow (
lib/kamal/commands/hook.rb) - Call the hook at the appropriate point in the orchestration flow (
lib/kamal/commander.rb) - Add sample hook template to the generated config (
lib/kamal/cli/templates/sample_hooks/)
Add a new accessory service (Redis, Postgres, etc.)
- Define accessory configuration in
deploy.ymlwith image, port, and environment variables (lib/kamal/cli/templates/deploy.yml) - Accessory commands are auto-generated from
Kamal::Commands::Accessoryusing the config (lib/kamal/commands/accessory.rb) - Orchestrator picks up the accessory in boot/deploy flows automatically via configuration (
lib/kamal/commander.rb)
🔧Why these technologies
- Thor (CLI framework) — Provides convention-based CLI routing, help generation, and argument parsing for complex multi-level command structure.
- SSHKit — Abstracts SSH execution across multiple servers in parallel with session management and logging.
- Docker — Container standard for packaging apps; Kamal generates Docker commands rather than embedding Docker client library.
- kamal-proxy — Custom reverse proxy (built by Basecamp) enabling zero-downtime deployments via traffic switching between containers.
- Ruby + ERB — Dynamic command generation and template rendering for multi-strategy build support and flexible deployment orchestration.
⚖️Trade-offs already made
-
Command generation (building shell strings) rather than embedding Docker/SSH client libraries
- Why: Simpler dependency management, easier to debug (see exact commands executed), works with any SSH target without driver installation.
- Consequence: Must carefully construct and quote shell commands; fewer type-safe guarantees at compile time.
-
Multiple builder strategies (local, remote, cloud, hybrid) rather than single uniform approach
- Why: Accommodates diverse deployment scenarios: CI systems may build locally, enterprises may use remote builders, cloud users prefer managed builders.
- Consequence: Added complexity in configuration and conditional logic; developers must understand their chosen strategy.
-
Configuration-first design with YAML deploy.yml rather than imperative API
- Why: Enables version control, auditing, and simplicity for non-programmatic use; natural for ops workflows.
- Consequence: Less flexible for dynamic scenarios; configuration cannot easily depend on runtime state.
-
Rely on external kamal-proxy for zero-downtime switching rather than built-in load balancer
- Why: Separates concerns; kamal-proxy can be deployed independently and updated without Kamal release cycles.
- Consequence: Additional component to manage; proxy must be pre-bootstrapped on servers.
🚫Non-goals (don't propose these)
- Does not provide container orchestration at the Kubernetes level—targets single-machine or small multi-machine deployments
- Does not handle application-level service discovery or load balancing (defers to kamal-proxy)
- Does not manage SSL certificate provisioning (assumes certificates are pre-loaded or provided via hooks)
- Does not provide real-time log aggregation (supports viewing logs from individual containers only)
- Does not enforce authentication or authorization—relies on SSH key management for access control
- Does not manage persistent data volumes across deployments (app is expected to handle data concerns)
🪤Traps & gotchas
SSH key configuration required (no password auth shown); assumes Docker and Docker Compose present on target servers; kamal-proxy must run separately and be configured in deploy.yml; deploy.yml is mandatory and path conventions matter (env vars and secrets loaded from config, not .env); hook scripts must be executable and in specific paths (lib/kamal/cli/templates/sample_hooks/); health check barrier waits for container readiness before switching traffic.
🏗️Architecture
💡Concepts to learn
- Zero-downtime deployment — Kamal's core value proposition; understanding request switching semantics between container versions is essential to using and extending it safely.
- Health check barriers / polling — lib/kamal/cli/healthcheck/barrier.rb and poller.rb implement blocking waits for container readiness; understanding this prevents deploying unhealthy containers.
- SSH multiplexing via SSHKit — Kamal uses SSHKit to execute commands on remote servers; understanding connection pooling and command batching avoids N+1 SSH connections during large deployments.
- Container registry authentication — lib/kamal/cli/registry.rb manages Docker registry credentials for private images; deployment failures often stem from auth issues.
- Blue-green deployments — Kamal implements blue-green semantics via kamal-proxy; old and new container versions coexist briefly, with traffic cut over atomically.
- Hook scripting / lifecycle callbacks — Pre and post-deploy hooks (lib/kamal/cli/templates/sample_hooks/) allow custom logic injection; understanding execution order prevents race conditions.
- Secrets management via environment files — Kamal loads secrets from configured sources; lib/kamal/cli/secrets.rb handles credential injection into containers—mismanagement exposes credentials in logs.
🔗Related repos
basecamp/kamal-proxy— The load balancer companion that performs zero-downtime request switching between containers during deployments—Kamal depends on this for its core differentiator.capistrano/capistrano— Historical predecessor deployment tool for Rails; Kamal modernizes this approach for containerized workloads with less configuration boilerplate.docker/compose— Docker Compose is referenced implicitly; Kamal orchestrates multi-container deployments similar to compose but across remote servers with zero downtime.mina-deploy/mina— Lightweight alternative deployment tool for Ruby apps; Kamal targets the same use case but with Docker-first, multi-server focus.basecamp/kamal-site— Official documentation repository; contributors to Kamal code should also check here for docs that need updating alongside implementation changes.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive unit tests for lib/kamal/commands/app/execution.rb
The execution.rb file handles critical app container execution logic, but there's no visible test file for it in the repo structure. Given that Kamal is a deployment tool where execution correctness is critical, this file needs robust test coverage to prevent regressions in container execution commands.
- [ ] Create test/kamal/commands/app/execution_test.rb with test cases for command generation
- [ ] Add tests for various execution scenarios (with/without environment variables, with/without volumes)
- [ ] Add edge case tests (empty commands, special characters, privilege escalation scenarios)
- [ ] Ensure tests follow the pattern established in existing test files for other command builders
Add integration tests for healthcheck polling in lib/kamal/cli/healthcheck/poller.rb
The healthcheck poller is responsible for verifying deployments succeeded, but there are no visible test files covering the polling logic, timing, and retry behavior. This is critical deployment functionality that needs thorough testing to prevent false-positive deployment completions.
- [ ] Create test/kamal/cli/healthcheck/poller_test.rb with tests for polling intervals and timeouts
- [ ] Add tests for successful polling scenarios and failure/timeout scenarios
- [ ] Test interaction with lib/kamal/cli/healthcheck/barrier.rb for coordination
- [ ] Add tests for the error handling in lib/kamal/cli/healthcheck/error.rb
Add tests for port forwarding logic in lib/kamal/cli/build/port_forwarding.rb
Port forwarding during Docker builds is a specialized feature, and the port_forwarding.rb module lacks visible test coverage. Given its role in supporting remote builds with local dependencies, incorrect port forwarding could break entire build pipelines.
- [ ] Create test/kamal/cli/build/port_forwarding_test.rb with unit tests
- [ ] Add tests for port allocation, tunnel setup, and cleanup logic
- [ ] Test edge cases: unavailable ports, tunnel failures, cleanup on interruption
- [ ] Verify integration with lib/kamal/cli/build/clone.rb build context
🌿Good first issues
- Add test coverage for lib/kamal/cli/app/ssl_certificates.rb—the file exists but no obvious test parallel in the repo structure suggests testing gaps for SSL-specific deployment logic.
- Enhance error messages in lib/kamal/cli/healthcheck/error.rb with specific remediation steps (e.g., what to check if container fails health checks) instead of generic errors.
- Document the hook execution order and available environment variables in sample_hooks files; currently only filenames like pre-app-boot.sample exist with minimal guidance on what they receive.
⭐Top contributors
Click to expand
Top contributors
- @djmb — 80 commits
- @lewispb — 5 commits
- @dependabot[bot] — 2 commits
- @9c23a5 — 2 commits
- @jeremy — 2 commits
📝Recent commits
Click to expand
Recent commits
6a31d14— Merge pull request #1803 from basecamp/otel-deploy-logs (djmb)589688d— Add output logging framework with OTel and file backends (lewispb)5e5eab9— ci: harden GitHub Actions workflows (#1805) (flavorjones)453d8d7— Bump version for 2.11.0 (djmb)56de070— Fix alias doc validation (djmb)89c0d32— Doc change for Kamal 2.11 (djmb)9c6252d— Rubocop fixes (djmb)7a05140— Split the commands (djmb)a070fe7— Merge pull request #1095 from brightbox/fix-980 (djmb)3a68543— Merge pull request #1767 from ron-shinall/fix-typo (djmb)
🔒Security observations
- High · Incomplete Docker WORKDIR Documentation —
Dockerfile (final lines). The Dockerfile has an incomplete comment at the end ('Tell git it's safe to access /workdir/.git even if the directory is owned b') which suggests either a configuration incomplete or potential security guidance not fully documented. This may indicate incomplete security hardening instructions. Fix: Complete the Docker security documentation and ensure all git configurations are explicitly documented. Consider adding explicit git safe directory configuration:RUN git config --global --add safe.directory /workdir - High · Ruby Gem Installation Without Version Pinning —
Dockerfile (gem install bundler line and bundle install). The Dockerfile installs bundler with a specific version (2.6.5) but usesbundle installwithout pinning exact dependency versions in runtime. The Gemfile.lock should be committed, but any development dependencies could introduce vulnerabilities if not properly managed. Fix: Ensure Gemfile.lock is always committed to version control. Consider usingbundle install --frozenin production builds to prevent version drift. Regularly audit dependencies usingbundle audit. - Medium · Alpine Linux with SSH Client in Production Image —
Dockerfile (apk add line with openssh-client-default). The Docker image includes openssh-client-default and git which are necessary for Kamal's deployment functionality but increase attack surface. An image with SSH capabilities could be a target if compromised. Fix: Consider using a multi-stage Docker build to separate build-time tools from runtime requirements. Alternatively, ensure this image is only used in trusted CI/CD environments and not exposed directly. - Medium · Potential Command Injection in CLI Commands —
lib/kamal/commands/ (multiple files, especially app.rb, builder.rb, docker.rb). The codebase contains multiple CLI command files (lib/kamal/commands/*) that execute shell commands remotely via SSHKit. Without visible input validation in the file structure, there's risk of command injection if user inputs aren't properly sanitized. Fix: Implement strict input validation for all CLI arguments. Use parameterized command execution where possible. Audit SSHKit usage to ensure proper escaping of shell arguments. Consider using array-based command construction instead of string interpolation. - Medium · Secrets Management in Configuration Files —
lib/kamal/cli/templates/secrets, lib/kamal/cli/secrets.rb. The codebase references secrets configuration (lib/kamal/cli/templates/secrets) but the actual secrets handling mechanism is not visible in the provided structure. Insecure secrets storage could expose credentials. Fix: Ensure secrets are never committed to version control. Validate that the secrets CLI properly uses environment variables or secure vaults. Implement strict RBAC for secret access. Document and enforce secure secret rotation policies. - Medium · Hook Execution Without Validation —
lib/kamal/cli/templates/sample_hooks/, lib/kamal/commands/hook.rb. The codebase includes hook execution functionality (lib/kamal/cli/templates/sample_hooks/) which runs arbitrary scripts at various deployment stages. These hooks could be exploited if not properly validated. Fix: Implement signature verification for hooks. Restrict hook execution to explicitly whitelisted paths. Log all hook executions. Consider requiring explicit user approval for hook execution, especially for pre-deploy hooks. - Low · Missing Security Headers Documentation —
lib/kamal/cli/templates/deploy.yml, lib/kamal/commands/proxy.rb. No visible configuration for security headers (CSP, HSTS, X-Frame-Options, etc.) in the kamal-proxy integration or deployment templates. Fix: Document recommended security headers configuration in deployment templates. Provide examples and guidance for enabling security headers in kamal-proxy configuration. - Low · Dependency Audit Gap —
.github/workflows/ci.yml, .github/workflows/docker-publish.yml. No visible automated dependency scanning or audit configuration in GitHub workflows for regular vulnerability checks. Fix: Addbundle auditor similar checks to CI/CD pipeline. Implement Dependabot configuration (partially visible in dependabot.yml) to automatically check for vulnerabilities. Consider adding SAST tools like Brakeman for Rails security checks.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.