Comparisons

Docker Swarm vs Kubernetes: which one for one to three servers?

Docker Swarm vs Kubernetes for one to three servers: setup, RAM, ports, the same app as a stack file and as manifests, k3s, and whether Swarm is dead.

Published ~12 min read
On this page

Short answer

Docker Swarm is built into Docker Engine: docker swarm init plus one join command per server gives you a cluster that runs Compose-style stack files. Kubernetes (or its small distribution k3s) costs more RAM and concepts but brings autoscaling and a huge ecosystem. For one to three servers, Swarm is simpler; pick k3s if you want Kubernetes itself.

Most comparisons of the two are written for platform teams with fifty nodes. This one is for the far more common case: a side project, a small business app or an agency stack on one to three VPS. It covers what each tool costs on a small server, which ports you have to open between machines, how the same app looks in both, and whether Swarm is still safe to build on in late 2026.

Docker Swarm vs Kubernetes at a glance

Simpler for 1 to 3 serversDocker SwarmKubernetes (k3s)
InstallNothing extra: part of Docker EngineOne install script per node (k3s); kubeadm or a managed service for upstream Kubernetes
Create a clusterdocker swarm init, then docker swarm join on each nodeInstall script on the server, then the same script with K3S_URL and K3S_TOKEN on each agent
Minimum per node (docs)No separate figure: the manager runs inside dockerdServer 2 cores and 2 GB RAM, agent 1 core and 512 MB
App definitionStack file: Compose format 3 with a deploy: sectionManifests: Deployment, Service, Ingress, PersistentVolumeClaim, often via Helm
Scalingdocker service scale web=5, by handReplicas, plus HorizontalPodAutoscaler on CPU or memory (metrics-server ships with k3s)
Rolling updatesYes, with parallelism, delay and automatic rollbackYes, with readiness probes, surge limits and kubectl rollout undo
NetworkingOverlay networks, routing mesh on every node, built-in DNSCNI plugin (flannel in k3s), Services with cluster DNS, Ingress (Traefik in k3s)
SecretsBuilt in, encrypted in the Raft log, mounted as filesSecret objects (base64 by default; encryption at rest is an option)
Survives losing one nodeWith 3 managersWith 3 servers (embedded etcd)
Monitoringdocker service logs and docker service ps, plus the Engine’s Prometheus metrics endpointkubectl top through the bundled metrics-server; Prometheus and Grafana via Helm
Documented scale limitNone published: Docker states no node limit, only that more managers slow down writesKubernetes v1.37: up to 5,000 nodes, 110 pods per node, 150,000 pods in total
EcosystemNo package format (no Helm); stacks are shared as YAML in project READMEsVery large: Helm charts, operators, every monitoring tool
Status in 2026Maintained in Docker Engine, few new featuresThree new minor releases a year, about one year of patches each
From docs.docker.com, docs.k3s.io and kubernetes.io, read 02.11.2026.

What Docker Swarm is today

“Docker Swarm” means two different things, and half the confusion online comes from mixing them up. The old standalone Classic Swarm was a separate program that is no longer developed. Swarm mode is the orchestrator built into Docker Engine itself since version 1.12, and that is what docker swarm init, docker service and docker stack use today.

A swarm has two kinds of nodes:

  • Managers keep the cluster state with the Raft consensus algorithm and schedule work. They also run containers unless you drain them.
  • Workers only run the tasks (containers) the managers assign.

Raft needs a majority of managers, so a swarm with N managers survives (N-1)/2 of them failing. That is the single most important number for a small cluster:

ManagersMajority neededManager failures tolerated
1 manager10
2 managers20
3 managers21
5 managers32

Two managers are worse than one: either can take the cluster down. Docker’s admin guide asks for an odd number. On three VPS, make all three managers; they still run your containers. If quorum is lost, containers that are already running keep running, but nothing can be scheduled, moved or updated until you recover with docker swarm init --force-new-cluster on a surviving manager.

What you get with no extra software

  • Services and replicas. Declare five copies of an image and the managers keep five running, rescheduling them when a node drops out.
  • Routing mesh. A published port is open on every node and forwarded to a healthy replica wherever it runs, so DNS can point at any node. mode: host publishing skips the mesh when you need the client IP.
  • Overlay networks and DNS. Services reach each other by name across hosts. Create the network with docker network create --driver overlay --opt encrypted when the traffic crosses the public internet (IPsec on the VXLAN traffic); Docker notes a non-negligible performance cost.
  • Rolling updates with rollback. update_config sets how many tasks change at a time, the pause between them and what happens on failure. The defaults are order: stop-first and failure_action: pause.
  • Secrets and configs. Secrets (up to 500 KB) are stored in the encrypted Raft log and appear in the container as files under /run/secrets/ on a tmpfs, never on the container’s disk. They work for services only, not for plain docker run containers.
  • Mutual TLS between nodes, with certificates the managers create and rotate themselves.

