29 January 2026

Kamal: The Deployment Tool That Actually Makes Sense

Zero-downtime deploys without the Kubernetes hangover.


In the previous posts, we covered server provisioning with cloud-init and why managing your own infrastructure isn’t as scary as the cloud industry wants you to believe. But there’s still a missing piece: how do you actually deploy your app?

This is where things usually get messy. You’ve got your hardened server, Docker installed, firewall configured. Now what? How do you ship code without downtime? How do you handle SSL? How do you roll back when things go wrong?

The industry has two popular answers. Both are wrong for most of us.

The Kubernetes Trap

Kubernetes is an incredible piece of engineering. It was designed by Google to orchestrate containers across thousands of machines, handling auto-scaling, self-healing, service discovery, and a hundred other things you’ve never heard of.

Here’s the problem: you’re not Google.

You’re running a Rails app. Or a Django app. Or a Node service. You have one server. Maybe three. You don’t need:

  • A control plane with etcd clusters
  • Pod networking with CNI plugins
  • Ingress controllers and service meshes
  • Helm charts that span 500 lines of YAML
  • A dedicated DevOps engineer just to understand what went wrong

Kubernetes solves problems you don’t have, and creates problems you didn’t ask for. It’s like buying an truck to drive to the grocery store. Sure, it can carry groceries. But at what cost?

The cognitive overhead alone is staggering. I’ve seen teams spend more time debugging Kubernetes than building features. And when something breaks at 3 AM, good luck figuring out if it’s the pod, the service, the ingress, the network policy, or the node itself.

The Docker Swarm Gap

“Okay,” you say, “Kubernetes is overkill. What about Docker Swarm? It’s simpler, it’s built into Docker, it handles orchestration.”

You’re right. Swarm is simpler. But it’s also… incomplete.

Swarm gives you container orchestration. It will run your containers across multiple nodes, handle basic load balancing, and restart failed services. That’s genuinely useful.

But then it stops. Swarm doesn’t help you with:

  • SSL certificates: You need to set up Traefik, Nginx, or Caddy yourself. Configure Let’s Encrypt. Hope the renewal doesn’t break.
  • Deployment pipelines: How do you actually push new code? Swarm has no opinion. You’re on your own.
  • Secrets management: Swarm has secrets, but getting them into your app requires ceremony.
  • The glue: A hundred small things that nobody tells you about until you’re in production.

Swarm gives you puzzle pieces. You still have to assemble the puzzle yourself. And when you do, you’ve basically built a custom deployment system that only you understand.

Enter Kamal

Kamal is a deployment tool built by 37signals, the creators of Rails (and Basecamp, and HEY). It was born from a simple question: what’s the minimum viable deployment system for a modern web app?

And here’s what makes Kamal trustworthy: 37signals built it for themselves. They needed an end-to-end system to deploy Basecamp, HEY, and Fizzy, which are small to medium-sized apps by cloud industry standards. It turns out that’s exactly what most of us need too.

The answer turns out to be surprisingly elegant: SSH, Docker, and a clever proxy.

That’s it. No control planes. No cluster setup. No cloud-specific APIs. Just your server, your containers, and a single YAML file.

Getting started takes one command:

kamal init

This generates everything you need: a config/deploy.yml file with sensible defaults, a .kamal/secrets file for your credentials, and a Dockerfile if you don’t have one. Rails 8 apps come with all of this out of the box, but kamal init works for any project.

Here’s what a complete Kamal configuration looks like:

service: myapp
image: myapp

servers:
web:
- 192.168.1.10
job:
hosts:
- 192.168.1.10
cmd: bundle exec sidekiq

proxy:
ssl: true
host: myapp.com

registry:
server: localhost:5555

env:
secret:
- RAILS_MASTER_KEY
clear:
RAILS_ENV: production
RAILS_LOG_TO_STDOUT: true

volumes:
- "storage:/rails/storage"

That’s a production-ready deployment configuration. SSL included. Zero-downtime deploys included. Rolling restarts included.

Notice the registry line? That’s pointing to local registry running on your own machine. No Docker Hub. No GitHub Container Registry. No third-party service standing between you and your deployment.

Now deploy:

kamal setup  # First time: installs Docker, configures everything
kamal deploy # Every time after: builds, pushes, deploys

Done.

The Swiss Army Knife

What makes Kamal special isn’t any single feature. It’s that it handles the entire deployment pipeline, end to end, without requiring you to glue together a dozen different tools.

SSH-Based Deployment

Kamal deploys over SSH. You need exactly one thing: root access to a Linux box. No agents to install. No cluster to bootstrap. No control plane to maintain.

