Kamal vs Kubernetes: An Honest Comparison for Teams Who Don’t Need 1,000 Services

Kamal vs Kubernetes: An Honest Comparison for Teams Who Don't Need 1,000 Services

The framing problem

Most “Kamal vs Kubernetes” articles are written by people who just discovered Kamal and are excited about it. They frame it as a simpler alternative that embarrasses Kubernetes. That framing is wrong and it will lead you to the wrong decision.

Kamal is not better than Kubernetes. Kubernetes is not overkill for everyone. They solve different problems at different scales, and picking the wrong one for your context is expensive either way.

This article gives you the honest version: what each tool actually is, where each one breaks, and the specific signals that should push you in one direction or the other.


What Kamal actually is

Kamal (formerly MRSK) is a deployment tool built by 37signals. It deploys Docker containers to servers via SSH, uses kamal-proxy as its default reverse proxy, and handles rolling deploys with zero downtime. That is the entire feature set.

kamal deploy

It connects to your servers over SSH, pulls the new image, starts the new container, waits for it to be healthy, then moves traffic through kamal-proxy and stops the old one. Traefik is still possible, but in Kamal 2 it sits in front of kamal-proxy as an optional accessory rather than being the standard path.

There is no control plane. No etcd. No API server. No scheduler. No concept of desired state reconciliation. Docker or systemd can restart a crashed container if you configure the right restart policy, but Kamal itself does not reschedule workloads to another node or provide cluster-level self-healing.

Kamal is a deployment tool. Kubernetes is an orchestration platform. These are not the same category.

That is the key distinction from tools like Nomad. I covered that angle in Nomad vs Kubernetes, as part of the same “lighter alternatives to Kubernetes” discussion. Nomad is a scheduler/orchestrator. Kamal is a deployment tool that leaves scheduling and node-level recovery mostly outside its scope.


What Kubernetes actually is

Kubernetes is a distributed system that continuously reconciles actual state against desired state. You declare what you want — N replicas of container X, with these resource limits, this health check, this rollout strategy — and the control plane makes it happen and keeps it that way.

When a node fails, your pods are rescheduled. When a container crashes, it is restarted. When you push a new image, the rollout respects your maxUnavailable and maxSurge settings. When traffic spikes, HPA can add replicas.

This machinery is powerful. It is also genuinely complex to operate.

A minimal production-grade Kubernetes cluster involves: a multi-node control plane for HA, a CNI plugin, an ingress controller (or Gateway API), cert-manager for TLS, a CSI driver for storage, a metrics server for HPA, RBAC policies, network policies, pod disruption budgets, and some form of secret management. Getting all of this right takes time and ongoing maintenance.

The question is whether the problems Kubernetes solves are problems you actually have.


The minimum config tells the story

Here is the same tiny web service expressed in both worlds. The exact fields vary by app, registry, ingress setup, and Kubernetes distribution, but the shape is representative.

<table> <thead> <tr> <th>Kamal <code>deploy.yml</code></th> <th>Kubernetes <code>Deployment</code> + <code>Service</code></th> </tr> </thead> <tbody> <tr> <td> <pre><code class=”language-yaml”>service: web image: ghcr.io/acme/web

servers: web: hosts: – 203.0.113.10

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

registry: username: acme password: – KAMAL_REGISTRY_PASSWORD

env: secret: – DATABASE_URL</code></pre> </td> <td> <pre><code class=”language-yaml”>apiVersion: apps/v1 kind: Deployment metadata: name: web spec: replicas: 2 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: – name: web image: ghcr.io/acme/web ports: – containerPort: 3000 envFrom: – secretRef: name: web-secrets — apiVersion: v1 kind: Service metadata: name: web spec: selector: app: web ports: – port: 80 targetPort: 3000</code></pre> </td> </tr> </tbody> </table>

The Kubernetes example is still incomplete for internet traffic and TLS: you would normally add an Ingress or HTTPRoute, cert-manager, DNS, and secret provisioning. Kamal’s example is closer to “deploy this app to these servers.” Kubernetes is closer to “declare this workload inside a platform that already exists.”


The 37signals case

37signals (the company behind Basecamp and Hey) moved off cloud Kubernetes to bare-metal servers running Kamal in 2022-2023. Their own Ops team wrote about the “de-cloud and de-k8s” move. The story got a lot of attention.

What the hot takes missed: 37signals is not a startup. They have a dedicated ops team. They own and operate hardware. Their workload is well-understood and stable: a small number of mature applications serving a large, established user base.

