Where to go next

Compose to orchestration, and the path beyond.

Beginner6 min · lesson 16 of 16

You can package a real app now. A container is a running copy of a packaged app. An image is the frozen package it starts from, and you can build both. You can wire containers onto a shared network so they find each other by name, hand them volumes so their files outlive a restart, and write one Compose file, a plain text list of every part of the app, to bring the whole thing up at once. You can push the finished image to a registry, the shared library you upload images to, and a teammate pulls it down and gets your exact setup. That covers most of what packaging takes. This last lesson doesn't go back over any of it. It walks you straight into the one wall your current setup runs into, lets you feel that wall with your own hands, then points at the courses built for getting past it.

One machine only takes you so far

A busy shop with one checkout lane works fine until the queue backs up out the door. Then you want more lanes open. Copies of your web app behave the same way. Each extra copy is called a replica, and more replicas share the incoming traffic between them. Compose can start several replicas on your one machine with a --scale flag. There's a catch, and it's worth walking into on purpose. Here's a small Compose file of the same shape as the one you wrote earlier. Publishing a port means handing traffic from a port number on your machine (8080) through to a port inside the container (80).

compose.yaml
services:
web:
image: nginx:alpine
ports:
- "8080:80"

Ask for three copies instead of one, and start them in the background:

Terminal
docker compose up -d --scale web=3
Output
Network shop_default Created
Container shop-web-1 Created
Container shop-web-2 Created
Container shop-web-3 Created
Container shop-web-1 Starting
Container shop-web-1 Started
Container shop-web-2 Starting
Error response from daemon: driver failed programming external connectivity on endpoint shop-web-2 (a3f9c2b1e4d7): Bind for 0.0.0.0:8080 failed: port is already allocated

Skip the wall of status lines and read the last one. "port is already allocated." The first replica claimed port 8080 on your machine and came up. Your machine has exactly one port numbered 8080, so the second replica had nowhere to land, and the whole command gave up. Running plenty of containers on one box is fine. Pointing all of them at the same front door is not. That single shared door is the whole problem, and getting around it is what orchestration was built for.

What orchestration does for you

A shift manager on a factory floor does this job for people. Someone goes home sick, the manager spots the empty station, calls in a replacement, and points them at the machine that needs hands. Orchestration is that manager, written in software and aimed at containers. It holds a set number of replicas alive across a group of machines. When one dies, it starts a fresh one. It rolls out a new version a few replicas at a time, so the app never drops offline completely. And it puts a single load balancer out front, a traffic director that spreads requests across every copy. Docker Swarm is the lightweight version built into Docker itself. Kubernetes (usually shortened to k8s, for the eight letters sitting between the k and the s) is the heavier one the industry settled on.

You already have Swarm. It ships inside Docker. Turn your single install into a one-node cluster, meaning one machine acting as its own manager:

Terminal
docker swarm init
Output
Swarm initialized: current node (xktaloqh1zqa9teyr80cysmag) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-077goduk...5e3-ajvebn8z 192.168.65.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Now ask Swarm for three copies, using the same published port that broke Compose a minute ago. A service is Swarm's word for a set of identical replicas managed as one thing:

Terminal
docker service create --name web --replicas 3 -p 8080:80 nginx:alpine
Output
g9h8rhc1eslzzrpv3yv7m96or
overall progress: 3 out of 3 tasks
1/3: running
2/3: running
3/3: running
verify: Waiting 6 seconds to verify that tasks are stable...
...
verify: Service g9h8rhc1eslzzrpv3yv7m96or converged

Same three replicas. Same single port 8080. This time it holds. Swarm parks a load balancer called the routing mesh in front of the service, so port 8080 takes every request and fans it out across all three replicas instead of leaving them to fight over the door. Check on the service:

Terminal
docker service ls
Output
ID NAME MODE REPLICAS IMAGE PORTS
g9h8rhc1eslz web replicated 3/3 nginx:alpine *:8080->80/tcp

Need more copies? Nothing gets rebuilt and the image never changes. You ask for a bigger number:

Terminal
docker service scale web=5
Output
web scaled to 5
overall progress: 5 out of 5 tasks
1/5: running
2/5: running
3/5: running
4/5: running
5/5: running
verify: Service web converged

That's the jump most beginner courses only promise out loud: three copies to five, or to thirty, one command, no downtime. Swarm starts the new replicas and hangs them off the same load balancer. Healing is automatic too. If a replica dies at 3 a.m., Swarm sees the running count fall below the number you asked for and starts a replacement, with nobody watching the terminal.

