Before you type your first command, you need to understand which problem you came here to solve. Docker is not a fad or just another tool in the developer's toolbox: it is the answer to a very specific and very old pain in software development, the pain of the same code behaving differently depending on where it runs. In this lesson you will see what that pain is, what containerization consists of exactly, how it differs from the virtual machines you may already know, and what the three concepts —image, container and registry— are on which absolutely everything you will see in the rest of the course rests. You are not going to install anything yet or type any commands: this lesson builds the mental map that will make the next six modules make sense.

Contents

  1. The real problem: "it works on my machine"
  2. Environment drift and dependency hell
  3. What containerization is
  4. What Docker is, specifically
  5. Containers versus virtual machines
  6. The benefits you get
  7. Real-world use cases
  8. The three fundamental concepts: image, container and registry
  9. The project you will build: Aurora Libros

  1. The real problem: "it works on my machine"

Picture this scene, because it happens every single day in thousands of teams. A developer finishes a feature, tests it on her laptop, everything looks fine and she pushes. The continuous integration server builds it and fails. Her teammate pulls the branch and it works for him. It gets deployed to staging and the application starts up but returns strange errors three hours later. The line that ends the discussion is always the same: "well, it works on my machine".

The problem is not that anyone is lying. Everybody is telling the truth. The problem is that "my machine", "the CI server" and "staging" are four different environments that resemble each other only by accident:

Element Developer's laptop Teammate's laptop CI server Staging
Operating system macOS 15 Ubuntu 24.04 Ubuntu 22.04 Debian 12
Node.js 22.11 22.3 20.9 18.20
PostgreSQL 16 (Homebrew) 15 (apt) none 14 (managed)
Time zone Europe/Madrid Europe/Madrid UTC UTC
Environment variables local .env file a different local .env CI secrets provider variables
System libraries OpenSSL 3.3 OpenSSL 3.0 OpenSSL 3.0 OpenSSL 1.1

Every one of those rows is a potential source of failure. An application that uses a Node function added in version 21 will break on the server running Node 20. A SQL query that takes advantage of a PostgreSQL 16 novelty will fail against PostgreSQL 14. A test that assumes the local time zone will pass in Madrid and fail in UTC.

  1. Environment drift and dependency hell

There are two classic pathologies behind all of this, and it is worth naming them because you are going to hear about them constantly.

Environment drift. Environments start out identical and grow apart over time. Someone installs a package by hand on the server to get out of a jam and never documents it. Someone else upgrades their local Node because they felt like trying something out. Six months later, nobody knows how to rebuild the server from scratch, and the only reliable documentation is the server itself. It becomes what is known as a snowflake server: a one-of-a-kind, irreproducible machine that everyone is afraid to touch.

Dependency conflicts. You have one machine, and it can only have one version of each thing installed globally. If project A needs Python 3.9 and project B needs Python 3.12, or if one project needs PostgreSQL 14 and another needs PostgreSQL 16, you have a problem. The traditional workarounds (version managers such as nvm or pyenv, virtual environments) help with the language, but they solve nothing below it: system libraries, databases, web servers, command-line tools.

And there is a third cost, less visible but enormous: onboarding. When someone new joins the team, they spend a day or two following an outdated document to get their machine ready. They install a database, configure it, load test data, install a cache, adjust ports, discover they are missing a system library... and even then something still does not work. That time, multiplied by every person who joins and every time someone reinstalls their machine, is money.

  1. What containerization is

Containerization starts from a simple idea: if the environment is the problem, package it together with the application.

A container is a process (or a group of processes) that runs on your operating system but isolated from everything else, and that sees a filesystem of its own containing exactly what the application needs: its code, its interpreter or runtime, its libraries, its configuration files. From inside the container, the application believes it is alone on a clean machine. From outside, it is simply one more process on the system.

The key point is that this isolation is not achieved by emulating hardware or booting another operating system, but by using features the Linux kernel itself already offers to separate processes from each other and cap their resources. That is why a container starts in milliseconds and consumes almost nothing: there is nothing to "power on", just a process launched with a restricted view of the system.

Later in this course we will cover that mechanism in detail, in lesson 05-07 (The Runtime Inside: Namespaces, Cgroups and Layers). For now, the intuition is enough: isolation at the process level, not at the machine level.

  1. What Docker is, specifically

