Skip to content

Docker Introduction and Fundamentals

Docker is a tool that packages an application together with everything it needs to run: the runtime, libraries, settings and files. That package is called an image, and a running copy of it is called a container. A container runs the same way on your laptop, on a test server and in the cloud.

Docker has become a basic skill for every developer and DevOps engineer. ASP.NET Core APIs, Python FastAPI services, databases, caches and CI/CD pipelines all commonly run in containers. This first lesson explains Docker in simple words: what it is, why it exists, how it works inside, and its main building blocks. Every command output on this page comes from a real run on Docker 29.8 (Docker Desktop on Windows).

Think of shipping containers. Before them, every cargo (bananas, cars, furniture) was loaded differently, and every port and ship handled it in its own way. Then the world agreed on one standard steel box. Now any ship, train or truck can carry any container, without caring what's inside. Docker does the same for software: one standard "box" that any computer with Docker can run, whatever is inside.

Understand the problem before Docker

Suppose you develop an ASP.NET Core application on your laptop. To run, your application requires:

  • the .NET 10 runtime
  • SQL Server connectivity
  • specific NuGet packages
  • environment variables (for example the connection string or the environment name)
  • configuration files such as appsettings.json

It works perfectly on your laptop. Now you deploy the same application to another developer's laptop, or to an AWS EC2 server, and it fails, because:

  • the required .NET runtime is missing, or a different version is installed,
  • the server has a different configuration,
  • some dependencies are unavailable,
  • the environment variables are different,
  • the operating system configuration differs.

This is one of the most common problems in software deployment, usually summed up as "but it works on my machine!" The application itself is fine; what differs is the environment around it.

Docker solves this by packaging your application together with its required dependencies (the runtime, libraries and configuration) into a container image. Wherever that image runs, on a laptop, a test server or EC2, the application finds exactly the environment it was built and tested with.

The problems Docker solves

1. "It works on my machine"

As in the example above: with Docker, the application and its environment are shipped together. If it runs in the container on your laptop, it runs the same way everywhere, because the "machine" now travels with the app.

2. Slow and error-prone setup

Joining a new project used to mean a day of installing the right SDK, database, message queue and tools, following a long document. With Docker, one command starts the whole environment. For example, a SQL Server or Redis instance runs from a ready-made image in seconds, without installing it on your computer.

3. Conflicting versions

Project A needs Python 3.9 and project B needs Python 3.12. Project C needs PostgreSQL 13 and D needs 16. Installed directly on one machine, they clash. In containers, each application has its own isolated environment, so they can't interfere with each other.

4. Heavy virtual machines

Before containers, the usual way to isolate applications was a virtual machine (VM) for each one. Each VM runs a complete operating system, uses gigabytes of disk and memory, and takes minutes to start. Containers give similar isolation but share the host's operating system kernel, so they are much smaller and start in seconds or less.

5. Different environments for development, testing and production

The same image is used in every stage. The tester tests exactly the image that will go to production, not a "similar" setup. This removes a whole class of "it worked in testing" surprises and is the foundation of modern CI/CD pipelines.

Containers vs virtual machines

Virtual machines Containers App A App B App C Libraries Libraries Libraries Guest OSGBs, slow boot Guest OSGBs, slow boot Guest OSGBs, slow boot Hypervisor (Hyper-V, VMware, VirtualBox) Host operating system Hardware App A App B App C Libraries Libraries Libraries Docker Engine Host operating system (shared kernel) Hardware
Each virtual machine carries a full guest operating system; containers share the host's kernel and carry only the app and its libraries.
PointVirtual machineContainer
What it virtualisesHardware: each VM runs a full operating systemThe operating system: containers share the host's kernel
SizeGigabytesMegabytes (the nginx web server image below is 94.5 MB; our own image is 12.1 MB)
Start-up timeMinutesSeconds or less
Resource useHeavy: memory reserved for each guest OSLight: only what the app needs
IsolationVery strong (separate kernels)Strong, but the kernel is shared
How many per serverA few to tensTens to hundreds
Best forRunning different operating systems, strong security boundariesPackaging and running applications and microservices

Containers and VMs are not enemies: in the cloud, containers usually run inside VMs. And on Windows and macOS, Docker Desktop itself runs a small Linux VM (on Windows, through WSL 2) in which the Linux containers run.

A short history