Their decision was rational for their context. Kubernetes was costing them in cloud spend and operational complexity for problems their workload does not have. Moving to Kamal on owned hardware made economic and operational sense for them.

It does not follow that Kamal is the right choice for your early-stage startup, your company that runs on AWS because your team does not want to manage hardware, or your platform that needs to scale from 0 to 10x on short notice.


Where Kamal breaks

No cluster-level self-healing. If your container crashes, Docker or systemd can restart it with the right policy, but Kamal has no involvement. If your server fails, Kamal will not reschedule that workload elsewhere; your service is down until you intervene or until you have set up something else to handle it.

No built-in auto-scaling. Kamal has no equivalent of HPA or KEDA. If your traffic spikes, you add servers manually or script something yourself.

No multi-AZ HA by default. You can deploy to multiple servers across regions, but the coordination is yours to manage. There is no concept of spreading replicas across availability zones automatically.

Rolling deploys are basic. Kamal’s zero-downtime deploy is good for single-server or simple multi-server setups. It is not a substitute for Kubernetes rolling update with fine-grained health check integration across a fleet.

Secret management is manual. Kamal reads secrets from .kamal/secrets and has kamal secrets helpers for fetching and extracting values from password managers and cloud secret stores, but there is no Kubernetes-style projected secret lifecycle. Rotation and policy remain yours to manage.

No workload isolation. Everything on the same server shares the same kernel, same resources. Resource limits are Docker-level, not enforced by an orchestration layer with quotas and namespaces.


Where Kubernetes breaks

The complexity tax is real. Running Kubernetes yourself — not a managed service, but actual cluster operation — requires dedicated expertise. A team without a platform engineer will spend significant time debugging networking issues, understanding why pods are Pending, or figuring out why cert-manager is not issuing certificates.

Managed K8s is not free. EKS charges for the cluster control plane, with standard support priced around $0.10/hour per cluster; GKE and AKS have their own pricing rules and credits. In all cases, you still pay for nodes, load balancers, and persistent volumes. For a small application, this cost can exceed what bare-metal hosting would cost.

The YAML surface area scales with complexity. A simple web app on Kubernetes requires a Deployment, a Service, an Ingress (or HTTPRoute), a Certificate, potentially a HorizontalPodAutoscaler, a PodDisruptionBudget, and NetworkPolicies. That is a lot of infrastructure for a CRUD app.

Debugging is harder. Distributed systems are harder to debug than single-server setups. kubectl exec and kubectl logs are good tools but diagnosing why traffic is not reaching your pod — DNS, CNI, network policy, service selector, readiness probe — requires Kubernetes-specific knowledge that takes time to build.


The honest decision framework

Ask yourself these questions in order:

1. Do you have more than one team working on independent services with different scaling needs?

If yes: Kubernetes starts making sense. The isolation and per-service resource management are worth the overhead.

If no: Kamal is probably sufficient.

2. Do you need to survive individual server failures automatically, without manual intervention?

If yes: You need either Kubernetes with proper HA setup, or a managed container platform whose recovery and placement model fits your app. Cloud Run, Fly.io, Render, and Railway all help here, but their HA boundaries differ. Kamal alone does not give you this.

If no: A Docker or systemd restart policy covers the common crash case.

3. Does your traffic profile require automatic scaling?

If yes: Kubernetes with HPA or KEDA, or a managed platform with the scaling behavior you actually need. Cloud Run scales request-driven services to and from zero; Fly.io supports autostart/autostop and machine scaling; Render and Railway support scaling, but with provider-specific limits and billing. Kamal has no answer here without custom tooling.

If no: Fixed capacity is fine. Kamal works.

4. Do you have a platform team, or is “ops” a shared responsibility among product engineers?

If platform team exists: Kubernetes operational overhead is distributed and manageable.

If ops is everyone’s side job: Kamal’s simplicity is a genuine advantage. Less to go wrong, less to learn, faster to debug.

5. Are you on cloud infrastructure you do not own, and cloud costs matter?

If yes: Run the numbers on managed K8s vs a few VPS instances. For small workloads, VPS + Kamal is significantly cheaper.

If no: You have hardware already. Kamal is almost certainly the right choice.


Side-by-side

