Deploying containers: PaaS, Docker Swarm and Kubernetes
Take the image you built to production: hand it to a platform that runs it for you, spread it across machines with Docker Swarm, or move to Kubernetes, and know which fits your team.
By the end of this lesson you'll be able to
0 of 5 completeSame image, four homes
Everything so far produced one thing: an image in a registry. Where it runs is a separate decision, mostly about how much you want to operate yourself:
One server with Compose (lesson 9) is where many products start: docker compose pull && docker compose up -d on a VM, restart policies, backups of the volumes. It's cheap and easy to understand. Its limit: one
machine is one failure, and a deploy restarts containers on it.
Platforms that run it for you
Container platforms such as Google Cloud Run, AWS App Runner and ECS on Fargate, Azure Container Apps, Fly.io and Render take an image and run it: TLS, load balancing, scaling, restarts and logs are theirs. You give them:
- the image reference, ideally by digest or SHA tag (lesson 8), and registry credentials;
- the port your app listens on. Many platforms tell the app with a
PORTenvironment variable, so read it instead of hard-coding 8080; - configuration and secrets, set in the platform, not baked in (lesson 11);
- a health check path such as
/health, so bad releases are stopped before they get traffic; - CPU, memory and instance limits, which decide the bill.
The contract is the same everywhere, and it's what the earlier lessons built: log to stdout, keep no state on the container's disk (use a managed database and object storage), start fast, and exit cleanly on SIGTERM, because instances are replaced at any time.
Docker Swarm
Swarm mode is built into Docker: it turns several machines into one cluster and runs services, each with a number of replicas. On one machine you can try it in a minute:
$ docker swarm init --advertise-addr 127.0.0.1 Swarm initialized: current node (ucnmsfuxg904oob5mjus3gxm3) is now a manager. $ docker service create -d --name api --replicas 3 -p 8090:8080 -e APP_VERSION=1.0.0 price-api:multi $ docker service ls NAME MODE REPLICAS IMAGE PORTS api replicated 3/3 price-api:multi *:8090->8080/tcp
docker swarm init --advertise-addr 127.0.0.1dockerbuild and run containersswarmmanage swarm modeinitturn this machine into a swarm manager
docker service create -d --name api --replicas 3 -p 8090:8080 -e APP_VERSION=1.0.0 price-api:multiservicemanage swarm servicescreatecreate one-drun in the background--name apiname the container-p 8090:8080publish a port, host:container-e APP_VERSION=1.0.0set an environment variable inside
docker service lslslist them
A rolling update replaces replicas one at a time:
$ docker service update --env-add APP_VERSION=1.1.0 --update-parallelism 1 --update-delay 2s api $ docker service ps api --format 'table {{.Name}}\t{{.CurrentState}}\t{{.DesiredState}}' NAME CURRENT STATE DESIRED STATE api.1 Running 20 seconds ago Running \_ api.1 Shutdown 21 seconds ago Shutdown api.2 Running 7 seconds ago Running \_ api.2 Shutdown 8 seconds ago Shutdown api.3 Running 14 seconds ago Running \_ api.3 Shutdown 15 seconds ago Shutdown
docker service update --env-add APP_VERSION=1.1.0 --update-parallelism 1 --update-delay 2s apidockerbuild and run containersservicemanage swarm servicesupdatechange it, with a rolling update
docker service ps api --format 'table {{.Name}}\t{{.CurrentState}}\t{{.DesiredState}}'pslist the services and their state--format 'table {{.Name}}\t{{.CurrentState}}\t{{.DesiredState}}'print only these fields (Go template)
And it heals itself. Kill one replica's container and the manager starts a new one, because three are desired:
$ docker rm -f $(docker ps -q --filter name=api.1) $ docker service ps api --filter desired-state=running --format '{{.Name}} {{.CurrentState}}' api.1 Running less than a second ago api.2 Running 15 seconds ago api.3 Running 22 seconds ago $ docker service rm api && docker swarm leave --force
docker rm -f $(docker ps -q --filter name=api.1)dockerbuild and run containersrmdelete a container-fstop it first if running$(docker ps -q --filter name=api.1)the container
docker service ps api --filter desired-state=running --format '{{.Name}} {{.CurrentState}}'servicemanage swarm servicespslist the services and their state--filter desired-state=runningshow only matching items--format '{{.Name}} {{.CurrentState}}'print only these fields (Go template)
docker service rm api && docker swarm leave --forcermdelete one&&run the next command only if this one workedswarmmanage swarm modeleaveleave the swarm
Other machines join with the docker swarm join command that init prints, and docker stack deploy -c compose.yaml shop runs a Compose file across the cluster, with the deploy: section setting replicas and update
order. Swarm is simple and needs little more than Docker skills, but its ecosystem is small; most teams that
outgrow one server go to a platform or to Kubernetes.
Kubernetes
Kubernetes is the industry's standard orchestrator: the same idea of desired state and self-healing, with far more features and far more to learn. Your Docker knowledge maps straight onto it:
| Docker / Compose | Kubernetes |
|---|---|
| a container | a container in a Pod |
a service with replicas: 3 | a Deployment |
| a service name in DNS | a Service |
| published port, reverse proxy | a Service of type LoadBalancer, or an Ingress / Gateway |
environment:, secrets: | a ConfigMap, a Secret |
| a named volume | a PersistentVolumeClaim |
healthcheck: | liveness and readiness probes |
mem_limit, cpus | resources.requests and limits |
apiVersion: apps/v1 kind: Deployment metadata: name: api spec: replicas: 3 selector: matchLabels: { app: api } template: metadata: labels: { app: api } spec: containers: - name: api image: ghcr.io/acme/price-api:sha-3f9c2e1 ports: [{ containerPort: 8080 }] readinessProbe: httpGet: { path: /health, port: 8080 } resources: limits: { memory: 256Mi, cpu: 500m } securityContext: runAsNonRoot: true allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: { drop: [ALL] }
Every hardening flag from lesson 11 has a field here. Managed services (EKS, GKE, AKS) run the control plane for you; k3s is a light distribution for small clusters and labs. The Kubernetes course on this site starts where this lesson ends.
Swarm stacks, secrets and rolling updates
docker stack deploy turns a Compose-shaped file into Swarm services. A Swarm secret is mounted under
/run/secrets, not put in an image or command line. Rolling-update settings control parallelism and rollback.
services: api: image: price-api:multi deploy: replicas: 3 update_config: {parallelism: 1, delay: 5s, order: start-first} rollback_config: {parallelism: 1} secrets: api_token: {external: true}
$ printf 'local-demo-token' | docker secret create api_token - $ docker stack deploy -c stack.yml academy $ docker service ls $ docker service update --rollback academy_api $ docker stack rm academy && docker secret rm api_token
printf 'local-demo-token' | docker secret create api_token -|pipe: send output to the next commanddockerbuild and run containers-the previous directory
docker service lsservicemanage swarm serviceslslist them
docker service update --rollback academy_apiupdatechange it, with a rolling update
docker stack rm academy && docker secret rm api_token&&run the next command only if this one worked
Self-healing replicas on your laptop
Turn your Docker into a one-node swarm, run the price API with three replicas, roll out a new version one replica at a time, kill a replica and watch it come back, then leave the swarm.
3/3 replicas, a completed rolling update, a replaced replica, and plain Docker againReveal one safe solution
$ docker swarm init --advertise-addr 127.0.0.1 $ docker service create -d --name api --replicas 3 -e APP_VERSION=1.0.0 price-api:multi $ docker service ls --format '{{.Name}} {{.Replicas}}' api 3/3 $ docker service update --env-add APP_VERSION=1.1.0 --update-parallelism 1 --update-delay 2s api $ docker service ps api # each replica: one Running, one Shutdown $ docker rm -f $(docker ps -q --filter name=api.1) $ docker service ls --format '{{.Name}} {{.Replicas}}' # back to 3/3 after a few seconds api 3/3 $ docker service rm api && docker swarm leave --force $ docker network rm docker_gwbridge # left behind by swarm mode
docker swarm init --advertise-addr 127.0.0.1dockerbuild and run containersswarmmanage swarm modeinitturn this machine into a swarm manager
docker service create -d --name api --replicas 3 -e APP_VERSION=1.0.0 price-api:multiservicemanage swarm servicescreatecreate one-drun in the background--name apiname the container-e APP_VERSION=1.0.0set an environment variable inside
docker service ls --format '{{.Name}} {{.Replicas}}'lslist them--format '{{.Name}} {{.Replicas}}'print only these fields (Go template)
docker service update --env-add APP_VERSION=1.1.0 --update-parallelism 1 --update-delay 2s apiupdatechange it, with a rolling update
docker service ps apipslist the services and their state
docker rm -f $(docker ps -q --filter name=api.1)rmdelete a container-fstop it first if running$(docker ps -q --filter name=api.1)the container
docker service rm api && docker swarm leave --forcermdelete one&&run the next command only if this one workedleaveleave the swarm
docker network rm docker_gwbridgenetworkmanage networks
You never restarted anything yourself: you declared "three replicas of this image" and Swarm kept it true through an update and a failure. That idea, desired state, is the core of every orchestrator, Kubernetes included.
Try it yourself
0 of 6 steps doneRun each task in your own account or terminal, then tick it off.
Quick check
What do all the options in this lesson have in common?
Official reading for this lesson
Or get every quiz answer right and it completes itself.