DEV Community

Jonathan Pitter
Jonathan Pitter

Posted on

I Run a Full PaaS on One $11/Month ARM Server. Here's What the Resource Usage Actually Looks Like.

A managed Kubernetes cluster on AWS starts at ~$73/month, just the control plane, before you run anything on it. Add a load balancer, a managed Postgres instance, and a few worker nodes and you're at $300-400/month before your first tenant deploys.

I run a multi-tenant deployment platform on a single Hetzner ARM server that costs €10.99/month. That includes the API server, dashboard, build pipeline, PostgreSQL, Redis, container registry, ingress controller, automatic SSL, and room for 10-25 tenant environments.

Here's the full breakdown.

The Server

Hetzner CAX21, one of their Ampere ARM64 cloud instances:

  • 4 vCPU (ARM64)
  • 8 GB RAM
  • 80 GB NVMe SSD
  • 20 TB traffic included
  • €10.99/month (~$11)

For context, the closest equivalent on AWS would be a t4g.large (2 vCPU, 8GB ARM) at ~$49/month on-demand, or a t4g.xlarge (4 vCPU, 16GB) at ~$98/month. That's a 4.5-9x price difference for comparable compute.

Why ARM? Every piece of software I'm running (Go, K3s, Traefik, PostgreSQL, Redis, Node.js, and Buildah) has native ARM64 support. Container images need to target linux/arm64, but the tooling handles this transparently. There's no performance penalty for my workload. The only thing I gave up was familiarity. Most tutorials and Stack Overflow answers assume x86. That cost me a few hours of debugging early on and nothing since.

What's Running on 8 GB

Here's the actual memory layout. I'm running K3s (lightweight Kubernetes), so everything is organized into namespaces.

System Components (~1.5 GB)

These are the Kubernetes infrastructure services that run outside the staxa-system namespace:

Component What it does RAM
k3s server Kubernetes API server, scheduler, controller manager, etcd replacement (SQLite) ~500 MB
CoreDNS Cluster DNS, resolves service names inside the cluster ~50 MB
cert-manager Watches for new Ingress/IngressRoute resources and provisions Let's Encrypt certificates automatically ~50 MB
local-path-provisioner Storage, creates PersistentVolumes backed by the server's NVMe SSD ~30 MB
Miscellaneous kubelet overhead, pause containers, system daemons ~200 MB

Total system footprint: ~1-1.5 GB out of 8 GB.

K3s deserves special mention here. A full Kubernetes distribution (kubeadm + etcd) would use 2-3 GB for the control plane alone. K3s gets this under 600 MB by replacing etcd with SQLite, stripping unused cloud provider code, and shipping as a single ~70 MB binary. On a constrained server, that 1.5 GB savings is the difference between running the platform and not.

Platform Services (~750 MB)

These are the services that make up the actual deployment platform, all in the staxa-system namespace. These aren't estimates. This is kubectl top pods -n staxa-system from the live production server:

A note on these numbers: The platform is currently in public testing with low traffic. These represent baseline memory, what each component uses just by running, not under heavy load. Concurrent builds, active API traffic, and more tenants will push these higher. I'll update this article as the platform scales.

Component What it does RAM
Go API (staxad) Single binary that handles all 60 API endpoints, orchestrates Kubernetes, manages jobs 13 Mi
Redis 7 Job queue (deployment pipeline) and pub/sub (SSE events to dashboard) 10 Mi
Notifier Node.js service that bridges Redis pub/sub events to the dashboard via Socket.IO 20 Mi
Container registry Stores built tenant images. Buildah pushes here, Kubernetes pulls from here 33 Mi
PostgreSQL 16 Platform database storing tenants, deployments, providers, and configs 44 Mi
Traefik Ingress controller that routes all HTTP/HTTPS traffic to the right tenant 65 Mi
Next.js dashboard Frontend for visual tenant management, connects to the Go API 85 Mi
Grafana Dashboards for log volume, API requests, error rates, and active tenants 98 Mi
Loki Log aggregation. Receives logs from Promtail, stores in Hetzner Object Storage 154 Mi
Strapi CMS Headless CMS serving API documentation and support center content 200 Mi

