What is Docker? Containers Explained for Beginners

If you have ever heard a developer say "it works on my machine" and watched it break in production, Docker is the direct answer to that problem. Docker packages an application and everything it needs -- its runtime, libraries, environment variables, and config files -- into a portable unit called a container. That container runs identically on a developer's laptop, a CI runner, and a cloud production server.

Docker was released in 2013 by Solomon Hykes and became one of the most influential tools in modern software development. Today it is foundational to nearly every DevOps tools stack.

How Docker Works

Docker uses three core concepts: images, containers, and the Docker daemon.

A Docker image is a read-only blueprint for your container. It is built from a text file called a Dockerfile that describes, step by step, what goes into the environment. An image is immutable -- once built, it does not change.

A container is a running instance of an image. You can run many containers from the same image simultaneously. Containers are ephemeral by default: when a container stops, its writable layer is gone unless you mount a volume.

The Docker daemon (dockerd) is a background service on the host that manages images, containers, networks, and volumes. The docker CLI sends API calls to this daemon.

The Dockerfile

The Dockerfile is where you define your image. Here is a practical example for a Node.js API:

# Use a specific version tag -- never just "latest" in production
FROM node:20-alpine

WORKDIR /app

# Copy dependency manifests first so Docker caches this layer
COPY package.json package-lock.json ./
RUN npm ci --only=production

# Copy the rest of the application code
COPY . .

EXPOSE 3000
CMD ["node", "src/server.js"]

A few things to note: using node:20-alpine instead of node:20 cuts the image size from ~1 GB to ~150 MB. Copying package.json before your source code means Docker reuses the cached npm ci layer on every rebuild where dependencies have not changed -- this alone can save minutes in CI pipelines.

Docker vs. Virtual Machines

The confusion between containers and VMs is common. Both provide isolated environments, but they work at different levels.

A virtual machine runs a full guest operating system on top of a hypervisor (VMware, VirtualBox, KVM). Every VM includes its own kernel, system libraries, and services. That means a simple web application might need a 5 GB VM image and 30 seconds to boot.

A Docker container shares the host machine's Linux kernel. It uses Linux namespaces to isolate processes, and cgroups to limit CPU and memory. The container only ships the application code and its user-space dependencies -- no kernel. A production container image for the same web application might be 80 MB and start in under a second.

The tradeoff is isolation. VMs offer stronger security boundaries (separate kernels). Containers share the kernel, so a kernel-level exploit could affect all containers on the host. For most web workloads this is acceptable; for high-security or multi-tenant scenarios, dedicated VMs or sandboxed runtimes like gVisor are worth considering.

The Docker Workflow

Working with Docker day-to-day follows a clear pattern:

# Build an image from the Dockerfile in the current directory
docker build -t myapp:1.0 .

# Run a container from that image
docker run -p 3000:3000 --name myapp-container myapp:1.0

# List running containers
docker ps

# View logs
docker logs myapp-container

# Stop and remove the container
docker stop myapp-container && docker rm myapp-container

For local development involving multiple services (app + database + cache), docker compose is the standard approach. A docker-compose.yml file describes the entire stack, and docker compose up starts everything with a single command.

Docker Registries

Images are distributed through registries. Docker Hub is the public default registry. Most production teams use a private registry -- Amazon ECR, Google Artifact Registry, or GitHub Container Registry -- for security and performance reasons.

Tagging images for a registry follows a naming convention:

# Tag the image for AWS ECR
docker tag myapp:1.0 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0

# Push to the registry
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0

From there, a Kubernetes cluster or a service like AWS ECS pulls the image and runs it. This is how code moves from a developer's branch to a production pod -- see the CI/CD guide for the full pipeline picture.

Why Docker Matters for DevOps

Docker solves the environment drift problem at scale. Before containers, deploying software meant configuring each server manually, managing version conflicts between applications, and debugging environment-specific failures. Docker made the application artifact -- the container image -- the unit of deployment, not the server.

Combined with Kubernetes for orchestration and tools like Helm for packaging, Docker is how virtually every modern cloud application gets shipped. Learning Docker is one of the first practical skills covered in any serious DevOps training.

Frequently Asked Questions