15% off your workspace - subscribe to our blogNo per-service fees - one plan, unlimited appsDeploy in under 40 seconds99.95% uptime SLAFree tier available - start building today15% off your workspace - subscribe to our blogNo per-service fees - one plan, unlimited appsDeploy in under 40 seconds99.95% uptime SLAFree tier available - start building today
Miget x AIPlansEnterpriseCompareBlogDashboard
Start for Free
Blog/engineering/kamal/
·

Kamal Deploy: VPS vs PaaS - Notes From Our KRUG and SRUG Talk

Miget was proud to sponsor KRUG (Kraków Ruby Users Group) and SRUG (the Silesia Ruby Users Group), and our CEO Chris Taraszka gave a talk there called "Kamal Deploy: the VPS-vs-PaaS deploy you don't know yet, but will love." This post is the write-up. The short version: you can keep Kamal's workflow and still hand off the ops, and the trick under the hood is more fun than it has any right to be.

Chris Taraszka giving the Kamal talk at KRUG and SRUG, on stage in front of the twenty-years-of-Rails-deployment slide

Chris on stage at KRUG / SRUG, in front of the slide this post opens with.

Twenty years of Rails deployment

The talk opened with the arc every Rails developer has lived through:

  • 2005 - Capistrano. SSH, rsync, symlinks. rake deploy and a prayer.
  • 2010 - Heroku. git push heroku main. Magic, just pricey.
  • 2015 - Docker and Kubernetes. "Time to learn Kubernetes," said every DevOps engineer, ever.
  • 2024 - Kamal. SSH plus Docker, no Kubernetes. Capistrano in 2024, but good.

The point of the arc is the tension it never resolved. Developers want three things at once:

  • Simplicity - Heroku DX. git push and it works.
  • Control - your own server, full SSH, logs in hand.
  • Scale - zero-downtime, rolling restarts, more boxes on a click.

The question the whole talk circles: can you actually have all three together?

Meet Kamal

Kamal is 37signals' deploy tool, the Basecamp and HEY folks, and it is the default in new Rails 8 apps. Its pitch is "deploy web apps anywhere," and it delivers:

  • Zero-downtime rolling deploys
  • A built-in kamal-proxy for routing and the traffic switch
  • SSL via Let's Encrypt with zero config
  • Asset bridging between releases
  • One deploy.yml, and that is it

The whole stack is just SSH, Docker, and kamal-proxy. No Helm, no charts, no values/ folder.

Act I: Kamal on a VPS

On a raw VPS, everything lives in one file:

service: my-rails-app
image: you/my-rails-app

servers:
  web:
    - 159.65.123.45

proxy:
  host: myapp.example.com
  ssl: true
  app_port: 3000

registry:
  username: your-dockerhub
  password:
    - KAMAL_REGISTRY_PASSWORD

You need four things before your first deploy: a VPS (Hetzner, DO, OVH - Ubuntu 22.04+, 1 GB RAM), an SSH key on the server, a DNS A record, and a registry (Docker Hub or GHCR). Then kamal setup once, and kamal deploy from then on.

kamal deploy is six steps with no magic: build on your laptop, push to the registry, pull on the server, run the new container, switch traffic once it passes its health check, clean up the old one. Zero-downtime comes down to that one rule - the new container must be healthy before the proxy sends it traffic.

The balance sheet

This is where the talk got honest. Everything that is not in deploy.yml, you handle yourself. After hours. In pyjamas. Sometimes in panic.

Kamal on a VPS gives you...and hands you the rest
Full control - root, SSH, logsPatching and CVEs, alone, at night
Cheap - a VPS from about $5/moDB backups, until you lose your first
One YAML, one commandThe Postgres major upgrade that eats your weekend
SSL and zero-downtime out of the boxThe TLS cert that expires at midnight on a Sunday
No vendor lock-in - move it anywhereThe pager that does not take a vacation

None of that is an argument against Kamal. It is an argument about who runs the box.

Act II: the same deploy.yml, different infrastructure

Here is the reveal. Deploying the same app to Miget uses the same Kamal, the same commands, and a deploy.yml that is practically identical - only the host, port, and registry change:

service: my-rails-app-somnh
image: migetbgq/my-rails-app

servers:
  web:
    - tcp11.eu-east-1.onmiget.com

proxy:
  host: app.eu-east-1.migetapp.com
  ssl: true
  app_port: 5000

registry:
  server: registry.eu-east-1.miget.io

ssh:
  user: deploy
  port: 50715

You still run kamal deploy. You still get the same log output. But on the other end there is no VPS.

Kamal thinks it is talking to Docker. It is Kubernetes.

Miget runs a small kamal-shim - a pod in the cluster that exposes the Docker Engine API and translates every call into the Kubernetes API. Kamal speaks stock SSH and Docker; it never sees a single line of Kubernetes YAML. A few of the translations:

Docker API callBecomes on KubernetesWhat actually happens
POST /containers/createPatch the DeploymentRolling update with the new image
POST /containers/{id}/startScale replicas to >= 1Wake from zero if the app was asleep
POST /containers/{id}/stopno-opK8s already did the rolling update; Kamal has no idea
GET /containers/jsonList pods + ConfigMapStatus comes from the pod, the source of truth
POST /exec/{id}/startkubectl execA bidirectional session into the pod
POST /authimagePullSecretRegistry creds land in a K8s secret

So the kamal deploy you run against Miget produces the same output as against a VPS and finishes in about the same time, but behind it is a real cluster: Deployment, Service, ConfigMap, rolling updates, IngressRoute, Traefik, and SSL. Kamal just never has to know.

Side by side

Kamal + VPSKamal + Miget
Setup time30-60 minAbout 5 min
deploy.ymlStandardPractically identical
SSLkamal-proxy + Let's EncryptTraefik, automatic
ScalingManual, add an IPUI or API
MonitoringYou roll your ownBuilt in
DatabaseYou install and back it upManaged PostgreSQL
Cost~$5-10/moFree tier, from $5/mo
Ops overheadHighLow

When to pick what

The talk did not declare a winner, because there isn't one. There is a fit.

Reach for Kamal on a VPS when ops is the point: you are learning DevOps and want hands on the metal, you need very specific hardware or a specific location, you already have an infra team that does this, or compliance forces you onto bare metal.

Reach for Kamal on Miget when you would rather write code: a hobby or side project on the free tier, an indie hacker or solo SaaS, or a production app with customers where you want scaling, preview apps, managed Postgres, monitoring, and backups included - and you would rather not be the on-call DevOps at 3 a.m.

The VPS-versus-PaaS choice was never really about the deploy command. It is about where your attention goes. Kamal keeps the command identical either way; Miget just changes what happens after you hit enter.

Thanks

Thanks to the KRUG and SRUG communities for having us. If you want to try the Miget side of the talk, the same deploy.yml, none of the pager duty, the Kamal deployment method is documented in the Miget docs, and you can start on app.miget.com.


Kamal Deploy: VPS vs PaaS - Notes From Our KRUG and SRUG Talk - Miget Blog