CoursesDocker for beginnersWhat Docker is, and why

What Docker is, and why

The problem containers solve, in plain terms.

Beginner10 min · lesson 1 of 16
In plain terms
Think of a shipping container. Before them, loading a ship meant hand-packing barrels, sacks, and crates that each needed different handling. Standard containers changed that: pack once, and any crane, truck, or ship moves it the same way. Docker does this for software — pack the app and everything it needs once, and it runs the same on any machine.

Every developer lives through this at least once. You build something on your laptop and it runs perfectly. You hand it to a teammate, and on their machine it falls over. A missing library. Python 3.9 where you had 3.12. Some setting that only exists on your computer. Your code never changed. Everything around it did.

That gap has a name, and you have probably heard someone say it with a shrug: "works on my machine." Closing it is the entire reason Docker exists. The fix it uses was borrowed from the shipping industry.

The idea Docker borrowed from shipping

Loading a cargo ship used to be brutal, slow work. Every barrel, sack and crate came in its own shape, so dockworkers packed each one by hand, and a crane rigged for one load could not be trusted with the next. Then the industry agreed on a single steel box with fixed dimensions. You pack your goods into the box once. After that, every crane, truck, train and ship handles it identically, and nobody along the route needs to know what is inside. They move the box.

Docker does the same trick for software. You pack your app and everything it needs into one standard box, and any machine with Docker on it moves that box the same way. What is inside stops being the host machine's problem.

Image and container: the recipe and the cake

Two words will follow you through this whole course, so pin them down before you type a command. A recipe versus a cake. An image is the recipe, written out with every ingredient listed and nothing assumed. A container is the cake you bake from it. One recipe makes as many cakes as you want, and they all come out the same, because the recipe never moves.

The precise version: an image is a read-only package (read-only means nothing can change it once it has been built) holding everything your app needs to run, which is its code, its libraries (bundles of prewritten code your app borrows from), and its settings. A container is that image running as a live process (a program currently executing) on a computer. Image is frozen. Container is alive. Hold on to that difference and most of Docker stops being mysterious.

Your first container

Enough background. If Docker is installed, one command is all you need. hello-world is a tiny official image with exactly one job: prove your setup works.

terminal
docker run hello-world
output
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
e6590344b1a5: Pull complete
Digest: sha256:ec153840d1e635ac434fab5e377081f17e0e15afab27beb3f726c3265039cfb2
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

Those first lines tell a small story, so read them slowly. Docker went looking for the hello-world image on your machine, came up empty, and downloaded it from Docker Hub. Docker Hub is a public shelf of ready-made images, and any shelf like that is called a registry. Docker then created a container from the image, the container printed its message, and it exited. You installed nothing by hand. You configured nothing. One line fetched a packaged app and ran it.

Two more words are hiding in that output. Look at hello-world:latest. The bit after the colon is the tag, a label that says which version of an image you want. You never typed one, so Docker filled in latest on your behalf. Now count how often the word daemon shows up. A daemon is a program that sits running quietly in the background, waiting to be asked to do something, and Docker runs as one on your machine. That background service is what actually downloads images and starts containers. The command you type is the messenger. The daemon does the lifting.

Something you can actually open

hello-world says hello and quits. Run something that stays up instead. nginx (say it "engine-x") is a real web server, the kind of program that hands web pages to browsers, and it has an official image.

terminal
docker run -d -p 8080:80 nginx:1.27
output
Unable to find image 'nginx:1.27' locally
1.27: Pulling from library/nginx
9c704ecd0c69: Pull complete
5f3a58b0acfd: Pull complete
b1e9f9d3a2c4: Pull complete
Digest: sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c
Status: Downloaded newer image for nginx:1.27
9f3a2b1c8e4d5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b

Two flags carry the weight here. -d means detached, so the container runs in the background instead of taking over your terminal. The other flag is about ports. A port is a numbered doorway into a computer, and network traffic goes in and out through it. -p 8080:80 wires two doorways together: nginx inside the container listens on port 80, and -p forwards port 8080 on your machine to port 80 inside the box. Ask for localhost:8080 and you reach the server sitting in the container. That long string of letters and numbers at the end of the output is the container's ID (its unique identifier). Check that it really is running.

terminal
docker ps
output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9f3a2b1c8e4d nginx:1.27 "/docker-entrypoint.…" 8 seconds ago Up 7 seconds 0.0.0.0:8080->80/tcp nice_hopper

docker ps lists the containers running right now. One line, one live web server, and nginx was never installed on your machine. Open http://localhost:8080 in a browser and the nginx welcome page is waiting for you. When you are finished, docker stop nice_hopper shuts it down (use the name from the last column, or the ID).

When it breaks: reading your first error

You are going to hit errors, and that is fine. Docker's errors look like noise at first and read clearly once you know their shape. Here is a good one to meet on purpose. With the first web server still up, try starting a second one on the same port.

terminal
docker run -d -p 8080:80 nginx:1.27
output
docker: Error response from daemon: driver failed programming external
connectivity on endpoint recursing_torvalds:
Bind for 0.0.0.0:8080 failed: port is already allocated
Run 'docker run --help' for more information

