Containers transformed how software is built and deployed. Before understanding Kubernetes, you need to understand containers — what they are, why they were created, and how they work.
The Problem Before Containers
Deploying software used to mean: "it works on my machine." An application that ran fine on the developer's laptop would fail on the staging server because of different library versions, OS configuration, or installed dependencies. The solution was either to carefully document every system dependency or to ship entire virtual machines — both fragile and slow.
What is a Container?
A container is a lightweight, isolated process that runs on a shared operating system kernel. It packages an application with everything it needs to run: code, runtime, libraries, environment variables, and configuration. From the application's perspective, it has its own filesystem, network, and process space — but no full OS kernel of its own.
Containers vs Virtual Machines
| Virtual Machine | Container | |
|---|---|---|
| OS | Full guest OS per VM | Shares host kernel |
| Startup | Minutes | Milliseconds |
| Size | GBs | MBs |
| Isolation | Strong (hardware-level) | Process-level |
| Overhead | High | Very low |
Containers use two Linux kernel features: namespaces (isolate process, network, filesystem) and cgroups (limit CPU and memory).
Docker
Docker made containers accessible. It provides:
- Docker Engine: The runtime that builds and runs containers
- Docker CLI: Command-line tool to interact with the engine
- Dockerfile: A recipe for building a container image
- Docker Hub: Public registry for sharing container images
Container Images
A container image is an immutable, layered package. Each instruction in a Dockerfile creates a read-only layer. When you run a container, a thin writable layer is added on top. Images are stored in registries (Docker Hub, AWS ECR, GitHub Container Registry).
Example Dockerfile
FROM node:20-alpine # base image
WORKDIR /app # set working directory
COPY package*.json ./ # copy dependency files
RUN npm install # install dependencies (creates a layer)
COPY . . # copy application code
EXPOSE 3000 # document the port
CMD ["node", "server.js"] # default command to run
Core Docker Commands
docker build -t myapp:v1 . # build image from Dockerfile
docker images # list local images
docker run -p 8080:3000 myapp:v1 # run container, map port
docker run -d --name app myapp:v1 # run in background
docker ps # list running containers
docker ps -a # list all containers
docker logs app # view container logs
docker exec -it app /bin/sh # open shell inside container
docker stop app # stop container
docker rm app # remove container
docker rmi myapp:v1 # remove image
docker pull nginx:alpine # download image from registry
docker push myregistry/myapp:v1 # push image to registry
Docker Compose
Docker Compose lets you define and run multi-container applications with a single YAML file:
version: '3.9'
services:
web:
image: nginx:alpine
ports:
- "80:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
docker compose up -d # start all services in background
docker compose down # stop and remove
Container Registries
- Docker Hub: Public and private images; free tier available
- Amazon ECR (Elastic Container Registry): Private registry integrated with AWS IAM
- Google Artifact Registry / GCR: GCP's container registry
- GitHub Container Registry (ghcr.io): Images stored alongside source code
In the next lesson, you'll learn why running containers in production requires something more than Docker — and how Kubernetes solves that problem.