It pays to be precise here, because words that are not synonyms get used as if they were:

  • Container is the concept: an isolated process with its own filesystem.
  • Docker is the most popular platform for creating, distributing and running containers. It includes an engine that runs them (dockerd), a command-line tool (docker), a format for describing how to build the package (the Dockerfile) and a public service for sharing those packages (Docker Hub).
  • Docker, Inc. is the company that develops it.

Docker did not invent containers —Linux had had the necessary pieces for years— but it did do two decisive things in 2013: it made them easy to use (one command instead of an arcane configuration) and, above all, it created a standard format for packaging and sharing containerized applications. That format is standardized today under the OCI (Open Container Initiative), which means an image created with Docker can run on other compatible tools.

In one sentence you can commit to memory:

Docker is a platform that packages an application together with its entire environment into a portable, standard unit, and runs it in an isolated, reproducible way on any machine that has Docker installed.

  1. Containers versus virtual machines

If you have already used VirtualBox, VMware or cloud machines, this comparison will put the concept in its place. A virtual machine also isolates, it also packages a complete environment... but it does so at a much lower and much more expensive level.

A virtual machine emulates hardware. On top of that fake hardware you install a complete guest operating system, with its own kernel, its boot services, its system processes. Then you install your application. The result is gigabytes of disk and hundreds of megabytes of RAM before your code has executed a single line.

A container shares the host system's kernel. There is no guest operating system: only the user-space files the application needs.

graph TB
    subgraph VM["Virtual machines"]
        VMHW["Physical hardware"] --> VMHOST["Host operating system"]
        VMHOST --> HYP["Hypervisor"]
        HYP --> G1["Guest OS 1<br/>(full kernel)"]
        HYP --> G2["Guest OS 2<br/>(full kernel)"]
        G1 --> A1["App A + libs"]
        G2 --> A2["App B + libs"]
    end

    subgraph CT["Containers"]
        CTHW["Physical hardware"] --> CTHOST["Host operating system<br/>(a single shared kernel)"]
        CTHOST --> ENG["Container engine (Docker)"]
        ENG --> C1["Container 1<br/>App A + libs"]
        ENG --> C2["Container 2<br/>App B + libs"]
        ENG --> C3["Container 3<br/>App C + libs"]
    end

Notice the structural difference in the diagram: in the virtual machine column there is a "guest OS with a full kernel" block for each application; in the container column that block disappears and everything shares the host's kernel. That is where the savings come from.

Aspect Virtual machine Container
What it isolates Virtualized hardware Processes on a shared kernel
Kernel One of its own per machine Shared with the host
Typical size 1–20 GB 5 MB – 500 MB
Startup time 30 s – several minutes Milliseconds to 2 s
Density per server Dozens Hundreds or thousands
CPU/RAM overhead Noticeable Almost none
Isolation Very strong (hardware boundary) Strong, but shares the kernel
Different operating systems Yes (Windows on Linux, etc.) No: the container uses the host's kernel
Typical case Isolating different tenants, running another OS Packaging and deploying applications

Two important nuances you should not forget:

  1. They are not mutually exclusive. In practice, a great many containers run inside virtual machines in the cloud. A provider gives you a VM and you put twenty containers inside it.
  2. A container's isolation is good, but it is not a VM's. Because they share a kernel, a kernel vulnerability affects every container on the machine. That is why there are specific security best practices, which you will see in lesson 05-03.

  1. The benefits you get

Let's translate all of the above into concrete advantages.

  • Portability. The same packaged unit runs identically on your laptop, in CI and in production. This is what kills "it works on my machine".
  • Reproducibility. The package is described in a text file versioned alongside the code. Rebuilding the environment stops being a ritual and becomes a command.
  • Startup in seconds. Spinning up a test database or five replicas of a service stops being a heavyweight operation.
  • Density. Where 10 virtual machines fit, hundreds of containers fit, because you are not paying for one operating system per application.
  • Isolation. Two projects with incompatible versions of everything coexist without touching each other. And your operating system stays clean: you install no databases or runtimes globally.
  • Fast onboarding. A new team member clones the repository, runs one command and has the whole platform running.
  • Disposability. If a container breaks, you delete it and create another one. There are no more "delicate" servers that nobody dares restart.

  1. Real-world use cases

These are the scenarios where Docker has become the de facto standard:

Use case What Docker solves
Local development Spinning up the whole stack (API, database, cache, proxy) with one command, without installing anything on your system
Continuous integration (CI) Every test run starts in a clean, identical environment; no more failures caused by leftover state on the agent
Microservices Each service is packaged, versioned and deployed independently, with its own runtime and its own dependencies
Deployment and orchestration The deployable unit is the very image that was tested in CI; platforms such as Kubernetes build on that format
Legacy applications Freezing an old environment (an ancient version of PHP or Java) that you can no longer install on a modern server
One-off tools Running a utility without installing it: you use it inside a container and then delete it
Training and demos Handing out an identical practice environment to everyone

  1. The three fundamental concepts: image, container and registry

If you take only one thing away from this lesson, make it this one. All of Docker revolves around three objects and the relationship between them.

The image is the template: an immutable, read-only package containing the application's filesystem (its code, its runtime, its libraries) plus metadata describing how to start it. An image does not run: it is used to create containers.

The container is the running instance of an image. It is the image, plus a writable layer of its own, plus a live process. From a single image you can create one container or a thousand, and each will have its own life, its own temporary data and its own state.

The registry is the store where images live and from which they are distributed. Docker Hub is the default public registry; companies usually have private registries as well.

The analogy that works best if you come from object-oriented programming:

Programming Docker Comment
Class Image A static definition; it does nothing on its own
Object / instance Container Created from the class, has its own state, there can be many
Library repository (npm, Maven) Registry Where you download definitions made by others

And the basic flow you will repeat a thousand times during the course:

flowchart LR
    D["Your code +<br/>packaging instructions"] -->|build| I["Image"]
    I -->|publish| R[("Registry<br/>Docker Hub")]
    R -->|pull| I2["Image on another machine"]
    I2 -->|run| C1["Container 1"]
    I2 -->|run| C2["Container 2"]
    I2 -->|run| C3["Container 3"]

Read it like this: you build an image from your code, you publish it to a registry, any machine pulls it and from it runs as many identical containers as it wants. That chain is, quite literally, Docker's reason for existing.

Throughout the rest of the module you will dig into each piece: the architecture that makes this flow possible in lesson 01-03, the commands to drive it in 01-04, the anatomy of images in 01-05 and your first real container in 01-06.

  1. The project you will build: Aurora Libros

So that none of this stays theoretical, the entire course rests on a single project that will grow module by module: Aurora Libros S.L., a fictional online bookshop.

Its platform will have four pieces:

  • aurora-api: the catalog REST API, in Node.js 22 with Express.
  • aurora-db: a PostgreSQL 16 database holding the book catalog.
  • aurora-cache: a Redis 7 that caches the most frequent queries.
  • aurora-web: an Nginx that serves the static site and acts as a reverse proxy to the API.

Today, at the course's starting point, none of that is containerized: the Aurora Libros team suffers exactly the problems described in sections 1 and 2. In the last lesson of this module (01-07) you will get to know their code, you will try to start it without Docker and you will see with your own eyes why they need this course. From there, each module will add a piece until you reach a complete, secure, deployed platform.

Common Mistakes and Tips

  • Believing a container is "a lightweight VM". It is the most widespread analogy and also the one that causes the most confusion later on. A container has no kernel of its own, does not boot an operating system and, generally speaking, runs a single main process. When that process ends, the container ends. Think "packaged process", not "small machine".
  • Confusing image and container. This is beginners' mistake number one and it drags misunderstandings along for weeks. Repeat the analogy: image = class, container = object. Deleting a container does not delete its image; deleting an image does not affect the containers already created from it.
  • Thinking Docker guarantees your application will work anywhere. It guarantees the environment is the same. It does not protect you from depending on a specific CPU architecture (an image built only for amd64 will not run as-is on an Apple Silicon Mac), nor from external resources you do not control.
  • Assuming "container" automatically implies "secure". The default isolation is reasonable, but it is configurable and can be weakened by accident. Security is a topic of its own (lesson 05-03).
  • Tip: learn the vocabulary before the commands. Commands can be looked up; concepts cannot. If image, container and registry are clear to you, 80% of Docker's error messages become readable.
  • Tip: think "disposable". The correct mindset is that any container may die at any moment and be replaced by an identical one. That idea guides every best practice you will see later on.

Exercises

Exercise 1: diagnose the scenario

A team is in this situation:

  • The application works on Marta's laptop (Node 22, PostgreSQL 16).
  • It fails on the CI server with a SQL syntax error.
  • The CI server has PostgreSQL 14.
  • Nobody remembers who installed PostgreSQL on CI, or when.