Docker was created by Solomon Hykes at a company called dotCloud and released as open source in 2013. The ideas behind containers (Linux namespaces and cgroups) already existed, but Docker made them easy: one simple command line, a simple file format to describe images, and a public registry to share them. Today container images follow an open industry standard, the OCI (Open Container Initiative), so images built with Docker also run on other container tools and on Kubernetes.

Docker architecture

Docker uses a client-server architecture:

Client the docker CLI docker build docker pull docker run Docker host Docker daemon (dockerd) uses containerd and runc to run containers Images nginx:alpine my-app:1.0 read-only templates Containers web (running) api (running) running instances Registry Docker Hub, ECR, ACR nginx alpine dotnet/aspnet your images REST API pull / push run
The client sends commands to the daemon; the daemon pulls images from a registry and runs containers from them.

Docker has four important components:

1. Docker Client

The Docker Client is the command-line interface through which you communicate with Docker. You type commands into PowerShell or any terminal, for example:

docker run nginx              # start a container from the nginx image
docker ps                     # list running containers
docker images                 # list images on this machine
docker stop mycontainer       # stop a container

The client doesn't run containers itself. It sends each command to the Docker daemon through a REST API, and shows you the response. The client can even talk to a daemon on another machine.

2. Docker Daemon

The Docker Daemon is a background service responsible for managing all Docker objects. It is commonly called dockerd. Its responsibilities include:

  • building images,
  • running containers,
  • managing networks,
  • managing volumes,
  • managing the container lifecycle (create, start, stop, remove).

Under the hood, the daemon uses containerd (which manages running containers) and runc (the low-level tool that actually starts each container process). The machine where the daemon runs, and where images and containers are stored, is called the Docker host.

3. Docker Registry

A Docker Registry stores Docker images, so they can be shared and downloaded. For example:

  • Docker Hub: the default public registry
  • Amazon Elastic Container Registry (ECR)
  • GitHub Container Registry
  • Azure Container Registry (ACR)

Suppose you execute:

docker pull nginx

Docker downloads the requested image from a registry (Docker Hub, by default) if it isn't already available locally. docker run does the same automatically: if the image isn't on your machine yet, it is pulled first, then started.

4. Docker Objects

Docker manages several objects:

ObjectPurpose
ImagesApplication templates
ContainersRunning instances of images
VolumesPersistent data
NetworksCommunication between containers

Each of these objects is explained in detail below.

How the components work together

The hello-world image explains this flow in its own output. Here is the real result of running it:

docker run --name mmcdemo-hello hello-world
Output
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.

How containers are isolated

A container is not a small VM; it's a normal process on the host that Linux keeps in its own "bubble" using two kernel features:

  • Namespaces give the container its own view of the system: its own process list, network, hostname, users and file system. Inside the container, the app thinks it's alone on the machine.
  • Control groups (cgroups) limit how much CPU, memory and disk I/O a container may use, so one container can't starve the others.

You can see the isolation in action. Inside an nginx container, the process list shows only nginx, and nginx is process number 1, even though the host has hundreds of processes:

docker exec mmcdemo-web ps
Output
PID   USER     TIME  COMMAND
    1 root      0:00 nginx: master process nginx -g daemon off;
   22 nginx     0:00 nginx: worker process
   23 nginx     0:00 nginx: worker process

Docker objects in detail

Now let's look more closely at the objects you work with every day, plus the tools around them.

Image

An image is a read-only template that contains the application and everything it needs: a minimal operating system layer, the runtime, libraries, your code and configuration. Think of it as a class in C#: a definition from which you create objects. Images are named repository:tag, for example nginx:alpine or mcr.microsoft.com/dotnet/aspnet:8.0. If you leave out the tag, Docker uses latest.

Container

A container is a running (or stopped) instance of an image, like an object created from a class. You can run many containers from the same image, and each gets its own small writable layer on top of the read-only image, so changes in one container don't affect the image or the other containers.

Dockerfile

A Dockerfile is a plain text file with step-by-step instructions to build an image. You will see a complete example below.

Registry and repository

A registry stores images (Docker Hub, ECR, ACR). A repository is one named collection of related images inside a registry, with different tags for different versions, for example nginx:1.27, nginx:alpine and nginx:latest.

Volume

A container's writable layer is deleted when the container is removed. To keep data, such as database files or uploads, Docker uses volumes: storage that lives outside the container and survives when it is recreated.

Network

Docker creates virtual networks so containers can talk to each other (for example, an API container talking to a database container by name) while staying isolated from everything else. Ports are published to the host with -p host:container.

Docker Compose