What Swarm does not have: autoscaling, a package manager like Helm, network policies, fine-grained access control, or storage that follows a container to another node. A named volume lives on the node where it was created.

Is Docker Swarm dead?

No, but it is in maintenance more than in growth. As of 2 November 2026:

  • Swarm mode still ships inside every Docker Engine release; the latest at the time of writing is Docker Engine 29.9.2 (published 20 October 2026 on the moby/moby GitHub repository).
  • SwarmKit, the library behind Swarm mode, still gets commits (the newest on 16 October 2026), mostly maintenance and dependency work rather than new features.
  • Mirantis, which bought Docker Enterprise in 2019, said on 1 July 2025 that Swarm will be “fully supported through at least 2030” as part of its Mirantis Kubernetes Engine 3.

So Swarm is a safe choice for a small cluster you want to run for years with little attention. It is a poor choice if you want features that do not exist yet, a large community writing guides for your exact setup, or a skill that shows up in job ads.

What Kubernetes (and k3s) adds

Kubernetes works from the same idea of desired state, with far more building blocks: Deployments and StatefulSets, Services and Ingress, PersistentVolumeClaims backed by storage classes, namespaces, role-based access control, network policies and the HorizontalPodAutoscaler. Around that sits the largest ecosystem in infrastructure: Helm charts for almost any open-source app, operators that run databases for you, and every monitoring and CI tool.

Upstream Kubernetes set up with kubeadm wants at least 2 GB of RAM per machine and 2 CPUs on control-plane machines, plus a network plugin, an ingress controller and a storage provisioner that you choose and install. k3s removes most of that work. It is a certified Kubernetes distribution in one binary that brings flannel, CoreDNS, the Traefik ingress controller, a ServiceLB load balancer, a local-path storage provisioner and metrics-server, and uses SQLite instead of etcd on a single server. Manifests and kubectl work as on any other cluster.

Key facts

k3s server minimum
2 cores, 2 GB RAM
k3s agent minimum
1 core, 512 MB RAM
Kubeconfig
/etc/rancher/k3s/k3s.yaml
Join token
/var/lib/rancher/k3s/server/node-token
Maintained Kubernetes minors
1.37, 1.36, 1.35kubernetes.io/releases, 02.11.2026
Patch support per minor
about 1 year

The price is upkeep. Kubernetes keeps only the three newest minor versions patched, so a cluster needs an upgrade every few months, and when a node dies its pods move only after the default 300-second toleration for an unreachable node, not instantly.

Setting up a three-node cluster: both commands

Both assume three Linux VPS that can reach each other, with the ports in the next section open between them (here 203.0.113.10, .11 and .12). Swarm needs Docker Engine on every node first (see installing Docker on Ubuntu and Debian); k3s brings its own container runtime (containerd) and does not need Docker at all.

Docker Swarm · Docker Engine on all three nodes
# node 1: create the swarm (use the IP the other nodes can reach)
docker swarm init --advertise-addr 203.0.113.10

# still on node 1: print the command for further managers
docker swarm join-token manager

# node 2 and node 3: paste that command, for example
docker swarm join --token SWMTKN-1-... 203.0.113.10:2377

# node 1: check
docker node ls
k3s · Debian, Ubuntu or Rocky Linux, as root
# node 1: install the server, with encrypted pod traffic (WireGuard)
curl -sfL https://get.k3s.io | sh -s - --flannel-backend=wireguard-native
cat /var/lib/rancher/k3s/server/node-token

# node 2 and node 3: join as agents
curl -sfL https://get.k3s.io | K3S_URL=https://203.0.113.10:6443 K3S_TOKEN=<token> sh -

# node 1: check
k3s kubectl get nodes -o wide

The k3s example joins two agents, so the single server is still a single point of failure for the control plane. A k3s cluster that survives losing a server needs three servers with embedded etcd, started with --cluster-init on the first, and every server then needs the 2 GB minimum and the same --flannel-backend flag. The Swarm example makes all three nodes managers, which costs nothing extra.

Encrypt the traffic between the nodes

