- Running Kubernetes on cloud VMs means paying a virtualization tax on every CPU cycle and fighting noisy-neighbor network jitter between nodes.
- This guide covers sizing dedicated bare-metal nodes for Docker and Kubernetes clusters, real resource-request math, and the networking decisions that make or break a self-hosted cluster.
Kubernetes on cloud VM instances carries a hidden cost most teams never fully account for: the underlying VM is already a virtualized slice of a physical host, so every container running inside it is virtualized twice over, and cross-node network traffic between cloud VM instances is subject to the same noisy-neighbor jitter as any other cloud workload. Running Docker and Kubernetes directly on dedicated bare-metal servers removes a full layer of virtualization overhead and gives you predictable, low-latency networking between nodes — which matters enormously for stateful workloads, databases running inside the cluster, and any service sensitive to inter-pod network latency. This guide covers real node sizing math, resource request/limit planning, storage classes, and cluster networking for bare-metal Kubernetes.
Bare Metal Kubernetes vs Managed Cloud Kubernetes: The Real Trade-offs
| Factor | Bare Metal on Dedicated Servers | Managed Cloud Kubernetes (EKS/GKE/AKS) |
|---|---|---|
| Raw compute performance per dollar | Higher — no hypervisor tax, full CPU/RAM/NVMe access | Lower — cloud VM virtualization overhead plus provider margin |
| Operational burden | You own control-plane upgrades, etcd health, node OS patching | Control plane is managed for you; you focus on workloads |
| Network latency between nodes | Consistently low with a private network/VLAN between dedicated servers | Generally good but subject to cloud provider's underlying network conditions |
| Storage performance for stateful workloads | Direct NVMe access, no cloud block-storage abstraction layer latency | Cloud block storage (EBS-class) typically has higher latency than local NVMe |
| Scaling elasticity | Requires provisioning new dedicated nodes in advance (hours, not seconds) | Near-instant autoscaling of nodes and pods |
The practical takeaway: dedicated bare-metal Kubernetes wins decisively on cost-per-compute and storage/network performance for steady-state workloads, while managed cloud Kubernetes wins on elastic burst scaling. Many teams run a bare-metal dedicated cluster for baseline steady-state load and burst into cloud capacity only for genuine spikes — though for most small-to-mid workloads, a well-sized dedicated cluster alone is simpler and cheaper.
Choosing a Kubernetes Distribution for Bare Metal
| Distribution | Footprint | Best Fit |
|---|---|---|
| kubeadm (vanilla upstream Kubernetes) | Full-featured, more manual setup | Teams wanting full upstream compatibility and control over every component |
| K3s | Lightweight, single binary, strips some default components | Small-to-mid clusters, edge deployments, teams wanting fast setup with lower resource overhead |
| RKE2 (Rancher Kubernetes Engine) | Security-hardened, CIS-benchmark-focused defaults | Teams with compliance requirements wanting hardened defaults out of the box |
| Talos Linux | Immutable, API-managed OS purpose-built for Kubernetes | Teams wanting a minimal-attack-surface, declarative-only node OS |
For most self-managed bare-metal clusters on dedicated servers, K3s has become a popular default for small-to-mid clusters because of its minimal resource footprint (control plane components use meaningfully less RAM than vanilla Kubernetes), while kubeadm remains the choice for teams wanting exact upstream parity for compatibility with specific operators or Helm charts that assume a standard Kubernetes distribution.
Sizing Dedicated Nodes for a Kubernetes Cluster
Control Plane Nodes
| Cluster Size | Control Plane Nodes | Per-Node CPU/RAM |
|---|---|---|
| Small (under 10 worker nodes) | 1 (non-HA) or 3 (HA) | 2-4 core, 8-16 GB |
| Medium (10-50 worker nodes) | 3 (HA required at this scale) | 4-8 core, 16-32 GB |
| Large (50+ worker nodes) | 3-5, with etcd potentially on separate dedicated nodes | 8-16 core, 32-64 GB, fast NVMe for etcd specifically |
etcd, Kubernetes' cluster state store, is extremely latency-sensitive to disk write performance — always place etcd's data directory on NVMe, never on network-attached or slow storage, since etcd write latency directly affects overall cluster API responsiveness and, at the extreme, cluster stability.
Worker Nodes
| Workload Profile | Per-Node CPU | Per-Node RAM | Storage | Est. Monthly Cost per Node |
|---|---|---|---|---|
| Small microservices fleet, light traffic | 4-8 core | 16-32 GB | 500 GB-1 TB NVMe | $85-$160 |
| Mid-size production workloads, moderate stateful services | 8-16 core | 32-64 GB | 1-2 TB NVMe | $180-$320 |
| Large-scale production, heavy stateful workloads (databases, message queues in-cluster) | 16-32 core | 64-128 GB | 2-4 TB NVMe RAID 10 | $350-$650 |
Resource Requests and Limits: The Math That Actually Matters
Kubernetes schedules pods based on their declared requests, not their actual usage — a node's "allocatable" capacity is what determines how many pods can be scheduled, and undersizing requests leads to overcommitment and noisy-neighbor problems between pods on the same node, exactly the problem bare metal was supposed to solve.
A Practical Sizing Example
A worker node with 32 GB RAM and 8 cores, after subtracting roughly 1-2 GB and 0.5-1 core for the OS and kubelet/container runtime overhead, has approximately 30 GB and 7 cores of allocatable capacity. If each application pod requests 512 MB RAM and 250m CPU, that node can schedule roughly 55-60 pods purely on RAM requests, but real-world limits are usually reached sooner due to CPU request accumulation and per-node pod-count caps (default 110 pods per node in most distributions).
Requests vs Limits
- Requests — what the scheduler guarantees is reserved for the pod; set this close to actual typical usage, not a padded guess, so scheduling capacity isn't wasted.
- Limits — the hard ceiling the pod cannot exceed; setting CPU limits too aggressively can cause CPU throttling even when the node has spare capacity, a frequently misunderstood Kubernetes behavior worth testing under real load before finalizing.
Storage Classes for Bare-Metal Kubernetes
Cloud Kubernetes gets dynamic block storage provisioning for free (EBS, Persistent Disk); bare-metal clusters need an explicit choice:
| Storage Solution | Approach | Best Fit |
|---|---|---|
| Local Path Provisioner | Uses local node NVMe directly, no replication | Simple stateful workloads where the application handles its own replication (e.g., a database with its own clustering) |
| Longhorn | Distributed block storage replicated across nodes | Teams wanting cloud-like dynamic PVC provisioning with built-in replication on bare metal |
| Rook/Ceph | Full distributed storage cluster (block, object, and file) | Larger clusters needing enterprise-grade distributed storage with strong consistency guarantees |
| NFS-backed storage | Centralized NFS server providing shared volumes | Simpler shared-storage needs where distributed replication isn't required |
Local NVMe with an application-level replication strategy (e.g., a StatefulSet running a clustered database like PostgreSQL with Patroni, or a replicated message queue) frequently outperforms distributed storage layers like Longhorn or Ceph for latency-sensitive workloads, at the cost of needing the application itself to handle replication rather than relying on the storage layer.
Networking Between Dedicated Nodes
Private Network / VLAN
Cluster inter-node traffic (pod-to-pod, kubelet-to-API-server, etcd replication) should run over a private network between your dedicated servers, not the public internet — most dedicated server providers support a private VLAN between servers in the same data center at no or low additional cost, and this should be a non-negotiable requirement for any multi-node bare-metal cluster.
CNI Plugin Choice
| CNI Plugin | Characteristics |
|---|---|
| Cilium | eBPF-based, strong network policy enforcement and observability, increasingly the modern default |
| Calico | Mature, widely used, solid network policy support with either BGP or overlay networking modes |
| Flannel | Simplest to set up, minimal feature set, good for smaller clusters without complex network policy needs |
Backup and Disaster Recovery for a Bare-Metal Cluster
Managed cloud Kubernetes typically bundles etcd snapshotting and control-plane recovery tooling; a bare-metal cluster needs this built and tested deliberately.
etcd Snapshots
Schedule regular etcdctl snapshot save backups (or the equivalent built into your chosen distribution) to storage outside the cluster itself, since a snapshot stored only on the same nodes that might fail provides no real protection.
Application-Level Backups
For stateful workloads using local NVMe with application-level replication, back up at the application layer (database dumps, replicated storage snapshots) rather than relying solely on Kubernetes-level primitives, which capture pod and configuration state but not necessarily consistent application data snapshots.
Testing Recovery, Not Just Taking Backups
A snapshot that has never been restored in a drill is an unverified assumption, not a backup — periodically practice restoring etcd and critical stateful workloads onto a scratch cluster to confirm the recovery process actually works end to end before you need it during a real incident.
Node Failure vs Cluster Failure
Design for the far more common failure mode first: a single worker node going offline (hardware fault, network blip, OS crash) should be handled automatically by Kubernetes rescheduling affected pods elsewhere, assuming sufficient spare capacity exists on remaining nodes. A full cluster-level failure (all control-plane nodes down simultaneously) is rarer but should still have a documented, tested recovery runbook rather than being figured out for the first time mid-incident.
High Availability and Rack-Level Fault Tolerance
Bare-metal dedicated servers, unlike cloud VM instances, are tied to specific physical hardware in a specific rack — a consideration cloud users rarely think about but that matters directly for bare-metal cluster design.
- Spread control-plane nodes across separate physical hosts at minimum — never run all three HA control-plane nodes as VMs on the same underlying physical dedicated server, which defeats the purpose of a 3-node HA quorum entirely.
- Ask your provider about power and network redundancy per rack — a data center with redundant power feeds and network uplinks per rack reduces the chance that a single rack-level fault takes out multiple nodes simultaneously.
- Consider spreading nodes across two facilities for critical workloads — at the cost of higher inter-node latency, running a subset of nodes in a second facility protects against a full single-facility outage, though this adds real complexity to etcd's latency-sensitive consensus protocol and should be tested carefully before relying on it.
Setting Up a Bare-Metal Kubernetes Cluster: Step by Step
1. Provision Dedicated Nodes and Private Networking
Order dedicated servers in the same data center with a private VLAN connecting them, and confirm private-network throughput and latency between nodes before proceeding.
2. Install a Container Runtime
Install containerd (the standard runtime for current Kubernetes versions, since dockershim was removed) on every node.
3. Bootstrap the Control Plane
Use kubeadm, K3s's built-in server mode, or RKE2 to initialize the control plane, generating the join tokens needed for worker nodes.
4. Join Worker Nodes
Run the generated join command on each worker node, confirming each joins successfully with kubectl get nodes.
5. Install a CNI Plugin
Deploy your chosen CNI (Cilium, Calico, or Flannel) so pod-to-pod networking functions across nodes.
6. Install a Storage Provisioner
Deploy Longhorn, Rook/Ceph, or configure local-path provisioning depending on your stateful workload requirements.
7. Set Up Ingress and Monitoring
Install an ingress controller (NGINX Ingress or Traefik are common defaults) for routing external traffic, and deploy Prometheus/Grafana for cluster and workload monitoring.
GitOps and Deployment Workflow
Bare-metal clusters benefit as much from GitOps discipline as cloud clusters do — arguably more, since there is no cloud console fallback to manually patch a misconfigured resource.
Declarative Deployment with Argo CD or Flux
Rather than applying manifests manually via kubectl apply, a GitOps controller (Argo CD or Flux) continuously reconciles the cluster's actual state against manifests stored in a Git repository, automatically correcting drift and providing a clear audit trail of every change that reached the cluster.
Helm for Reusable Application Packaging
Most production workloads and third-party components (ingress controllers, monitoring stacks, storage provisioners) are distributed as Helm charts; standardizing on Helm plus a values-per-environment convention keeps configuration consistent across your dedicated node fleet as it grows.
Observability for a Self-Managed Cluster
Cloud-managed Kubernetes often bundles basic monitoring; a bare-metal cluster needs this built deliberately.
| Layer | Typical Tooling | What to Watch |
|---|---|---|
| Node-level metrics | node_exporter + Prometheus | CPU/RAM/disk saturation per physical dedicated server, not just per pod |
| Cluster/workload metrics | kube-state-metrics + Prometheus + Grafana | Pod restart counts, pending pod counts, resource request vs actual usage per workload |
| Logs | Loki, or the ELK/OpenSearch stack | Centralized application and control-plane logs across all nodes, since SSHing into individual dedicated servers to tail logs does not scale past a handful of nodes |
| etcd health specifically | etcd's built-in Prometheus metrics endpoint | Disk write latency (wal_fsync_duration_seconds) and leader election frequency — both early indicators of storage or network problems |
Multi-Tenancy on a Shared Bare-Metal Cluster
Running multiple teams or applications on one bare-metal cluster requires deliberate isolation, since Kubernetes' default posture does not isolate namespaces from each other without explicit configuration.
- Namespace-based separation with ResourceQuotas — set per-namespace CPU/memory quotas so one team's workload cannot consume the entire node fleet's capacity and starve others.
- NetworkPolicies for pod-to-pod isolation — by default, any pod can reach any other pod across namespaces; explicit NetworkPolicy rules (supported by Cilium and Calico) are required to restrict cross-namespace or cross-team traffic.
- Dedicated node pools for sensitive or noisy workloads — use taints and tolerations to reserve specific dedicated nodes for workloads with special requirements (GPU workloads, compliance-isolated data), preventing unrelated pods from being scheduled there.
Common Bare-Metal Kubernetes Issues
- Pods pending with "insufficient CPU/memory" — check actual node allocatable capacity versus the sum of all scheduled pod requests; this is a scheduling math problem, not necessarily a real resource shortage.
- etcd latency warnings in logs — almost always a disk latency issue; confirm etcd's data directory is on genuinely fast NVMe, not a slower shared volume.
- Cross-node pod networking not working — verify the CNI plugin is correctly installed and that any required kernel modules (e.g., for BGP-mode Calico) and firewall rules for the CNI's overlay/VXLAN ports are in place.
- CPU throttling despite apparent spare node capacity — check pod CPU limits; Kubernetes' CFS-based CPU throttling can throttle a pod that hits its limit even when the node has idle CPU cycles available.
- Node NotReady after a reboot — confirm kubelet and container runtime services are configured to start automatically on boot, and that the private network interface used for cluster communication comes up before the kubelet service starts.
- One team's workload starving another's on a shared cluster — apply ResourceQuotas and, if contention persists, move the noisy workload to a dedicated tainted node pool rather than continually raising shared-namespace quotas.
- Configuration drift between what's deployed and what's in Git — adopt a GitOps controller so manual
kubectlchanges get reconciled back to the Git-defined state rather than silently diverging over time.
Docker/Kubernetes Dedicated Server Buyer's Checklist
- Does the provider support a private network/VLAN between multiple dedicated servers at reasonable cost?
- Is NVMe storage available and fast enough for etcd and any latency-sensitive stateful workloads?
- Can you provision additional identical nodes quickly to scale the cluster horizontally?
- Does the provider give full root/kernel-level access needed for CNI plugins and container runtime configuration?
- Is there IPMI/KVM remote access for troubleshooting a node that becomes unreachable over the network?
- What is the actual network bandwidth and latency between nodes if they are ever provisioned in different racks or facilities?
Frequently Asked Questions
Do I need Kubernetes, or is plain Docker enough for my workload?
For a handful of services on one or two servers, Docker Compose is simpler and sufficient. Kubernetes earns its operational complexity once you need multi-node scheduling, self-healing, rolling deployments across many services, or are already standardizing on Kubernetes-native tooling.
How many dedicated servers do I need for a production Kubernetes cluster?
A minimum viable production setup is typically 3 control-plane nodes (for HA quorum) plus 2 or more worker nodes; smaller non-critical clusters can run a single combined control-plane-and-worker node, accepting reduced fault tolerance.
Is bare-metal Kubernetes harder to operate than managed cloud Kubernetes?
Yes, meaningfully — you own control-plane upgrades, etcd health, and node OS patching that a managed service handles for you. The trade-off is lower cost and better raw performance per dollar, which is why many teams choose managed Kubernetes despite the premium until scale or cost justifies the operational investment in bare metal.
What CNI plugin should I use for a new bare-metal cluster?
Cilium is a strong modern default for most new clusters due to its eBPF-based performance and built-in observability; Calico remains a solid, mature alternative, particularly if your team already has Calico operational experience.
How much RAM overhead does Kubernetes itself use per node?
Budget roughly 1-2 GB for the OS, kubelet, and container runtime combined on a typical worker node, plus additional overhead for any DaemonSets you run cluster-wide (monitoring agents, log shippers, CNI components) — this reduces your actual allocatable capacity below the node's raw RAM figure.
Can I mix dedicated servers from different data centers in one cluster?
Technically possible, but strongly discouraged for latency-sensitive components like etcd — cross-facility network latency and reduced bandwidth compared to an intra-facility private VLAN can cause cluster instability; keep control-plane nodes within the same low-latency network whenever possible.
Is Longhorn or local storage better for my Kubernetes cluster?
Local NVMe storage with application-level replication offers the best raw performance for latency-sensitive stateful workloads. Longhorn or Rook/Ceph are better choices when you need dynamic PVC provisioning with storage-layer replication and your applications don't handle their own data replication.
How do I monitor a bare-metal Kubernetes cluster without a cloud provider's built-in dashboard?
Deploy Prometheus with node_exporter and kube-state-metrics for metrics, Grafana for dashboards, and a log aggregator like Loki — this combination replicates most of what managed cloud Kubernetes offerings provide out of the box, at the cost of setting it up and maintaining it yourself.
Can multiple teams safely share one bare-metal Kubernetes cluster?
Yes, with deliberate isolation — namespace-based ResourceQuotas prevent one team from consuming the whole cluster's capacity, and NetworkPolicies restrict which namespaces can reach each other, since Kubernetes does not isolate namespaces from each other by default.
What is the benefit of GitOps for a self-managed cluster?
A GitOps controller like Argo CD or Flux continuously reconciles the cluster against manifests stored in Git, which both provides an audit trail of every deployed change and automatically corrects manual configuration drift — particularly valuable on bare metal, where there is no cloud console to fall back on for quick fixes.
How do I achieve high availability across physical dedicated servers, not just virtual nodes?
Spread control-plane and critical worker nodes across genuinely separate physical dedicated servers, ideally in different racks with independent power and network paths, so a single hardware or rack-level fault cannot take down cluster quorum — this is a consideration that does not exist on cloud VM-based clusters, where the provider abstracts physical placement for you.
Should I back up etcd separately from my application data?
Yes — etcd snapshots capture cluster and configuration state, while stateful application data (databases, message queues) needs its own application-level backup strategy; relying on etcd snapshots alone will not restore your actual application data after a serious incident.
Bare-metal Kubernetes on properly sized dedicated hardware delivers performance and cost efficiency that virtualized cloud nodes cannot match for steady-state workloads. WebsNP's dedicated server plans support the private networking and NVMe storage a production cluster needs — compare against VPS options for smaller test clusters, or contact our team to plan node sizing and private networking for your cluster topology.