Answer: (a) what is the underlying phenomenon called?, (b) why is installing PostgreSQL 16 on CI not a satisfactory solution?, and (c) how does containerization change the framing?

Exercise 2: container or virtual machine

For each situation, decide whether the natural solution is a container, a virtual machine, or both combined, and justify it in one or two sentences:

  1. You need to run a Windows application on a Linux server.
  2. You want each of the team's 12 developers to have the same local development stack.
  3. You rent out compute capacity to customers who will run arbitrary code and whom you do not trust.
  4. You want to run 300 instances of a small microservice on a single powerful server.
  5. You need to test your application against PostgreSQL 14, 15 and 16 on the same machine at the same time.

Exercise 3: translate the analogy

Without using the word "Docker", explain to a non-technical colleague the relationship between image, container and registry using an analogy of your own (not the class/object one). Then answer: if you delete a container, does the image disappear? And if you delete the image from the registry, do the containers that were already running stop?

Solutions

Solution to exercise 1

(a) The phenomenon is environment drift: the environments started out alike and grew apart over time, made worse by the fact that the CI server is a snowflake server whose configuration nobody knows how to reproduce.

(b) Installing PostgreSQL 16 on CI fixes this particular failure but not the underlying problem. There is still a hand-configured server, there is still no versioned description of the environment, and tomorrow the clash will be with some other piece (the Node version, a system library, the time zone). What is more, if another project on the same CI needs PostgreSQL 14, you are back to a dependency conflict that a global installation cannot resolve.

(c) With containers, the PostgreSQL version stops being a property of the server and becomes a property of the project, declared in a file versioned alongside the code. Marta and CI run literally the same PostgreSQL 16 package, and another project on the same CI can use 14 without interfering. The environment becomes reproducible and disposable instead of installed and permanent.

Solution to exercise 2

  1. Virtual machine. A container shares the host's kernel; a Linux kernel cannot run Windows binaries natively. You need real virtualization.
  2. Containers. This is the canonical use case: a versioned definition of the environment that all 12 run identically, without installing anything on their systems.
  3. Virtual machines, or containers inside separate VMs per customer. With untrusted third-party code you want virtualization's strong boundary, because the containers' shared kernel is a common attack surface.
  4. Containers. Density is precisely their great advantage: 300 VMs on one server would be unworkable given the overhead of 300 complete operating systems.
  5. Containers. Three containers with three different versions coexist without trouble, each on its own port. With native installations it would be an exercise in patience, and with three VMs you would be paying unnecessary overhead.

Solution to exercise 3

One valid analogy is baking: the image is the written recipe together with all the ingredients already measured out and packed in a sealed box; the container is the cake you bake from that box (you can bake many identical cakes from identical boxes, and each cake then lives its own life); the registry is the shop where those boxes are sold and from which anyone can get the one they need. Other equally correct analogies: mold/cast part/mold catalog, or blueprint/building/blueprint archive.

Answers to the questions:

  • If you delete a container, the image does not disappear. The image is independent and can keep producing new containers. (Whatever data that container had written to its own layer is indeed lost; that is an important point, covered in lesson 01-05 and solved in 03-06.)
  • If you delete the image from the registry, running containers keep working. The registry only serves to distribute: once the image has been pulled onto a machine and there are containers created from it, the registry no longer plays any part. The problem will show up the day another machine tries to pull it, or you need to recreate the container from scratch.

Conclusion

Docker exists because software does not run in a vacuum: it depends on an environment, and environments drift. Containerization attacks the problem by packaging the application together with its environment into a portable unit that runs isolated while sharing the host's kernel and therefore without a virtual machine's overhead. From that come its major advantages: portability, reproducibility, instant startup, density and isolation.

You take away three words that will structure the whole course: the image (the immutable template, the "class"), the container (the running instance, the "object") and the registry (the store from which images are distributed). And you take away a project, Aurora Libros, which you will containerize piece by piece.

You now know what Docker is and why it matters. In the next lesson, Installing Docker, you will move to practice: you will see the difference between Docker Engine and Docker Desktop, you will install Docker step by step on your operating system and you will confirm that everything works by running your very first container.

Docker: From Beginner to Advanced

Module 1: Introduction to Docker

Module 2: Working with Docker Images

Module 3: Docker Containers

Module 4: Docker Compose

Module 5: Advanced Docker Concepts

Module 6: Docker in Production

Module 7: Docker Ecosystem and Tools

© Copyright 2026. All rights reserved