Take the message at face value. Port 8080 is already allocated, which means something already holds it. That something is the container you started a minute ago. Two programs cannot listen on the same port of the same machine at the same time. Pick a free one instead, like -p 8081:80, or stop the first container. Almost everyone trips over this once, and now you will recognise it on sight.

A container is not a virtual machine

This may sound like a virtual machine, a whole pretend computer running inside your real one. It is not, and the gap between the two explains why Docker spread so fast. A virtual machine boots an entire operating system of its own. That is renting a separate house for every guest: plenty of room, slow to move into, expensive to keep. A container shares the operating system of the machine it runs on (that machine is called the host) and carries only the app and its dependencies (the other pieces of software it needs) on top. Closer to giving each guest a private, locked room in one shared house.

Sharing the host's kernel is what makes containers cheap. The kernel is the core of the operating system, the part every other program sits on. Because containers borrow it instead of booting their own, they start in a fraction of a second and weigh megabytes rather than gigabytes. One ordinary laptop can run dozens at once. That same sharing is why container security has its own set of rules, but those come later in your reading.

What happens when you run 'docker run hello-world'
1You type the command
docker run hello-world in your terminal
2The command calls the daemon
it asks Docker's background service to do the work
3Daemon checks local images
hello-world isn't on the machine yet
4Daemon pulls from the registry
downloads the image from Docker Hub
5Daemon creates a container
a running copy made from the image
6Container runs, prints, exits
you see the Hello from Docker! message
"Cannot connect to the Docker daemon"
If your very first command comes back with "Cannot connect to the Docker daemon. Is the docker daemon running?", the problem is that Docker itself has not started. On Windows or Mac, open Docker Desktop and wait for the whale icon to stop moving and hold steady. On Linux, run sudo systemctl start docker. Your terminal only ever talks to the daemon. If the daemon is asleep, nothing you type will happen.

Plenty of people treat Docker as a tidier way to install software. That sells it short. The real payoff is that one build artifact, the image, is what a developer runs on a laptop, what CI (continuous integration, the automated system that builds and tests your code on every change) produces in a pipeline, and what production starts on a server. Before containers, those three environments drifted apart, and pulling them back together cost weeks of "works on my machine" patching. With containers, the thing you test is very close to the thing you ship.

None of this makes your operations problems vanish. You still need secrets, networking, storage, and a plan for upgrades. What moves is the packaging boundary. Instead of writing a twenty-step install guide and hoping someone follows every step, you hand over an image name and one run command. The image carries the operating system packages, the language runtime (the software that actually executes your code, such as Python or Node.js), and your app files in a known layout. The container is that image brought to life with a writable layer on top.

Build one habit while you work through this course. When a command surprises you, ask which stage broke: the image pull, the container create, the process start, or the network publish. Docker's messages are blunt once you know which stage you are standing in. That is the difference between "Docker is broken" and "port 8080 is already taken," and only one of those two is something you can fix.

Try this

Check that Docker answers you at all, then run hello-world once. Read the pull lines instead of scrolling past them. That one run walks through pull, create, start and exit in a few seconds.

terminal
docker version
docker run --rm hello-world
output
Client: Docker Engine - Community
Server: Docker Engine - Community
...
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
...
Hello from Docker!
This message shows that your installation appears to be working correctly.

Takeaway

Docker packs your app and everything it depends on into an image, then runs that image as a container. The same image runs on your laptop and on a server, so you stop rewriting install instructions for every machine that needs the app.

Quick check
01Your first run prints "Unable to find image 'hello-world:latest' locally" and then works fine anyway. What is that line telling you?
Incorrect — Nothing gave up. The run ended with the Hello from Docker! message. That line opens the story rather than ending it.
Correct — "Not found locally" means Docker fetched hello-world from Docker Hub and started a container from it, without you asking.
Incorrect — No hand-installing needed. docker run fetches any image you don't already have the first time you name it.
Incorrect — Nothing is damaged. Every image prints this the first time you use it on a machine.
02The output names the image hello-world:latest, but all you typed was docker run hello-world. What is the ':latest' part, and where did it come from?
Incorrect — The container ID is the long string of hex characters further down the output, not the piece after the colon.
Incorrect — Size never travels inside an image name. The colon separates the name from its version label.
Correct — Everything after the colon is the tag, and Docker assumes latest when you leave the version out.
Incorrect — The image did come from Docker Hub, but the tag doesn't record where it came from. The tag picks a version.
03An nginx container already holds -p 8080:80. You run docker run -d -p 8080:80 nginx:1.27 again and Docker answers 'Bind for 0.0.0.0:8080 failed: port is already allocated'. What's the cleanest way to get a second server up?
Incorrect — Nothing is broken. The daemon is healthy and telling you exactly what the conflict is.
Correct — Two programs can't hold the same host port, so give the new container a free one like 8081.
Incorrect — The image is fine. The collision is over a port number, not over image data.
Incorrect — The clash is on the host side, and 8080 is still taken there. Port 81 also isn't where nginx listens, so the mapping would point at nothing.

Related