Docker Compose describes a multi-container application (for example, web app, database and cache) in one compose.yaml file and starts all of it with docker compose up.

Docker Desktop and Docker Engine

  • Docker Engine is the core: the daemon, its API and the CLI. On Linux servers you install the engine directly.
  • Docker Desktop is the easy installer for Windows and macOS. It includes the engine (running in a small Linux VM, via WSL 2 on Windows), the CLI, Docker Compose and a graphical dashboard. It's free for personal use, education, non-commercial open source and small businesses (fewer than 250 employees and less than USD 10 million annual revenue); larger companies need a paid subscription.
TermIn one lineC# analogy
DockerfileRecipe to build an imageSource code
ImageRead-only package of the app and its environmentA class
ContainerA running instance of an imageAn object (instance)
RegistryOnline store of imagesNuGet
VolumeData that survives container restartsA database outside the app
Docker ComposeRuns several containers togetherA solution with several projects

Running your first real container

Let's run the nginx web server. First, download (pull) the image from Docker Hub:

docker pull nginx:alpine
Output (last lines)
26185d08a3ec: Pull complete
2ec5fb117ffa: Pull complete
b9b388a21ecf: Pull complete
Digest: sha256:785835ea65db4bba6f021acc0f3be91b0fef76a53fe41f8588c6698d2aed0606
Status: Downloaded newer image for nginx:alpine
docker.io/library/nginx:alpine

Each "Pull complete" line is one layer of the image (more on layers below). Now start a container from it:

docker run -d --name mmcdemo-web -p 8085:80 nginx:alpine
  • -d runs it in the background (detached).
  • --name mmcdemo-web gives the container a friendly name.
  • -p 8085:80 publishes the container's port 80 on port 8085 of your computer.

Docker prints the new container's ID. List running containers:

docker ps
Output
CONTAINER ID   IMAGE          STATUS         PORTS                                     NAMES
97cb7969656f   nginx:alpine   Up 2 seconds   0.0.0.0:8085->80/tcp, [::]:8085->80/tcp   mmcdemo-web

Open http://localhost:8085 in a browser and you'll see the nginx welcome page. The page's HTML contains:

Output
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>

You now have a web server running, and you never installed nginx on your computer. The container even has its own operating system files. Our host is Windows, but inside the container:

docker exec mmcdemo-web cat /etc/os-release
Output (first lines)
NAME="Alpine Linux"
ID=alpine

Other everyday commands on the same container:

docker logs mmcdemo-web        # what the app printed (here: nginx's access log)
docker stop mmcdemo-web        # stop it
docker start mmcdemo-web       # start it again
docker rm -f mmcdemo-web       # remove it (-f stops it first)
docker logs mmcdemo-web (last line)
172.17.0.1 - - [03/Nov/2026:14:16:27 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.18.0" "-"

Building your own image with a Dockerfile

Here is a tiny application: a shell script that prints a message, the container's operating system, and its hostname.

#!/bin/sh
echo "Hello from MakeMyCodes inside a container!"
echo "Container OS: $(grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '\"')"
echo "Hostname (container ID): $(hostname)"

And the Dockerfile, in the same folder:

# 1. Start from a small Linux base image
FROM alpine:3.20

# 2. Set the working folder inside the image
WORKDIR /app

# 3. Copy our script from the computer into the image
COPY hello.sh .

# 4. The command that runs when a container starts
CMD ["sh", "hello.sh"]
InstructionMeaning
FROMThe base image to start from (every Dockerfile starts with it)
WORKDIRCreates the folder and makes it the current folder for the next instructions
COPYCopies files from your computer (the "build context") into the image
RUNRuns a command while building, for example to install a package (not used here)
EXPOSEDocuments which port the app listens on
CMDThe default command when a container starts

Build the image, giving it a name and tag with -t (the . means "use the current folder"), and run it:

docker build -t mmcdemo-hello-app:1.0 .
docker run --rm mmcdemo-hello-app:1.0
Output of docker run
Hello from MakeMyCodes inside a container!
Container OS: Alpine Linux v3.20
Hostname (container ID): 8ccee06920ac

The hostname is the container's short ID: every container gets its own. --rm deletes the container automatically when it finishes. The finished image is just 12.1 MB.

Image layers and the build cache

Every instruction in a Dockerfile creates a layer: a set of file changes stacked on top of the previous one. You can list them with docker history:

docker history mmcdemo-hello-app:1.0
Output
CREATED BY                                      SIZE
CMD ["sh" "hello.sh"]                           0B
COPY hello.sh . # buildkit                      12.3kB
WORKDIR /app                                    8.19kB
CMD ["/bin/sh"]                                 0B
ADD alpine-minirootfs-3.20.10-x86_64.tar.gz …   8.47MB

Read it from the bottom up: the Alpine base layer (8.47 MB), then our WORKDIR and COPY layers (a few kilobytes). Layers make Docker efficient:

  • Shared storage: ten images built on the same base share one copy of the base layers on disk.
  • Fast downloads: when you pull a new version of an image, only the changed layers are downloaded.
  • Build cache: when you rebuild, unchanged steps are reused. Building the same Dockerfile a second time, the WORKDIR and COPY steps were simply marked CACHED and nothing was redone. This is why you put rarely-changing steps (installing packages) before frequently-changing ones (copying your code) in a Dockerfile.

The container lifecycle

  docker create          docker start            docker stop             docker rm
Created  ------------->  Running  <------------>  Exited  -------------->  (removed)
          (docker run = create + start)   docker start

Stopping a container keeps it, with its writable layer, so it can be started again. After we stopped our nginx container, docker ps -a (which also lists stopped containers) showed:

Output
NAMES           IMAGE          STATUS
mmcdemo-web     nginx:alpine   Exited (0) Less than a second ago
mmcdemo-hello   hello-world    Exited (0) 5 seconds ago

Exited (0) means the main process ended normally (exit code 0). hello-world exits by design as soon as it has printed its message: a container lives only as long as its main process.

Essential Docker commands

CommandWhat it does
docker versionShows client and server (daemon) versions
docker pull nginx:alpineDownloads an image from a registry
docker imagesLists images on this machine
docker run -d -p 8085:80 --name web nginx:alpineCreates and starts a container
docker ps / docker ps -aLists running / all containers
docker logs webShows a container's output
docker exec -it web shOpens a shell inside a running container
docker stop web / docker start webStops / starts a container
docker rm webRemoves a container
docker build -t my-app:1.0 .Builds an image from a Dockerfile
docker rmi my-app:1.0Removes an image
docker push myrepo/my-app:1.0Uploads an image to a registry

Benefits and limitations

BenefitsLimitations
Same behaviour on every machineA learning curve: images, layers, networking, volumes
Fast start-up and small footprint compared with VMsWeaker isolation than VMs, because the kernel is shared
Easy setup of databases, caches and tools for developmentData is lost with the container unless you use volumes
Each app has isolated dependencies; no version conflictsLinux containers need a Linux kernel (a VM on Windows and macOS)
Standard images for CI/CD, Kubernetes and every major cloudImages must be kept updated for security fixes

Interview questions

1. What is Docker?

An open-source platform for building, shipping and running applications in containers: lightweight, isolated environments that package an application with everything it needs, so it runs the same way everywhere.

2. What is the difference between an image and a container?

An image is a read-only template (like a class); a container is a running or stopped instance created from it (like an object). Many containers can run from one image, each with its own writable layer.

3. How is a container different from a virtual machine?

A VM virtualises hardware and runs a full guest operating system. A container shares the host's kernel and isolates only the application, using namespaces and cgroups, so it is much smaller and starts much faster.

4. Explain the Docker architecture.

It is client-server: the Docker client sends commands through a REST API to the Docker daemon (dockerd), which builds images, pulls and pushes them from registries such as Docker Hub, and runs containers using containerd and runc.

5. What is a Dockerfile?

A text file of instructions (FROM, WORKDIR, COPY, RUN, CMD...) that Docker follows to build an image. Each instruction creates a layer, and unchanged layers are reused from the build cache.

6. What happens to data when a container is removed?

Everything in the container's writable layer is deleted. Data that must survive belongs in a volume (or a bind mount to a host folder).

Summary

  • Docker packages an application with its environment into an image, and runs it as a container.
  • It solves "works on my machine", slow setups, version conflicts and the weight of virtual machines.
  • Containers share the host kernel and are isolated with namespaces and cgroups.
  • Without Docker, an app that works on your laptop can fail elsewhere because the runtime, configuration or dependencies differ.
  • Docker has four important components: the Client (CLI), the Daemon (dockerd), the Registry (Docker Hub, ECR) and Docker Objects (images, containers, volumes, networks).
  • Core objects: images, containers, Dockerfiles, registries, volumes and networks; Compose runs several containers together.
  • Images are built in layers, which makes storage, downloads and rebuilds fast.