Kamal does not run on your VPS. It is a deployment tool you install on your own machine or CI runner, and it connects to the server over SSH to do its work. Basecamp built it to ship Rails apps without Kubernetes, but it deploys anything that builds into a Docker image.
That distinction changes what you are shopping for. The VPS is Kamalโs target, so it has to be large enough for your application, its databases, and a few days of image versions. Kamalโs own footprint on the server is zero.
What Kamal actually puts on the server
kamal setup connects over SSH (as root by default, authenticated by your SSH key) and runs three steps: install Docker if it is not already present, start any accessories you declared, then deploy the app. Afterwards the host is running:
- kamal-proxy โ a small HTTP proxy that owns ports 80 and 443, obtains Letโs Encrypt certificates, and holds incoming requests during a deploy until the replacement container is ready.
- Your app containers โ one set per role defined in
config/deploy.yml. - Accessories โ the Postgres, Redis, or other services you listed in the config.
Kamal itself is a gem (gem install kamal, 2.12.0 at the time of writing) and leaves no daemon behind.
Prerequisites
Before you start, have:
- A VPS with a clean Linux install and root SSH key access (Ubuntu 24.04 is a sensible default)
- A container registry your server can pull from โ Docker Hub, GitHub Container Registry, or your providerโs
- Ruby locally for
gem install kamal; without Ruby you can run Kamal from a container image instead, with some limitations - A working
Dockerfilefor your app
Choosing a VPS Provider
Because the server only runs containers, judge providers on network reliability, disk headroom, and how quickly you can rebuild a host:
| Provider | Price | Features | Affiliate Link |
|---|---|---|---|
| Contabo VPS | 5.99 EUR/mo | 400 GB disk absorbs large images and long rollback history | Contabo VPS |
| Hetzner Cloud | 5.49 EUR/mo | 2 vCPU / 4 GB / 40 GB NVMe, snapshots for quick host rebuilds | Hetzner Cloud |
| DigitalOcean | 6 USD/mo | Best documentation and API for scripting the host itself | DigitalOcean |
| Vultr | 5 USD/mo | Wide region choice for placing the app near its users | Vultr |
| Linode | 5 USD/mo | Predictable pricing, straightforward network setup | Linode |
For a comprehensive comparison, see our full VPS comparison page.
DigitalOcean is the easiest starting point here, because its documentation covers the host-level work Kamal does not do for you โ firewall rules, swap, and unattended upgrades.
Preparing the Server
- Deploy the VPS with a minimal Ubuntu 24.04 image and your SSH key attached.
- Confirm root SSH works, since that is how Kamal connects by default:
ssh root@your-vps-ip
- Open only what you need. kamal-proxy terminates TLS on the server, so ports 22, 80, and 443 are enough:
ufw allow 22,80,443/tcp && ufw enable
You do not need to install Docker yourself. Kamal installs it on the first kamal setup if it is missing.
Installing Kamal and Writing the Config
On your own machine, inside your application directory:
gem install kamal
kamal init
kamal init creates config/deploy.yml. A minimum working config needs a service name, an image, and at least one host:
service: myapp
image: your-registry-user/myapp
servers:
web:
hosts:
- 203.0.113.10
registry:
username: your-registry-user
password:
- KAMAL_REGISTRY_PASSWORD
proxy:
ssl: true
host: app.example.com
Secrets live in .kamal/secrets, which Kamal reads at deploy time. It looks for .kamal/secrets-common first, then .kamal/secrets:
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
Keep that file out of version control and pull the value from your shell environment or a password manager.
Running the First Deploy
kamal setup
That single command installs Docker, starts accessories, builds and pushes your image, and boots the app. Every deploy after it is:
kamal deploy
To check what is running on the host:
kamal app containers
Where the Image Gets Built
By default Kamal builds locally, on whatever machine you run it from, then pushes to the registry. The server only pulls. If your laptop is arm64 and the VPS is amd64, either pin the target architecture or point Kamal at a remote builder:
builder:
arch: amd64
remote: ssh://[email protected]
Cross-building through emulation is slow enough to notice on every deploy. If the VPS is the only amd64 machine you own, it can double as the builder โ but then size it for a Docker build rather than just for running the app, which usually means moving up a plan for the extra RAM.
Sizing the Disk
Kamal keeps old containers and images on the server so kamal rollback can restart a previous version without pulling from the registry again. It prunes them after three days by default. Plan for your applicationโs own data plus about three days of image versions and their shared base layers.
A 1 GB image deployed a handful of times a day sits comfortably on Hetznerโs 40 GB NVMe. A 3 GB image on a 25 GB disk will fill it within a week, and a full disk breaks the next deploy rather than the current one โ which makes it an easy problem to miss until it is urgent.
Zero-Downtime Cutover
kamal-proxy only moves traffic to the new container once that container answers GET /up with a 200. Two things follow from this:
- Your app needs a
/upendpoint. Rails provides one; other frameworks usually need a few lines. - The endpoint must return 200 only when the app can actually serve requests. If it answers before database connections are ready, users hit the new container too early.
If the health check never passes, the deploy times out and Kamal leaves the old container serving traffic.
Securing the Deployment
- Kamal connects as root over SSH, so disable password authentication entirely and rely on keys.
- Let kamal-proxy handle TLS. Adding a second reverse proxy in front of it only complicates the certificate path.
- Add swap on 4 GB hosts. Deploys briefly run the old and new containers at once, and a host that starts swapping mid-deploy is slow enough to trip the health-check timeout.
- Keep the registry password in
.kamal/secretsrather than inconfig/deploy.yml, which is committed.
Final Tips
- Run
kamal deployfrom CI once the manual path works. The config is identical; only the secrets source changes. kamal rollback [version]is instant while the old image is still on disk, so checkkamal app containersbefore assuming a rollback target exists.- Deploying a second app to the same host is supported in Kamal 2 โ kamal-proxy routes by hostname, so each app just needs its own
servicename andproxy.host.
For more on picking the underlying server, see our full VPS comparison page.
Frequently asked questions
Does Kamal run on the VPS, or on my own machine?
Kamal runs on your machine or your CI runner. It is a Ruby gem that connects to the server over SSH, installs Docker if it is missing, and starts your containers. What stays behind on the VPS is kamal-proxy plus your app and accessory containers, never Kamal itself.
How much disk does a VPS used as a Kamal target need?
Budget for your app data plus roughly three days of image versions, because Kamal keeps old containers and images for rollback and prunes them after three days by default. A 1 GB image deployed a few times a day fits comfortably on 40 GB; a 3 GB image on a 25 GB disk will not.
Why does my Kamal deploy time out and roll back?
kamal-proxy only shifts traffic once the new container answers GET /up with a 200 response. If your app exposes no such endpoint, or it returns an error while the app is still booting, the health check never passes and Kamal reverts to the previous container.