KamalManaged container platformsKubernetes
ExamplesKamal on VPS/bare metalCloud Run, Fly.io, Render, RailwayEKS, GKE, AKS, self-managed
Deployment modelSSH + DockerPlatform API / Git deploy / container deployAPI-driven, reconciliation loop
Self-healingDocker/systemd restart policy onlyPlatform-managed restart and placementPod rescheduling, node failure recovery
Auto-scalingManualProvider-specific; not all scale the same wayHPA, VPA, KEDA
HAManual (multiple servers)Provider-managed, with platform limitsAvailable with a well-configured multi-node control plane (not free)
TLSkamal-proxy with automatic HTTPS; Traefik optionalUsually built incert-manager + ingress controller
Secret management.kamal/secrets + helper commandsPlatform secretsSecrets API, external-secrets, Vault
ObservabilityStandard Docker loggingProvider logs/metrics, export variesRich ecosystem (Prometheus, OTel, etc.)
Rollout controlBasic rolling deploySimple rollouts, provider-specificFine-grained (maxUnavailable, canary, etc.)
Learning curveLowLow to mediumHigh
Ops overheadLowLowMedium to high
Right scale1-20 services, stable loadSmall to medium teams that want managed recovery/scaling10+ services, variable load, or regulated

What the threshold actually looks like

Kamal is the right default if you are:

  • A team of 2–10 engineers shipping a web application
  • Running stable, predictable workloads
  • On a budget where managed K8s costs matter
  • Without a dedicated platform engineer

Kubernetes is the right choice when:

  • You have 10+ services with independent deployment cycles
  • You need multi-zone HA and automatic failover
  • Traffic is variable enough that auto-scaling saves real money or prevents real incidents
  • You have a platform team that can absorb the operational complexity
  • You are in a regulated environment that benefits from K8s’s audit trails and RBAC

There is a middle tier worth mentioning: managed container platforms like Fly.io, Railway, Render, and Google Cloud Run. These give you some of Kubernetes’s operational benefits with closer to Kamal’s operational simplicity, but the details matter: HA, auto-scaling, scale-to-zero, regions, and billing differ by provider. For teams that need more than Kamal but do not want to operate K8s, this tier is often the right answer and gets underrepresented in the debate.


The real lesson from 37signals

The story is not “Kamal beat Kubernetes.” The story is: match your infrastructure to your actual operational profile, not to what is fashionable or what scales to Google’s size.

37signals evaluated what problems they actually had and picked the tool that solved those problems at the lowest operational cost. That is the right framework.

For most teams reading this, Kubernetes is probably the right answer eventually — when your system’s complexity justifies it. The mistake is adopting it before you are there, burning engineering time on infrastructure problems instead of product problems.

Kamal is a good tool for a specific stage and scale. Use it until you outgrow it. When you outgrow it, you will know — because the things Kubernetes solves will be actual problems you have, not hypothetical ones.


What to do next

Before you choose, write down your actual requirements in one page: number of services, expected traffic variance, failure tolerance, who owns ops, whether you own hardware, and what compliance needs are real today. Then run a small proof of concept with the simplest tool that satisfies those requirements. If Kamal plus Docker restart policies covers the failure modes you actually accept, start there. If you need automatic placement, autoscaling, and multi-zone recovery now, compare managed container platforms first, then Kubernetes.


FAQ

Can Kamal and Kubernetes coexist?

Yes. Some teams use Kamal for simple auxiliary services (internal tools, cron jobs, simple APIs) while running their core platform on Kubernetes. The tools are not mutually exclusive.

Is Kamal production-ready?

Yes. 37signals runs Basecamp and Hey on it. It handles zero-downtime deploys, TLS, and multi-server deployments reliably. The limitation is not reliability — it is feature scope.

What about Docker Swarm? Is it still relevant?

Docker Swarm fills a similar niche — simpler than Kubernetes, multi-host orchestration. Swarm mode is still documented and supported as part of Docker Engine for teams that want it as a production runtime, but its ecosystem is much smaller than Kubernetes and less active in the market. I would not pick it as the default for a new project unless the team already knows and wants Swarm.

Does Kamal work with any cloud provider?

Yes. Kamal only requires SSH access to a server and a container registry. It works with any VPS provider (Hetzner, DigitalOcean, OVH, AWS EC2, etc.) and any registry (Docker Hub, GitHub Container Registry, ECR, Harbor).

Is Kubernetes worth learning even if you use Kamal today?

Yes. Kubernetes is the dominant orchestration platform. Understanding it — even without operating it — makes you a better platform engineer and opens more career options. Learning Kamal does not preclude learning Kubernetes.