Introduction to Docker
What Docker is and the problem it solves. Images versus containers, how containers differ from virtual machines, the core workflow, and when not to use it.
Docker packages an application together with everything it needs to run, the runtime, the libraries, the system tools, the configuration, into a single artifact that behaves the same wherever it runs.
That is the whole pitch, and it addresses a problem every team has had.
The problem
An application works on a developer's laptop and fails in staging. The cause is invariably an environment difference: a different Python version, a missing system library, a config file somewhere else, a locale setting nobody thought about.
The traditional fixes were documentation nobody kept current, configuration management that drifted, or a virtual machine image that was tens of gigabytes and slow to build.
Docker's answer is to ship the environment with the application. If it runs in the container on your laptop, it runs in the same container in production, because it is the same container.
Images and containers
The distinction people most often blur, and everything else depends on it.
| Image | Container | |
|---|---|---|
| What it is | A read-only template | A running instance of an image |
| Analogy | A class | An object |
| State | Immutable | Has a writable layer |
| Count | One image | Many containers from it |
docker pull nginx:1.27 # fetch an image
docker run -d nginx:1.27 # create and start a container from it
docker run -d nginx:1.27 # and another, from the same image
Images are built in layers, each the result of an instruction in the Dockerfile. Layers are cached and shared. Ten images based on the same Ubuntu layer store that layer once. This is why images are far smaller in practice than their nominal sizes suggest, and why ordering Dockerfile instructions well makes rebuilds fast.
How this differs from a virtual machine
A VM virtualises hardware and runs a full guest operating system. A container shares the host kernel and isolates only the process.
| Container | Virtual machine | |
|---|---|---|
| Isolation | Process-level, shared kernel | Full hardware virtualisation |
| Start time | Milliseconds | Tens of seconds |
| Size | Megabytes | Gigabytes |
| Density per host | Hundreds | Tens |
| Kernel | Shared with host | Its own |
| Isolation strength | Good | Stronger |
Two consequences worth knowing. Containers cannot run a different kernel. Linux containers on macOS or Windows actually run inside a small Linux VM that Docker Desktop manages, which is why file sharing between host and container is slower on those platforms. And because isolation is weaker than a VM's, running genuinely untrusted code deserves a stronger boundary.
The workflow
# 1. Write a Dockerfile describing the image
# 2. Build it
docker build -t myapp:1.0 .
# 3. Run a container
docker run -d -p 8080:80 --name web myapp:1.0
# 4. Look at what is happening
docker ps
docker logs web
# 5. Clean up
docker stop web && docker rm web
A minimal Dockerfile:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
The ordering there is deliberate and worth noticing early. Dependencies are copied and installed before the application code, so a change to your source does not invalidate the cached npm ci layer. Reversing those two lines makes every build reinstall everything, which is the single most common Dockerfile performance mistake.
The pieces
Docker Engine is the daemon that builds images and runs containers. The CLI talks to it over a socket, which is why docker commands work against a remote host just as easily as a local one.
A registry stores images. Docker Hub is the default; ECR, GCR, GitHub Container Registry and self-hosted registries all work the same way.
A volume persists data beyond a container's life. Anything written to a container's own filesystem disappears when it is removed, which surprises people exactly once.
A network lets containers reach each other. Containers on a user-defined network resolve each other by name, which is how multi-container applications are wired together.
Containers are disposable
This is the mental shift that makes Docker click.
A container is not a small server you log into and maintain. It is a process you replace. Configuration comes from environment variables, data lives in volumes or a database, and logs go to stdout, so that destroying and recreating the container loses nothing.
Treating containers as long-lived pets, SSHing in, installing packages, editing files, produces exactly the environment drift Docker exists to eliminate.
Where Docker is not the answer
Worth stating, because it is frequently applied by default:
- Desktop GUI applications. Possible, awkward, rarely worth it.
- Workloads needing a specific kernel or kernel modules. Containers share the host kernel.
- Genuinely untrusted multi-tenant code, where a VM or something like Firecracker gives a stronger boundary.
- A single simple application on one server that is working fine. Containerising it adds a layer for no gain.
Where to go next
| Topic | What you will learn |
|---|---|
| Why Use Docker | The practical benefits in more depth |
| Docker vs Virtual Machines | The architectural difference |
| Installing Docker | Setting it up on your platform |
| Key Concepts | Images, containers, volumes, networks |
| Dockerfile Basics | Building your own images |
Quick reference
| Task | Command |
|---|---|
| Pull an image | docker pull <image>:<tag> |
| Build | docker build -t <name>:<tag> . |
| Run detached with a port | docker run -d -p 8080:80 --name <n> <image> |
| Running containers | docker ps |
| All containers | docker ps -a |
| Logs | docker logs -f <name> |
| Shell inside | docker exec -it <name> sh |
| Stop and remove | docker stop <n> && docker rm <n> |
| Disk usage | docker system df |
Key takeaways
- An image is a template; a container is a running instance. One image, many containers.
- Images are layered and cached, which is why Dockerfile instruction order matters so much.
- Containers share the host kernel: far lighter than VMs, and a weaker isolation boundary.
- Copy dependency manifests before source code so the install layer stays cached.
- Containers are disposable. Config in environment variables, data in volumes, logs to stdout.
- Linux containers on macOS and Windows run in a hidden VM, which is why file sharing is slower there.
- Docker is not always the answer: a working single-server application may not need it.
Check your understanding
Images and containers
You run docker run three times against the same image. How many images and containers now exist?
A build that is slow every time
A Dockerfile copies all source code before running npm ci. Every build reinstalls all dependencies even when only one source file changed. Why?
Frequently Asked Questions
What is Docker used for?
Packaging an application with its runtime, libraries and configuration into a single image that runs identically anywhere Docker runs. It eliminates the "works on my machine" class of problem, since the environment ships with the application rather than being recreated at each destination. In practice it is used for local development environments, CI build and test isolation, and as the deployment unit for anything running on Kubernetes, ECS or similar platforms.
What is the difference between a Docker image and a container?
An image is a read-only template built from a Dockerfile; a container is a running instance created from that image with its own writable layer. The relationship is like a class and an object. One image can produce any number of containers, all sharing the same underlying layers and differing only in their writable layer and running state. Images are built and pulled; containers are run, stopped and removed.
How is Docker different from a virtual machine?
A VM virtualises hardware and runs a complete guest operating system with its own kernel, typically taking gigabytes and tens of seconds to boot. A container shares the host kernel and isolates only the process, so it is measured in megabytes and starts in milliseconds. That makes containers far denser and faster, at the cost of weaker isolation, and it means a container cannot run a different kernel, which is why Linux containers on macOS or Windows run inside a small managed VM.
Do I need to learn Docker before Kubernetes?
Yes. Kubernetes orchestrates containers and assumes you already understand images, containers, ports, volumes and environment variables. A large share of Kubernetes problems turn out to be container problems in disguise: a wrong entrypoint, a missing environment variable, a process writing to a path that is not persisted. Being comfortable with docker build and docker run makes the Kubernetes learning curve considerably shorter.
Why does my data disappear when a container restarts?
Because a container's writable layer is destroyed when the container is removed, and it is not preserved across docker rm and a fresh docker run. Anything that must survive belongs in a volume, mounted into the container, or in an external service such as a database. This catches nearly everyone once, and it is the practical expression of the principle that containers are disposable. Configuration comes from environment variables and data lives outside.
Is Docker always the right choice?
No. A single simple application running fine on one server gains little from containerisation and pays the cost of another layer to understand and maintain. Desktop GUI applications are awkward in containers. Workloads needing a specific kernel or kernel modules cannot use them, since containers share the host kernel. And genuinely untrusted multi-tenant code deserves a stronger boundary than a container provides. A VM or a microVM runtime such as Firecracker.