What Docker is, and why
The problem containers solve, in plain terms.
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.
docker run hello-world
Unable to find image 'hello-world:latest' locallylatest: Pulling from library/hello-worlde6590344b1a5: Pull completeDigest: sha256:ec153840d1e635ac434fab5e377081f17e0e15afab27beb3f726c3265039cfb2Status: Downloaded newer image for hello-world:latestHello 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 theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto 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.
docker run -d -p 8080:80 nginx:1.27
Unable to find image 'nginx:1.27' locally1.27: Pulling from library/nginx9c704ecd0c69: Pull complete5f3a58b0acfd: Pull completeb1e9f9d3a2c4: Pull completeDigest: sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059cStatus: Downloaded newer image for nginx:1.279f3a2b1c8e4d5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
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.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES9f3a2b1c8e4d 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.
docker run -d -p 8080:80 nginx:1.27
docker: Error response from daemon: driver failed programming externalconnectivity on endpoint recursing_torvalds:Bind for 0.0.0.0:8080 failed: port is already allocatedRun '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.
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.
docker versiondocker run --rm hello-world
Client: Docker Engine - CommunityServer: Docker Engine - Community...Unable to find image 'hello-world:latest' locallylatest: 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.