Key Takeaways
- Docker packages application code, dependencies, and startup configuration into images that you run as containers.
- An image is a read-only template; each container gets its own runtime configuration and writable filesystem layer.
- Linux containers share a Linux kernel. Docker Desktop supplies a virtualized Linux environment on macOS and Windows.
- A Dockerfile describes an image build. Registries distribute images, and Docker Compose defines application services together.
- Containers don't automatically provide persistent storage, published ports, or a complete security boundary.
Docker is a platform for building, distributing, and running applications in isolated environments called containers. You package your application and its dependencies into an image, then use that image to create containers with a consistent starting environment. That makes development, testing, and deployment less dependent on whatever happens to be installed on a particular machine.
The useful promise isn't “this runs unchanged on every computer.” It's “this packages the application environment explicitly.” You still need a compatible operating-system kernel and processor architecture, plus the right configuration, networking, and storage.
How does Docker work?
Docker uses a client-server architecture. The Docker CLI, the docker command you type, communicates with the Docker Engine API. The Engine's daemon, dockerd, manages objects such as images, containers, networks, and volumes.
The daemon can be local or remote. Docker contexts let one CLI switch between different daemon endpoints; installing a CLI alone doesn't give you a running container environment.
Docker CLI -- Engine API --> Docker Engine (dockerd)
|
+-- pull/push --> Image registry
|
+-- containerd
|
+-- OCI runtime, such as runc
|
+-- Container processes
Dockerfile + build context --> BuildKit --> ImageFor typical Linux containers, Docker delegates container lifecycle work through containerd, which uses a low-level runtime such as runc to create and start the container's processes. BuildKit handles image builds. These components have different jobs; Docker Engine and containerd aren't interchangeable names.
The Open Container Initiative, or OCI, defines standards for container images, runtimes, and distribution. Those standards help Docker-built images work with compatible tools beyond Docker itself.
What is a container?
A container is a runnable instance of an image. A running container consists of application processes, their filesystem view, and runtime settings such as environment variables, network connections, and mounts. A container can also exist in a stopped state.
For Linux containers, namespaces separate views of resources such as processes and networking, while control groups, or cgroups, provide resource accounting and limits. These are isolation mechanisms, not a guarantee of security: containers share a kernel, and their access depends on their configuration. See Docker's container security explanation.
Docker image vs container
An image is a read-only template containing filesystem layers and configuration, including a default startup command. Creating a container adds a writable layer above the image's layers. Two containers can start from the same image without sharing that writable layer.
Changing a file inside a container doesn't update its source image. Build a new image when you change application code, rather than treating a modified container as your deployment artifact. Docker's layering documentation explains this distinction.
What is a registry?
A registry stores and distributes container images. Docker Hub is a public registry; organizations can also use private registries. A repository groups related images, while a tag is a human-readable reference to a particular image version or variant.
For example, docker.io/library/python:3.13-slim identifies the registry, namespace, repository, and tag. Docker defaults to Docker Hub when you omit the registry, and to the latest tag when you omit a tag. The image reference documentation breaks down that naming scheme.
docker pull downloads an image; it doesn't start a container. docker push uploads an appropriately named image to a registry where you have permission to publish. Unlike a package manager installing individual application dependencies, these commands transfer container images.
Install Docker: Engine or Desktop?
Choose the installation path that matches where you want containers to run:
- macOS or Windows development: Docker Desktop bundles the CLI, Engine, build tooling, Compose, and a graphical interface.
- A Linux machine or server: follow the distribution-specific Docker Engine installation instructions to run the Engine directly on Linux. Docker Desktop is also available for Linux, but is a different installation choice.
Docker Engine can run Linux containers natively on Linux. On macOS, Docker Desktop runs them inside a Linux VM; on Windows, it supplies a Linux environment through backends such as WSL 2. The containers share that environment's Linux kernel, not the macOS or Windows kernel.
Windows containers are separate: they use Windows images and have host/image compatibility requirements that depend on the Windows versions and isolation mode. Consult Microsoft's Windows container compatibility matrix; a Linux image isn't a Windows container image.
After installation, start Docker Desktop or the Engine service, then verify the connection:
docker version
docker run --rm hello-worldA successful docker version reports client and server information. The second command runs a test container and removes it after exit. If the client cannot reach the server, check that the daemon is running and that your selected context and permissions are correct.
Advantages of Docker vs virtual machines
Containers isolate application processes; conventional virtual machines virtualize a machine that runs its own guest operating system. Containers usually avoid booting a separate guest OS for each application instance, but that doesn't make them universally faster or safer.
| Consideration | Linux container | Conventional VM |
|---|---|---|
| Kernel | Shares the kernel of its Linux execution environment | Runs a guest OS with its own kernel |
| Packaging | Application files, dependencies, and configuration | Guest OS plus application environment |
| Typical overhead | Less duplicated OS state per application instance | Additional guest OS memory and maintenance |
| Isolation | Shared-kernel isolation; configuration matters | Separate guest kernel; hypervisor and configuration still matter |
Containers and VMs aren't mutually exclusive: you can run many containers inside a VM, and Docker Desktop already adds a virtualization layer for Linux containers on macOS and Windows. Startup time, resource use, and filesystem performance depend on the workload and platform.
Docker's strongest advantage is often operational consistency: the same application image can move through local development, continuous integration, testing, and deployment. That benefits monoliths as well as microservices.
What is a Dockerfile? Build your first image
A Dockerfile is a text file of image-building instructions. The build context is the set of files available to the builder. In this example, you'll create three files in one directory and build a small Python HTTP application.
The base image uses the explicit python:3.13-slim tag. Python 3.13 is a supported release, and this tag is published in the Docker Official Image repository. It isn't described as the newest Python release, and its tag isn't an immutable pin.
Create app.py
This example uses Python's standard library, so it needs no third-party dependencies or requirements.txt. The application reads NAME from its environment and listens on port 8000 inside the container.
import os
import signal
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
def stop(_signum: int, _frame: object) -> None:
raise SystemExit(0)
class Handler(BaseHTTPRequestHandler):
def do_GET(self) -> None:
name = os.environ.get("NAME", "World")
body = f"Hello, {name}!\n".encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
if __name__ == "__main__":
signal.signal(signal.SIGTERM, stop)
with ThreadingHTTPServer(("0.0.0.0", 8000), Handler) as server:
try:
server.serve_forever()
except KeyboardInterrupt:
passCreate Dockerfile
FROM python:3.13-slim
WORKDIR /app
ENV NAME=World
COPY --chmod=644 app.py .
USER 10001:10001
EXPOSE 8000
CMD ["python", "app.py"]FROMselects the base image.WORKDIRsets the working directory, creating it if necessary.ENVsupplies the default greeting value used by the application.COPYcopies the application into the image. The explicit mode keeps it readable by the runtime user.USER 10001:10001runs the application with a non-root user ID and group ID.EXPOSEdocuments the listening port; it doesn't publish that port.CMDsupplies the default container command.
Prefer COPY for ordinary local files. Use ADD when you intentionally need its additional behavior, such as extracting a local tar archive. The Dockerfile reference documents both instructions.
Create .dockerignore
.git
.venv
__pycache__
*.pyc
.env
.env.*A .dockerignore file excludes matching files from the build context. Keeping version-control data, local environments, caches, and secret files out reduces unnecessary transfers and accidental inclusion. This example copies only app.py, but context hygiene still matters.
Build and run the image
Run these commands from the directory containing the three files. The line continuations below use Bash syntax; on other shells, enter each command on one line.
docker build --pull -t hello-docker:1.0 .
docker run -d --name hello-docker \
-p 127.0.0.1:8080:8000 \
-e NAME=Docker \
hello-docker:1.0-t names and tags the image; the final . selects the current directory as the build context. --pull checks for updated referenced base images. Building produces an image, not a running application.
docker run creates and starts a container. Here, -d runs it in the background, --name names it, and -e overrides the image's NAME value. The -p mapping publishes container port 8000 on host port 8080, bound to the host's loopback address.
On your local Docker installation, open http://127.0.0.1:8080 in a browser. You'll see Hello, Docker!. Binding to loopback avoids the usual all-interface publication of a mapping such as -p 8080:8000; see the docker run reference for port behavior.
View the running container and its request logs, then stop and remove it:
docker ps
docker logs hello-docker
docker stop hello-docker
docker rm hello-dockerRemoving this container leaves the image available for another run.
Image layers and build caching
Filesystem-changing steps contribute filesystem layers. Instructions such as ENV and CMD set image configuration instead; “every instruction creates a filesystem layer” is an inaccurate shortcut. WORKDIR also creates its directory when needed, so it isn't always metadata-only.
BuildKit is Docker's default builder for Linux image builds and can reuse cached build results. For an application with dependencies, copy its pinned dependency file and install dependencies before copying frequently changing source code. That ordering lets source edits reuse an unchanged dependency-installation result; Docker's cache optimization guide explains the pattern.
For example, replace the single application-copy step with this fragment when your project actually includes requirements.txt, keeping it before USER:
# Fragment: requires your project's pinned requirements.txt.
COPY requirements.txt .
RUN python -m pip install --no-cache-dir -r requirements.txt
COPY --chmod=644 app.py .Where does container data live?
A container's writable layer survives stopping and restarting that same container, but disappears when the container is removed. Treat it as disposable, not as the home for irreplaceable data.
- Volumes are Docker-managed storage with a lifecycle separate from individual containers.
- Bind mounts expose a specific host path inside a container, which is useful for editing source code during development.
This small named-volume example writes a file with one temporary container and reads it with another:
docker volume create docker-demo-data
docker run --rm \
--mount type=volume,src=docker-demo-data,dst=/data \
python:3.13-slim python -c "from pathlib import Path; Path('/data/message.txt').write_text('saved', encoding='utf-8')"
docker run --rm \
--mount type=volume,src=docker-demo-data,dst=/data \
python:3.13-slim python -c "from pathlib import Path; print(Path('/data/message.txt').read_text(encoding='utf-8'))"
# Delete the demonstration data when finished.
docker volume rm docker-demo-dataThe second container prints saved even though the first was removed. A volume isn't a backup, though: important data still needs an independent backup and recovery plan.
Use Docker Compose for application environments
Docker Compose defines services, networks, and volumes in a YAML file. It's useful for local environments containing an application, database, and other services, rather than maintaining a collection of long run commands.
Start with this single-service compose.yaml in the example directory:
services:
web:
build: .
image: hello-docker:1.0
ports:
- "127.0.0.1:8080:8000"
environment:
NAME: ComposeAfter removing the earlier container so port 8080 is available, build and start the service, then tear it down:
docker compose up --build -d
docker compose downAdd other services as your application needs them. Compose and cluster orchestration aren't the same topic; our Kubernetes vs Docker comparison explains where those tools fit.
Common Mistakes and a Security Baseline
- Assuming isolation means invulnerability: run application processes without root privileges, avoid unnecessary privileges and host mounts, and protect access to the Docker daemon. Understanding Linux users, groups, and permissions helps explain the sample's numeric user IDs.
- Confusing a tag with a fixed artifact: use trusted base images, pin reviewed release inputs, and rebuild with security updates. Docker's image-building best practices explain digest pinning and regular rebuilds.
- Baking credentials into images: don't copy secrets into image layers or put them in Dockerfile
ARGorENVvalues. Use BuildKit secret mounts for build-time credentials and your deployment environment's secret mechanism at runtime. - Assuming resource limits are automatic: Docker containers have no resource constraints by default. Set appropriate CPU and memory limits for shared environments; see Docker's resource constraints guide.
FAQ
What is the difference between a Docker image and a container?
An image is a read-only template containing application files, dependencies, and startup configuration. A container is an instance created from that image, with its own runtime settings and writable layer; it can be running or stopped.
How is Docker different from a virtual machine?
Linux containers share a kernel, while conventional VMs run guest operating systems with their own kernels. Containers typically require less duplicated OS state per application instance, but they can run inside VMs and don't provide identical isolation guarantees.
Does Docker run natively on macOS and Windows?
The Docker CLI runs on macOS and Windows, but local Linux containers need a Linux kernel supplied through virtualization, such as Docker Desktop's Linux VM or WSL 2 environment. Windows containers use Windows images and have separate host compatibility requirements.
Is Docker Desktop free for commercial use?
Under Docker's published terms, Docker Desktop is free for small businesses with fewer than 250 employees AND less than $10 million in annual revenue. Professional use in larger organizations and use by government entities require a paid subscription. Docker Engine is open source; that doesn't make Docker Desktop unrestricted.
Does EXPOSE publish a Docker container's port?
No. EXPOSE documents an intended listening port in the image; publish a host port with docker run -p or a Compose ports mapping. The application must also listen on an address reachable through the container network, such as 0.0.0.0 in this example.
Further Reading
- Docker overview — architecture, objects, and the container workflow.
- OCI standards overview — image, runtime, and distribution specifications.
- Dockerfile reference — instruction syntax and exact behavior.
- Docker image-building best practices — trusted bases, caching, pinning, and rebuilds.
- Docker volumes — persistent storage independent of container removal.
- How Docker Compose works — services and application configuration.
- Docker Engine security — namespaces, cgroups, privileges, and daemon access.
- Docker Desktop licensing — current eligibility and subscription requirements.
