Independent testing Updated August 2026 387 self-hosting guides 5 VPS providers tested

guide

How to Deploy to a VPS with Kamal (Complete Guide)

Kamal deploys your Docker app to a plain VPS over SSH, no Kubernetes needed. Write config/deploy.yml, size the server correctly, and ship with zero downtime.

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 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:

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:

ProviderPriceFeaturesAffiliate Link
Contabo VPS5.99 EUR/mo400 GB disk absorbs large images and long rollback historyContabo VPS
Hetzner Cloud5.49 EUR/mo2 vCPU / 4 GB / 40 GB NVMe, snapshots for quick host rebuildsHetzner Cloud
DigitalOcean6 USD/moBest documentation and API for scripting the host itselfDigitalOcean
Vultr5 USD/moWide region choice for placing the app near its usersVultr
Linode5 USD/moPredictable pricing, straightforward network setupLinode

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

  1. Deploy the VPS with a minimal Ubuntu 24.04 image and your SSH key attached.
  2. Confirm root SSH works, since that is how Kamal connects by default:
ssh root@your-vps-ip
  1. 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:

If the health check never passes, the deploy times out and Kamal leaves the old container serving traffic.

Securing the Deployment

Final Tips

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.