Promtail (the log shipper) runs in a separate observability namespace and adds another ~30-50 Mi.

Total platform footprint: ~750 Mi. The entire deployment platform, including the API, database, queue, dashboard, CMS, build registry, ingress, observability stack, and real-time event streaming, fits in under 800 MB.

The Go API is the star here. A single compiled binary handles authentication, tenant provisioning, build orchestration, deployment management, SSE streaming, and admin tools. At baseline, it uses 13 megabytes of RAM, less than the container registry and less than PostgreSQL. It's a fraction of any single JavaScript service in the stack. Even under load, Go APIs of this size typically stay under 50-80 MB. Compare that to a Node.js or Java equivalent that would start at 100-200 MB idle. The compiled language advantage isn't theoretical at this scale. It's the reason the platform fits on this server at all.

What's Left for Tenants: ~5 GB

At current baseline usage, after the system and platform services (~1.5 GB + ~750 MB), roughly 5 GB is available for actual tenant workloads. Under heavier platform load this shrinks. Realistically, plan for 4-5 GB of tenant headroom. Each tenant gets a Kubernetes namespace with:

  • An app container (the user's deployed application)
  • A database container (PostgreSQL 16 or MySQL 8, their choice)
  • Resource quotas enforced at the namespace level

The resource sizes I offer:

Size CPU Request RAM Request CPU Burst RAM Burst Typical use
Small 25m 64 MB 500m 512 MB Static sites, light frontends
Medium 50m 128 MB 1 CPU 1 GB APIs with database queries
Large 100m 256 MB 2 CPU 2 GB Heavy backends, workers

"Requests" are guaranteed minimums. "Burst" is what they can use if other tenants aren't competing for resources. On a lightly loaded server, a "small" tenant can burst up to 500m CPU and 512 MB. That's plenty for a real web application.

A small tenant (app + Postgres) uses roughly 200-300 MB of guaranteed memory. A medium tenant uses 400-500 MB. That gives me:

  • ~15-20 small tenants for static sites, SPAs, and light APIs
  • ~10-12 medium tenants for full-stack apps with databases
  • ~5-8 large tenants for heavier workloads

In practice, a mix of sizes. Call it 10-25 tenants depending on the workload profile. For a platform in early access, that's more than enough.

What Costs $0

Several things that would cost money on a managed cloud setup are effectively free here:

DNS: Cloudflare free tier. Wildcard DNS (*.tenants.staxa.dev) pointing to the server IP. Custom domains work via CNAME records that tenants add on their end.

SSL certificates: Let's Encrypt via cert-manager. Every tenant gets automatic SSL on both their subdomain and custom domain. cert-manager watches for new IngressRoutes and provisions certificates without any manual intervention. For wildcard certificates, it uses Cloudflare's DNS challenge.

Load balancing: Traefik handles all HTTP/HTTPS routing. On a single server there's no external load balancer to pay for. Traefik discovers new tenants automatically through Kubernetes CRDs. When the Go API creates an IngressRoute in a tenant's namespace, Traefik picks it up and starts routing traffic within seconds.

Container registry: Self-hosted inside the cluster. Buildah pushes built images here, Kubernetes pulls from here. No Docker Hub, no ECR, no per-image charges.

Monitoring and logging: Grafana + Loki + Promtail. Promtail ships pod logs to Loki (single-binary mode), which stores them in Hetzner Object Storage via S3. Grafana at logs.staxa.dev gives me dashboards for log volume by service, API request counts, error rates, and active tenant namespaces, with live log tailing and filtering by app or namespace. 30-day retention. The whole stack runs inside the cluster, and log storage costs are negligible on Hetzner's object storage pricing.

The One Thing That Would Blow Up the Budget

Managed databases.

A single AWS RDS db.t4g.micro (2 vCPU, 1GB) runs ~$12/month. If I gave every tenant their own RDS instance, 10 tenants would cost $120/month in databases alone, more than 10x the entire server.

Instead, each tenant's database runs in a container inside their Kubernetes namespace. PostgreSQL 16 or MySQL 8, backed by a PersistentVolume on the server's NVMe SSD. Is this as resilient as RDS with automated backups and failover? No. But for a platform in early access, it's the right tradeoff. I can always migrate individual tenants to managed databases later as a premium tier. The important thing is that the current architecture doesn't force a $12/tenant/month floor cost from day one.

When $11 Isn't Enough

The single-server setup has real limitations:

Single point of failure. If the server goes down, everything goes down, platform and tenants. There's no redundancy. For the current stage (early access, demo environments), this is acceptable. For paying customers expecting uptime SLAs, it's not.

Build pipeline contention. Container builds (Buildah) are CPU-intensive. When multiple tenants deploy simultaneously, builds queue up. On 4 vCPUs, I can realistically run 1-2 concurrent builds before performance degrades.

Disk pressure. Container images accumulate. Each tenant's built image sits in the registry, plus layers are cached for rebuild speed. At 80 GB total disk with the OS, Kubernetes, and databases taking their share, I'd start sweating around 20-30 tenants depending on image sizes. Registry garbage collection helps, but disk is the most likely bottleneck.

The Scaling Path

K3s makes horizontal scaling surprisingly simple. Adding a second server is one command:

# On the new server:
curl -sfL https://get.k3s.io | K3S_URL=https://<existing-server>:6443 \
  K3S_TOKEN=<node-token> sh -
Enter fullscreen mode Exit fullscreen mode

That joins it as a worker node. Kubernetes starts scheduling tenant pods across both servers automatically.

Setup Monthly Cost Tenant Capacity
1x CAX21 (current) ~€10.99 10-25 tenants
CAX21 + CAX31 worker ~€33 30-60 tenants
CAX21 + AX42 dedicated ~€200 80-150 tenants
3-node CAX21 cluster ~€33 40-60 tenants with HA

The jump from €10.99 to €33 roughly triples capacity. Going up to ~€200 gets you 10-15x more headroom with a dedicated bare metal worker. Compare that to scaling on AWS where adding a worker node costs $50-100/month.

The Full Monthly Bill

Line Item Monthly Cost
Hetzner CAX21 server €10.99 (~$11)
Cloudflare DNS $0 (free tier)
SSL certificates $0 (Let's Encrypt)
Container registry $0 (self-hosted)
Load balancer $0 (Traefik, self-hosted)
Managed database $0 (PostgreSQL in containers)
Email (Resend) $0 (free tier, up to 3k/month)
Domain (staxa.dev) ~$1.50/month amortized
Total ~$13/month

For comparison, a minimal equivalent stack on AWS: EKS control plane ($73) + t4g.medium worker ($25) + RDS micro ($12) + ALB ($16) + Route53 ($0.50) = ~$127/month. And that's before bandwidth, storage IOPS, or a second availability zone.

What I'd Do Differently

I'd start with an even smaller server. The CAX11 (2 vCPU, 4GB, ~€6.49/month) can technically run K3s + the platform services, but it's tight. I went with the CAX21 for breathing room during development. If I were optimizing purely for cost from day one, I'd start on the CAX11 and upgrade when the first 5 tenants were running.

I'd separate control plane and tenant workloads earlier. Right now everything shares one server. In hindsight, starting with a cheap control plane node (CAX11, €6.49) and a separate tenant worker (CAX21, €10.99) would give better isolation for ~€18 total. If a runaway tenant build pegs the CPU, it wouldn't affect the API and dashboard.

I wouldn't change the "no managed services" decision. Running databases in containers sounds heretical to some people, and I get it. RDS gives you automated backups, failover, and point-in-time recovery for free. But at the early stage, that $12/tenant/month cost floor would have fundamentally changed the economics. I can add managed database support as a premium tier later. The architecture supports it. The tenant database configuration is just a field in the API.


What's the cheapest infrastructure you've run something real on? I'm curious whether anyone else is pushing single-server setups further than "it's just a side project."


I'm Jonathan. I build infrastructure tools with Go and Kubernetes from Jamaica. Currently building Staxa, a multi-tenant deployment platform. Follow me for more Go and DevOps content.

Top comments (0)