Docker Introduction

Docker is a platform for packaging and running applications in isolated environments called containers. It matters because it lets you ship an application with its operating-system libraries, runtime, and startup command so it behaves consistently on a laptop, test server, or cloud host. Docker is not a full virtual machine; it shares the host kernel while isolating processes, filesystems, networking, and resource usage.

Overview: How Docker Works

At the center of Docker are three ideas: an image, a container, and a registry. An image is a read-only template made from stacked filesystem layers. A container is a running or stopped instance of that image with a thin writable layer on top. A registry, such as Docker Hub or a private registry, stores image manifests and layer blobs so machines can pull the same image by name and tag.

When you type docker run nginx:1.27-alpine, the Docker CLI sends a request to the Docker daemon. On Linux, the daemon creates the container using kernel features such as namespaces, control groups, and a union filesystem. On Docker Desktop for macOS or Windows, those Linux features run inside a small Linux VM managed by Docker Desktop. The user experience is mostly the same, but paths, file sharing, and performance can differ when bind mounting host files.

An image name like nginx:1.27-alpine points to a tag. The tag resolves to a manifest, and the manifest lists the content-addressed layers that make up the image. Docker downloads layers it does not already have, verifies them by digest, and stores them locally. Multiple images can share layers, which is why pulling a second related image can be fast.

A container usually runs one main process. If that process exits, the container stops. The container may have its own filesystem view, network interface, hostname, environment variables, and process tree, but it is still using the host kernel. This is why containers start quickly and use fewer resources than full VMs, but also why you should not treat containers as a security boundary equal to a separate physical machine.

Syntax

The general Docker CLI shape is:

docker [GLOBAL OPTIONS] COMMAND [COMMAND OPTIONS] [ARGUMENTS]
Part Meaning
docker The client program you run in your terminal.
GLOBAL OPTIONS Options that affect the client connection, such as selecting a context. Beginners usually omit these.
COMMAND The operation, such as run, pull, build, ps, stop, rm, or images.
COMMAND OPTIONS Flags for that command. For docker run, common flags include --name, -d, -p, -e, and -v.
ARGUMENTS Usually an image name, container name, file path, or command to run inside the container.

The most common first command is docker run:

docker run [OPTIONS] IMAGE [COMMAND] [ARGUMENTS]
Option Use
--name web Assigns a predictable container name instead of a random one.
-d Detached mode: run in the background and print the container ID.
-p 8080:80 Publishes host port 8080 to container port 80.
-e KEY=value Sets an environment variable in the container.
-v name:/path Mounts a named volume or bind mount for persistent data or local development files.
--rm Automatically removes the container when it exits.

Examples

Run a Test Container

docker run --rm hello-world:latest

Output:

Hello from Docker!
This message shows that your installation appears to be working correctly.

This downloads the hello-world image if it is not already present, creates a container from it, runs the image’s default command, prints a short message, and exits. The --rm flag removes the stopped container automatically. The image remains cached locally, so a later run does not need to download the same layers again.

Run a Web Server and Publish a Port

docker run -d --name web-intro -p 8080:80 nginx:1.27-alpine
docker ps --filter name=web-intro
docker stop web-intro
docker rm web-intro

Output:

a3f5c7d9e1b2
CONTAINER ID   IMAGE               COMMAND                  STATUS          PORTS                  NAMES
a3f5c7d9e1b2   nginx:1.27-alpine   "/docker-entrypoint..."   Up 3 seconds    0.0.0.0:8080->80/tcp   web-intro
web-intro
web-intro

The first command starts Nginx in the background and publishes the container’s port 80 on the host’s port 8080. Visiting http://localhost:8080 reaches the Nginx process inside the container. The docker stop command sends a stop signal and waits briefly before forcing shutdown if needed. The docker rm command deletes the stopped container, but it does not delete the nginx:1.27-alpine image.

Build a Small Image

FROM python:3.12-alpine
WORKDIR /app
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]

This Dockerfile expects an app.py file in the build directory. The pinned base image tag python:3.12-alpine is more reproducible than python:latest. The EXPOSE 8000 line documents that the application listens on port 8000, but it does not publish that port to your host. You still need -p when running the container.

docker build -t intro-python:1.0 .
docker run --rm -p 8000:8000 intro-python:1.0

Output:

[+] Building 2.1s (8/8) FINISHED
Successfully tagged intro-python:1.0
Serving application on port 8000

The build sends the current directory as the build context, executes each Dockerfile instruction, and stores the resulting image as intro-python:1.0. The run command starts a container from that image and maps host port 8000 to container port 8000.

How It Works Step by Step

  1. The Docker CLI parses your command and sends an API request to the Docker daemon through a local socket or named pipe.
  2. The daemon checks whether the requested image exists locally. If it does not, Docker contacts the configured registry, resolves the tag to a manifest, and downloads missing layers.
  3. Docker creates a container metadata record: name, image, command, environment variables, mounts, networking, labels, and restart policy.
  4. Docker prepares the filesystem by mounting the image’s read-only layers and adding a thin writable layer for that container.
  5. Docker creates network plumbing. By default, a container joins Docker’s bridge network and gets its own internal IP. Published ports are forwarded from the host to the container.
  6. The daemon starts the configured process. Logs written to stdout and stderr are captured by Docker’s logging driver and can be read with docker logs.
  7. When the main process exits, the container stops. Its writable layer remains until you remove the container, which is why stopped containers can still consume disk space.

This model explains several beginner surprises. Changing a file inside a running container changes only that container’s writable layer, not the original image. Removing an image can fail if a container still references it. Pulling an image can be fast because Docker only downloads layers missing from the local store.

Common Mistakes

Assuming EXPOSE Publishes a Port

FROM nginx:1.27-alpine
EXPOSE 80

This metadata is useful, but it does not make localhost:80 work. Publish the port when you run the container:

docker run --rm -p 8080:80 nginx:1.27-alpine

Using latest for Production

docker run -d --name production-web nginx:latest

The latest tag is only a tag name, not a promise of stability. It can move when the image publisher releases a new version. Use a specific tag, and for high-control production workflows consider pinning by digest after testing.

docker run -d --name production-web nginx:1.27-alpine

Baking Secrets into Images

FROM alpine:3.20
ENV API_KEY=changeme
RUN echo "$API_KEY" > /tmp/key.txt
RUN rm /tmp/key.txt

This is still wrong even though the file is removed later. Image layers are historical records; data written in an earlier layer can remain recoverable from that layer. Put secrets in runtime environment variables, Docker secrets, mounted files, or your orchestrator’s secret store, never in a Dockerfile.

Forgetting Containers Are Disposable

If a database writes data only inside the container filesystem, deleting the container deletes that data. Use a named volume for persistent service data and a bind mount for local source-code editing. Volumes are managed by Docker; bind mounts point at a specific host path.

Best Practices

  • Use specific image tags such as redis:7.2-alpine or node:20-alpine; avoid latest except for quick experiments.
  • Name important containers with --name so logs, stops, and removals are predictable.
  • Publish only the ports you need with -p. Do not assume EXPOSE opens anything on the host.
  • Keep images small by starting from appropriate base images and copying only the files the application needs.
  • Use a .dockerignore file in real projects so build contexts do not include node_modules, Git history, caches, or local secrets.
  • Store persistent data in named volumes, not in a container’s writable layer.
  • Read logs with docker logs and inspect container configuration with docker inspect before guessing.
  • Prefer docker compose for multi-container applications. The modern command has a space; the old docker-compose binary is legacy.

Practice Exercises

  1. Run nginx:1.27-alpine in detached mode with the name practice-web and publish it on host port 8090. Expected end state: http://localhost:8090 reaches Nginx, and docker ps shows one running container.
  2. Start redis:7.2-alpine with a named volume mounted at /data. Stop and remove the container, then create a new Redis container using the same volume. Hint: use docker volume create and -v volume-name:/data.
  3. Create a tiny Dockerfile for a command-line Python script using python:3.12-alpine. Build it with a versioned tag and run it with --rm. Expected end state: the script output appears, and no stopped container is left behind.

Summary

  • Docker packages applications as images and runs them as isolated containers.
  • An image is read-only layered content; a container adds a thin writable layer and a running process.
  • The Docker CLI talks to the Docker daemon, which pulls images, creates filesystems, configures networking, and starts processes.
  • Registries store manifests and layers, so tags can be pulled consistently across machines when you choose stable tags.
  • EXPOSE documents a port; -p or Compose ports: publishes it.
  • Containers are disposable by design, so persistent data belongs in volumes or external services.