Between most VPS, cluster traffic crosses public IP addresses unless the provider offers a private network, and neither tool encrypts container traffic by default. k3s uses flannel with VXLAN unless told otherwise; the --flannel-backend=wireguard-native flag above switches it to WireGuard, which encrypts all pod traffic between nodes. Flannel options are set on servers only, and every node, agents included, needs the WireGuard kernel module (part of Linux since 5.6). Swarm already encrypts its management traffic with mutual TLS, but application data on an overlay network is sent in plain VXLAN unless the network is created with the encrypted option, as in the stack file below. The routing mesh uses a separate ingress network, which Docker leaves unencrypted by default: recreate it with --ingress --opt encrypted before deploying services, or publish with mode: host. Only on a network you trust can you skip all of this.

Ports to open between the nodes

Docker Swarmk3s
Control plane2377/tcp (managers)6443/tcp (API server)
Node discovery / kubelet7946/tcp and 7946/udp10250/tcp (kubelet, metrics)
Container network4789/udp (VXLAN)8472/udp (flannel VXLAN), or 51820/udp with WireGuard
EncryptionIP protocol 50 (ESP) for encrypted overlaysBuilt into the WireGuard backend
High availabilitySame ports2379 to 2380/tcp between servers (embedded etcd)

Open these only to the other nodes’ IP addresses, never to the internet. Both projects warn that the VXLAN port has no authentication. With ufw, run this on every node (allowing a node’s own address does no harm):

ufw rules between the nodes · as root
# Docker Swarm
for ip in 203.0.113.10 203.0.113.11 203.0.113.12; do
  ufw allow from $ip to any port 2377 proto tcp
  ufw allow from $ip to any port 7946 proto tcp
  ufw allow from $ip to any port 7946 proto udp
  ufw allow from $ip to any port 4789 proto udp
  ufw allow proto esp from $ip to any          # encrypted overlays (IPsec)
done

# k3s with the WireGuard backend
for ip in 203.0.113.10 203.0.113.11 203.0.113.12; do
  ufw allow from $ip to any port 6443 proto tcp
  ufw allow from $ip to any port 10250 proto tcp
  ufw allow from $ip to any port 51820 proto udp   # 8472/udp with default VXLAN
done
# 2379:2380/tcp as well between servers with embedded etcd

# k3s also needs its pod and service networks, or pod DNS and networking break
ufw allow from 10.42.0.0/16 to any    # pods
ufw allow from 10.43.0.0/16 to any    # services

The k3s documentation recommends turning ufw or firewalld off altogether, and lists the two 10.42.0.0/16 and 10.43.0.0/16 rules (on firewalld: firewall-cmd --permanent --zone=trusted --add-source= for each) for when you keep it on. On a VPS with a public IP, keep the firewall and add the rules; if you change the pod or service CIDR, change them too. To run kubectl from your own computer, also allow 6443/tcp from your address. Remember also that ports a Swarm service or a container publishes go through Docker’s own iptables rules, so a ufw rule alone does not block them; the Docker install guide shows the fix.

The same app as a stack file and as Kubernetes manifests

A web service with three replicas and a Redis with a persistent volume, exposed on port 80. First as a Swarm stack:

stack.yaml · docker stack deploy -c stack.yaml app
version: "3.8"            # stack deploy reads the Compose file format 3

services:
  web:
    image: ghcr.io/example/web:1.4.2
    ports:
      - "80:8080"           # routing mesh: port 80 on every node
    environment:
      REDIS_URL: redis://redis:6379
    networks: [app]
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        failure_action: rollback
      restart_policy:
        condition: on-failure

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    networks: [app]
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.db == true   # the volume lives on that node

networks:
  app:
    driver: overlay
    driver_opts:
      encrypted: "true"    # IPsec between nodes (allow ESP, IP protocol 50)

volumes:
  redis-data:
deploy and inspect
docker node update --label-add db=true node1
docker stack deploy -c stack.yaml app
docker stack services app
docker service ps app_web

Now the same app for k3s:

app.yaml · k3s kubectl apply -f app.yaml
apiVersion: apps/v1
kind: Deployment
metadata: { name: web }
spec:
  replicas: 3
  selector: { matchLabels: { app: web } }
  strategy:
    rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
  template:
    metadata: { labels: { app: web } }
    spec:
      containers:
        - name: web
          image: ghcr.io/example/web:1.4.2
          ports: [{ containerPort: 8080 }]
          env: [{ name: REDIS_URL, value: "redis://redis:6379" }]
          readinessProbe: { httpGet: { path: /healthz, port: 8080 } }
