Deploying Docker Apps the Simple Way
Kubernetes is powerful, but it comes with a steep price: YAML files that stretch across screens, cluster configuration that takes days to get right, and ongoing maintenance that demands dedicated infrastructure expertise. For developers running small-to-medium applications on a handful of servers, that overhead rarely justifies itself. Kamal – the deployment tool originally built to ship Basecamp and Hey – offers a different approach. It wraps Docker, SSH, and a reverse proxy into a single CLI workflow that gets your app live without touching a cluster.
Kamal works by SSHing directly into your servers, pulling Docker images from a registry, and managing container swaps with zero-downtime deploys using its built-in Traefik proxy. No control plane, no worker nodes, no persistent volume claims. Just your servers, your images, and a single deploy.yml configuration file. The entire mental model fits on one page, which is exactly why a growing number of solo developers and small teams are choosing it over container orchestration platforms they never actually needed.

What You Need Before Starting
Before writing a single line of configuration, you need a few things in place. A Linux server – Ubuntu 22.04 or Debian 12 work cleanly – with a public IP address and SSH access via a root or sudo-capable user. Docker does not need to be pre-installed on the target server; Kamal handles that automatically on first deploy. You do need Docker installed on your local machine, since Kamal uses it to build images before pushing them to your registry.
You also need a container registry. Docker Hub is the default and simplest option, but GitHub Container Registry (ghcr.io) works just as well and is free for public images. Have your registry credentials ready before starting. On your local development machine, install Kamal via Ruby gems with gem install kamal. If you prefer not to touch your system Ruby, run it through a Docker container – the Kamal docs include a wrapper script for this. Either way, confirm the install with kamal version before moving on.
Configuring Your First Deploy
Inside your application directory, run kamal init. This creates a config/deploy.yml file and a .kamal/secrets file. Open deploy.yml first. The top of the file sets your application name, image path, and server list. Set service to your app name, image to your registry path (for example username/myapp), and under servers, list the IP addresses of every server you want to deploy to.
The registry block handles authentication. Set username to your registry username and point password to an environment variable – Kamal pulls secrets from environment variables or the .kamal/secrets file, never hardcoded values. A minimal registry block for Docker Hub looks like this: registry: server: docker.io, username: yourname, password: DOCKER_PASSWORD. Add that variable to your .kamal/secrets file, which Kamal automatically gitignores.
The env block is where you pass environment variables into your running containers. Split them into clear and secret sections. Clear variables go directly into the config file; secrets reference environment variables on your local machine and get pushed to the server encrypted. For a Rails app, SECRET_KEY_BASE and DATABASE_URL would both live in the secret section. Kamal stores these on the server using Docker secrets under the hood, so they are never exposed in process listings.
If your app runs on a non-standard port, set it under the proxy block using app_port. Kamal’s built-in proxy – Kamal Proxy, which replaced Traefik in Kamal 2 – handles SSL termination automatically through Let’s Encrypt when you provide a domain. Add your domain under proxy: host and set ssl: true. That is the entire TLS configuration. No Certbot cron jobs, no nginx snippets to maintain separately.

Running Your First Deployment
With the config file complete, run kamal setup on first use. This command SSHes into every server in your list, installs Docker if it is missing, starts Kamal Proxy, and pushes your encrypted environment variables. It takes two to three minutes on a fresh server. After setup completes, run kamal deploy to build your Docker image locally, push it to the registry, pull it on each server, and hot-swap the running container. Your app goes live with no downtime because Kamal holds traffic on the old container until the new one passes its health check.
Subsequent deploys are just kamal deploy. Kamal also gives you kamal rollback to instantly revert to the previous image version – a single command that takes under thirty seconds. For checking what is running, kamal details shows container status across all servers, and kamal logs tails live output. These four commands – setup, deploy, rollback, details – cover the overwhelming majority of day-to-day operational needs.
Handling Accessories and Multiple Servers
Kamal handles more than just your primary application container. The accessories block lets you define supporting services – a database, a Redis instance, a background job worker – that Kamal manages on specific servers. Each accessory gets its own image, environment variables, and volume mounts. A Redis accessory, for example, can be pinned to a single server with persistent storage mapped to a host directory, and Kamal will start, stop, and restart it independently of your main app container.
For multi-server setups, Kamal supports roles. Define a web role pointing at your front-end servers and a workers role pointing at background job machines. Each role runs the same image but with a different container command – your web servers run the HTTP process, your workers run the job queue process. You specify this with a cmd override inside each role block. This keeps your Dockerfile simple and your operational model consistent: one image, multiple runtime behaviors.
One practical detail worth building into your workflow: Kamal’s kamal audit command logs every deploy action with a timestamp and the Git SHA of the deployed commit. On teams, this creates a lightweight audit trail without any external tooling. Pair that with a CI pipeline that runs kamal deploy on merge to main, and you have a complete delivery workflow that runs entirely on infrastructure you control – no third-party deployment platform required, no per-seat pricing, and no abstraction layer between you and the server running your code.

The one scenario where Kamal’s model shows its limits is horizontal auto-scaling – adding servers dynamically in response to traffic spikes. Kamal manages a fixed list of servers defined in your config file. Adding a new server means updating that list and rerunning kamal deploy, which is a manual step. For applications where load is unpredictable and scaling must happen automatically in minutes, that constraint matters.
Frequently Asked Questions
Does Kamal require Kubernetes or a container orchestration platform?
No. Kamal deploys directly to Linux servers over SSH using Docker, with no cluster or orchestration layer required.
Can Kamal handle SSL certificates automatically?
Yes. Kamal 2 includes a built-in proxy that provisions Let’s Encrypt SSL certificates automatically when you set a domain and enable SSL in the config file.
What happens to running containers during a Kamal deploy?
Kamal performs a zero-downtime swap – the old container keeps serving traffic until the new one passes its health check, then Kamal switches over.