This is radically simpler than the alternatives. Your laptop talks to your server over SSH. That’s the entire architecture.

ssh root@your-server  # If this works, Kamal works

Your Own Registry: No Middleman

Most deployment guides assume you’ll use Docker Hub, GitHub Container Registry, or some other hosted service. Another dependency. Another account. Another thing that can go down or change its pricing.

Kamal works perfectly with a local registry running on your own machine:

registry:
server: localhost:5555

That’s it. Your laptop builds the image, pushes it to your machine, and Kamal deploys it. The entire pipeline is just two machines talking to each other over SSH. No third-party services. No accounts. No tokens to rotate. No vendor in the middle who can decide to rate-limit you or shut down your account.

Your PC. Your machine. Nothing in between.

Kamal-Proxy: The Secret Sauce

Kamal includes kamal-proxy, a lightweight Go-based reverse proxy that runs on your server. It handles:

  • Automatic SSL: Let’s Encrypt certificates, automatically provisioned and renewed
  • Zero-downtime deploys: New containers boot, health checks pass, traffic switches over, old containers stop
  • Request buffering: No dropped connections during deployments
  • Multiple apps: Run several applications on one server, routed by hostname

You don’t configure any of this. It just works. The proxy: ssl: true line in your config is all it takes for production-grade HTTPS.

Built-in Health Checks

Kamal doesn’t just start your container and hope for the best. It waits for your app to respond to health checks before routing traffic to it.

proxy:
healthcheck:
path: /up
interval: 3
timeout: 3

If your new deployment fails health checks, traffic stays on the old version. No manual rollback required.

Rolling Restarts and Rollbacks

Multiple servers? Kamal deploys to them one at a time, ensuring you always have healthy instances serving traffic.

Something went wrong? Roll back in seconds:

kamal rollback [version]

Accessories for Everything Else

Need Redis? PostgreSQL? A backup service? Kamal handles “accessories” too:

accessories:
db:
image: postgres:16
host: 192.168.1.10
directories:
- data:/var/lib/postgresql/data
env:
POSTGRES_PASSWORD: secret
redis:
image: redis:7
host: 192.168.1.10

These run alongside your app, managed by the same tool, configured in the same file.

The Real Comparison

Let me be blunt about what we’re comparing:

Kubernetes:

  • Weeks to learn properly
  • Dedicated infrastructure for the control plane
  • YAML files measured in hundreds of lines
  • Debugging requires specialized knowledge
  • Overkill for anything under massive scale

Docker Swarm:

  • Simpler than Kubernetes
  • But no SSL automation
  • No deployment tooling
  • No health-check-based routing out of the box
  • You’ll build a custom system anyway

Kamal:

  • Learn it in an afternoon
  • Works over SSH (nothing to install on the server)
  • Single YAML file for everything
  • SSL, zero-downtime, health checks built in
  • Use your own registry, no third-party dependencies
  • Designed for real-world web apps, not Google-scale infrastructure

What Kamal Isn’t

Let’s be honest about the tradeoffs. Kamal is not:

  • An auto-scaler: It won’t spin up new servers when traffic spikes. You handle capacity planning.
  • A self-healing system: If your server dies, Kamal won’t automatically migrate to another one. That’s what multiple servers and load balancers are for.
  • A service mesh: No built-in inter-service communication patterns. Your apps talk to each other normally.

If you need those features, you might actually need Kubernetes. But most of us don’t. Most of us need to deploy a web app to a few servers without losing our minds.

The Missing Piece

This is the deployment tool I wished existed for years. Something that:

  • Doesn’t require a PhD in distributed systems
  • Handles the boring stuff (SSL, health checks, zero-downtime)
  • Works with any Docker container
  • Deploys over SSH like a civilized tool
  • Fits in a single, readable config file
  • Needs nothing but your laptop and your server

Kamal is that tool. It’s the missing piece between “I have a server” and “my app is in production.”

Combined with cloud-init for provisioning and a $5/month VPS, you have a complete deployment story. No managed Kubernetes. No complex CI/CD pipelines. No cloud vendor lock-in. No container registry subscription. No third-party service that can go down, change their terms, or jack up their prices.

Just your code, your server, and a tool that gets out of your way.


This is the third post in the FreeFrom.Cloud series. Read the previous posts: The Cloud is Scarier Than You Think and Cloud-Init: The 42-Line Config That Replaces Your Cloud Setup. Subscribe to get notified when new posts drop.

STAY UPDATED

Don't Miss New Articles

No spam, no sales pitches. Just practical content.

NEED HELP?

Don't Want to Do It Yourself?

Let me handle the migration for you.