Short answer
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 Swarm | Kubernetes (k3s) | |
|---|---|---|
| Install | Nothing extra: part of Docker Engine | One install script per node (k3s); kubeadm or a managed service for upstream Kubernetes |
| Create a cluster | docker swarm init, then docker swarm join on each node | Install 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 dockerd | Server 2 cores and 2 GB RAM, agent 1 core and 512 MB |
| App definition | Stack file: Compose format 3 with a deploy: section | Manifests: Deployment, Service, Ingress, PersistentVolumeClaim, often via Helm |
| Scaling | docker service scale web=5, by hand | Replicas, plus HorizontalPodAutoscaler on CPU or memory (metrics-server ships with k3s) |
| Rolling updates | Yes, with parallelism, delay and automatic rollback | Yes, with readiness probes, surge limits and kubectl rollout undo |
| Networking | Overlay networks, routing mesh on every node, built-in DNS | CNI plugin (flannel in k3s), Services with cluster DNS, Ingress (Traefik in k3s) |
| Secrets | Built in, encrypted in the Raft log, mounted as files | Secret objects (base64 by default; encryption at rest is an option) |
| Survives losing one node | With 3 managers | With 3 servers (embedded etcd) |
| Monitoring | docker service logs and docker service ps, plus the Engine’s Prometheus metrics endpoint | kubectl top through the bundled metrics-server; Prometheus and Grafana via Helm |
| Documented scale limit | None published: Docker states no node limit, only that more managers slow down writes | Kubernetes v1.37: up to 5,000 nodes, 110 pods per node, 150,000 pods in total |
| Ecosystem | No package format (no Helm); stacks are shared as YAML in project READMEs | Very large: Helm charts, operators, every monitoring tool |
| Status in 2026 | Maintained in Docker Engine, few new features | Three new minor releases a year, about one year of patches each |
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:
| Managers | Majority needed | Manager failures tolerated |
|---|---|---|
| 1 manager | 1 | 0 |
| 2 managers | 2 | 0 |
| 3 managers | 2 | 1 |
| 5 managers | 3 | 2 |
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: hostpublishing 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 encryptedwhen the traffic crosses the public internet (IPsec on the VXLAN traffic); Docker notes a non-negligible performance cost. - Rolling updates with rollback.
update_configsets how many tasks change at a time, the pause between them and what happens on failure. The defaults areorder: stop-firstandfailure_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 plaindocker runcontainers. - 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.
# 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# 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 wideThe 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 Swarm | k3s | |
|---|---|---|
| Control plane | 2377/tcp (managers) | 6443/tcp (API server) |
| Node discovery / kubelet | 7946/tcp and 7946/udp | 10250/tcp (kubelet, metrics) |
| Container network | 4789/udp (VXLAN) | 8472/udp (flannel VXLAN), or 51820/udp with WireGuard |
| Encryption | IP protocol 50 (ESP) for encrypted overlays | Built into the WireGuard backend |
| High availability | Same ports | 2379 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):
# 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 # servicesThe 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:
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:docker node update --label-add db=true node1
docker stack deploy -c stack.yaml app
docker stack services app
docker service ps app_webNow the same app for k3s:
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 deployignoresbuild:, 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:
| Figure | Source | |
|---|---|---|
| Swarm manager or worker | No separate figure: Swarm runs inside the dockerd process you already have | docs.docker.com (no requirement listed) |
| k3s server, minimum | 2 GB RAM, 2 cores | docs.k3s.io requirements |
| k3s agent, minimum | 512 MB RAM, 1 core | docs.k3s.io requirements |
| k3s server with one agent, no workload | 1,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 workload | 1,596 MB (SQLite), 1,606 MB (embedded etcd) | same profiling; Prometheus, Grafana and a small demo app with all bundled components |
| k3s agent | 275 MB | same profiling |
| kubeadm (upstream Kubernetes) | 2 GB RAM per machine, 2 CPUs on control-plane machines | kubernetes.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:
# 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 includeThe 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
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.