---
apiVersion: v1
kind: Service
metadata: { name: web }
spec:
  selector: { app: web }
  ports: [{ port: 80, targetPort: 8080 }]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: { name: web }
spec:
  ingressClassName: traefik
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend: { service: { name: web, port: { number: 80 } } }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: redis-data }
spec:
  accessModes: [ReadWriteOnce]
  resources: { requests: { storage: 1Gi } }
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: redis }
spec:
  replicas: 1
  strategy: { type: Recreate }
  selector: { matchLabels: { app: redis } }
  template:
    metadata: { labels: { app: redis } }
    spec:
      containers:
        - name: redis
          image: redis:7-alpine
          volumeMounts: [{ name: data, mountPath: /data }]
      volumes:
        - name: data
          persistentVolumeClaim: { claimName: redis-data }
---
apiVersion: v1
kind: Service
metadata: { name: redis }
spec:
  selector: { app: redis }
  ports: [{ port: 6379 }]

What the side by side shows:

  • Six objects instead of two services. Kubernetes splits what a stack file says in one place into a Deployment, a Service, an Ingress and a claim, which is more text but also more control (the readiness probe, for example, has no direct Swarm equivalent beyond a container healthcheck).
  • Port 80 works differently. Swarm’s routing mesh opens it on every node. In k3s the bundled Traefik already listens on 80 and 443 through ServiceLB, so the web app gets an Ingress instead of its own load balancer.
  • Storage is pinned in both. The Swarm label constraint and k3s’s default local-path volumes both tie Redis to one node. Moving data between nodes needs replicated storage (Longhorn or Ceph on Kubernetes), which is a system of its own.
  • Images come from a registry in both. docker stack deploy ignores build:, and Kubernetes has no build step at all, because every node pulls the image itself. Push it to a registry first.

How much RAM each one costs on a small VPS

This is where the two differ most for one to three servers, and where the numbers are hardest to find. What the projects themselves publish:

FigureSource
Swarm manager or workerNo separate figure: Swarm runs inside the dockerd process you already havedocs.docker.com (no requirement listed)
k3s server, minimum2 GB RAM, 2 coresdocs.k3s.io requirements
k3s agent, minimum512 MB RAM, 1 coredocs.k3s.io requirements
k3s server with one agent, no workload1,428 MB (SQLite), 1,450 MB (embedded etcd)docs.k3s.io resource profiling, k3s v1.26.5, 4-core Intel cloud VM, 95th percentile
k3s server with a workload1,596 MB (SQLite), 1,606 MB (embedded etcd)same profiling; Prometheus, Grafana and a small demo app with all bundled components
k3s agent275 MBsame profiling
kubeadm (upstream Kubernetes)2 GB RAM per machine, 2 CPUs on control-plane machineskubernetes.io kubeadm requirements

Read that against a small plan. On a VPS with 4 GB of RAM, a k3s server can take over a third of the memory before your own containers start; with three servers for high availability, each node pays that again. Swarm adds its Raft store and scheduler to a daemon you run anyway, which is why Docker lists no requirement for it. We have not benchmarked the two side by side for this article, so measure your own node rather than trusting anyone’s round number:

measure on your own node
# 1. before: Docker installed but no swarm yet, or a node without k3s
free -m

# 2. docker swarm init, or the k3s install; wait a few idle minutes
free -m                    # compare the "used" column of both runs

# 3. where the memory sits, sorted by memory
systemd-cgtop -m           # docker.service + containerd.service, or k3s.service + kubepods
k3s kubectl get pods -A    # the bundled pods k3s.service does not include

The difference in the used column of free -m between the two runs is the number to compare, on an idle node with no app containers. Per-service figures mislead in both directions. k3s.service holds the server and its own containerd, but CoreDNS, Traefik, metrics-server, the local-path provisioner and the ServiceLB pods run as containers under the kubepods cgroup, so systemctl status k3s alone shows too little. And the Memory: value systemd prints for a service is cgroup memory, which includes page cache the kernel can drop again, so it is often far above what the processes themselves use.

When plain Docker Compose is enough

On a single server, neither orchestrator buys you much. A compose.yaml with restart policies, healthchecks and named volumes already restarts crashed containers and brings everything back after a reboot. What one server cannot give you is survival of that server, and a one-node swarm or k3s install does not change that. Compose is enough when:

  • everything fits on one VPS (scale up to more cores and RAM before you scale out);
  • a minute of downtime for a reboot or an update is acceptable;
  • the data lives in one database that you back up to another machine;
  • one or two people run it.