Swarm mode does not switch itself off
Running docker swarm init flips your Docker into Swarm mode, and it stays flipped long after you close the terminal. When you've finished poking at it, run docker swarm leave --force. That pulls your node out of Swarm and deletes the service and its replicas in one move. Skip the cleanup and you'll be scratching your head later, when a plain docker run and some forgotten Swarm services are sharing the same machine.

The road forks three ways from here, and all three carry on right here on this site. To understand Docker itself more deeply, the intermediate course opens up how images really work, plus the engine, storage, networking and Swarm in full, which is the working knowledge behind the Docker Certified Associate (DCA) exam. To make containers safe enough for production, the advanced security course covers hardening your images and the daemon (the background service that actually starts and stops your containers), along with container internals and the escape paths that hardening closes. And to run stacks at real scale, the Kubernetes track carries orchestration the whole way.

Diagram

The wall you hit above was a port. The next few have the same shape, only larger. Twenty machines instead of one, so something has to decide which machine each replica lands on. A new version going out while people are still using the old one. Health checks, so a container that is technically alive but wedged gets replaced instead of quietly serving errors. Passwords and API keys reaching a hundred containers without ever being baked into the image. Every one of those is an operations problem rather than a packaging problem.

That gap is what orchestrators fill. Compose runs on the one machine you started it on. It won't pick a host for you out of a fleet, won't restart a replica that died over on some other machine, and gives you no way to say "replace two replicas at a time, and stop the rollout if the new ones fail their health check." Swarm is Docker's own clustering answer and gets the full treatment in the deeper Docker course. Kubernetes is what most large estates run, and it has its own learning path here.

Hardening is the second fork. Running your process as root inside the container, mounting docker.sock (the socket file that hands whoever holds it total control of the Docker engine), and shipping fat images stuffed with shells and compilers are all fine while you're learning and all bad news in production. The advanced container security course makes those trade-offs concrete with namespaces, capabilities, and the escape paths that hardening is meant to shut.

Reading more is not the next step. Packaging an app you already know is. Write a Dockerfile for it, add a compose.yaml with one dependency such as a database, mount a volume for the data, pin the image tags, then run the whole stack cold on a second machine that has never seen your code. Anything you half-understand shows up as a specific error message you can search for word for word.

Keep the words sharp while you do it: image versus container, volume versus bind mount, bridge network versus published port, CMD versus ENTRYPOINT. Most confusion in so-called advanced material is beginner vocabulary going soft under pressure. Hold those terms steady and the harder courses read like more of the same, not a new language.

Try this

Take stock of what is already sitting on this machine: running and stopped containers, images, volumes, networks, and the Compose version you have. Then sketch out which of those pieces an app of your own would need next.

terminal
docker ps -a
docker images
docker volume ls
docker network ls
docker compose version
output
CONTAINER ID IMAGE ...
REPOSITORY TAG ...
DRIVER VOLUME NAME
NETWORK ID NAME DRIVER
Docker Compose version v2.x.x

Takeaway

One machine carried you a long way, and port 8080 is exactly where it stops. Orchestration is the road across many machines. Hardening is the road to machines you can safely point at the internet. Both are built on the vocabulary you already have.

Quick check
01You run docker compose up -d --scale web=3 against a service that publishes 8080:80, and only one replica ever starts. What is it that actually lets you scale past one machine's single port?
Incorrect — That is the exact setup that failed a moment ago. Every replica still wants host port 8080, and the machine owns exactly one of those.
Correct — The routing mesh takes traffic on a single port and spreads it over every replica, across machines too, so nothing has to fight for 8080.
Incorrect — You would collect the same 'port is already allocated' error each time, and finish with three unmanaged stacks instead of one working app.
Incorrect — The port is not wedged from a crash. One host port genuinely cannot serve three containers at once, so restarting changes nothing.
02One of the three replicas in your Swarm service dies at 3 a.m. and nobody is watching the terminal. Going by this lesson, what does Swarm do about it?
Incorrect — No manual step is needed. Holding the replica count is Swarm's job, not yours.
Incorrect — It does the opposite. It heals back up to the count you set instead of tearing the service down.
Correct — An orchestrator keeps a set number of replicas alive and repairs itself when one dies, with nobody at the keyboard.
Incorrect — The routing mesh does spread traffic, but Swarm still brings the missing replica back to hold the count you set.
03You ran docker swarm init to try these examples, then closed the terminal. What is true afterwards, and what should you do once you've finished?
Incorrect — Swarm mode is not tied to a terminal session. It stays on until you turn it off yourself.
Correct — init leaves Docker in Swarm mode until you say otherwise, and leave --force is the undo, clearing the leftover service along with it.
Incorrect — docker swarm leave --force undoes it in one command. No reinstall is involved.
Incorrect — Swarm keeps the service up to hold your replica count. It does not expire on its own.

Related