The usual path is Compose on one server, then Swarm when a second or third server appears, because the stack file is almost the same YAML. Move to Kubernetes when you need what only its ecosystem has, not because the cluster grew from one node to three. For running single containers, the docker run guide covers ports, variables and volumes, and Docker image vs container explains what every node pulls and runs.

Moving from Swarm to Kubernetes later

Starting with Swarm does not lock you in. kompose -f stack.yaml convert reads a stack file and writes Kubernetes manifests: replicas become Deployment replicas, placement constraints become node affinity, update_config becomes the rollout strategy, ports become Services and volumes become PersistentVolumeClaims. Read the output before you apply it. Per kompose’s conversion guide, the restart policy decides the object type (on-failure produces a bare Pod instead of a Deployment, so remove restart_policy first), an Ingress appears only with the kompose.service.expose label, and there is no readiness probe unless you add one. Swarm and k3s are not the only small options either: MicroK8s and k0s are other compact Kubernetes distributions, and HashiCorp Nomad is a scheduler outside the Kubernetes world.

Pros and cons

Docker Swarm: strengths

  • Already in Docker Engine: no extra install, no extra memory budget
  • Stack files are Compose files with a deploy: section
  • Three managers give high availability on three small VPS
  • Secrets, TLS between nodes and rolling updates with rollback built in
  • Long-term maintenance, with commercial support promised to at least 2030

Docker Swarm: trade-offs

  • Few new features; a small and shrinking community
  • No autoscaling, no Helm, no network policies
  • Ignores build, depends_on and other Compose keys
  • Volumes stay on the node they were created on

Kubernetes / k3s: strengths

  • The industry standard: Helm charts and operators for almost everything
  • Autoscaling, readiness probes, namespaces and RBAC
  • k3s installs in one command and includes ingress, storage and metrics
  • Skills and manifests carry over to any managed Kubernetes

Kubernetes / k3s: trade-offs

  • 2 GB minimum per k3s server; about 1.4 GB used before your apps in the project's own profiling
  • Upgrades every few months to stay on a patched version
  • Six objects where a stack file has two services
  • HA needs three servers, each paying the control-plane memory

Docker Swarm or Kubernetes: the verdict

Which one should you pick?

  • Pick Docker Compose if everything runs on one server; add a backup instead of a cluster1 server
  • Pick Docker Swarm if you have two or three servers, already use Compose and want high availability with the least to learn and run2 to 3 servers
  • Pick k3s if you want to learn Kubernetes, need Helm charts or operators, or will move to a managed Kubernetes later
  • Pick Managed or upstream Kubernetes if several teams ship many services, or the workload needs autoscaling across many nodes
Whichever you pick, give each node enough headroom: a k3s server on a 2 GB VPS has almost nothing left for your apps.

Frequently asked

Is Docker Swarm dead?

No. Swarm mode is still part of every Docker Engine release, the SwarmKit project behind it still receives maintenance commits, and Mirantis has said Swarm will be fully supported through at least 2030 in Mirantis Kubernetes Engine 3. It gets few new features, though, so it suits small clusters that should run quietly for years more than projects that need a growing ecosystem.

Is Docker Swarm easier than Kubernetes?

Yes. Swarm is built into Docker Engine: docker swarm init on one server and the printed join command on the others create a cluster, and apps are deployed from Compose-style stack files. Kubernetes, even as k3s, adds its own objects (Deployments, Services, Ingress, volume claims), its own tooling and regular version upgrades.

Can Docker Swarm use a docker-compose.yml file?

Mostly. docker stack deploy -c compose.yaml NAME reads the Compose file format 3 and uses its deploy: section for replicas, placement and rolling updates. It ignores build, container_name, depends_on, links, network_mode and restart, so images must come from a registry and restarts are set with deploy.restart_policy.

Is k3s better than Docker Swarm?

It depends on what you need. k3s is real Kubernetes with Helm charts, autoscaling and a large ecosystem, but its documentation asks for 2 cores and 2 GB of RAM per server node. Swarm adds no separate service to Docker and is simpler to run on two or three small servers. Pick k3s to learn or use Kubernetes, Swarm for the least work.

Can Docker Swarm autoscale?

Not by itself. Swarm keeps the number of replicas you set and reschedules them when a node fails, but it has no built-in autoscaler: you scale with docker service scale web=5 or by changing replicas in the stack file. Kubernetes has the HorizontalPodAutoscaler, which k3s can use out of the box because it ships metrics-server.

Related articles

All comparisons