Docker¶
Notes from AWS Apprenticeship — ILT with Kris, February 2026. Plus personal practice labs.
Ultra-Short Summary¶
Docker packages an application and all its dependencies into a portable unit called a container. The container runs the same everywhere — your laptop, a server, AWS. Docker Compose extends this to multi-container apps, letting you define an entire stack (app + database + cache) in one file and spin it all up with one command.
Why Docker Exists¶
The classic problem:
Before Docker: install dependencies manually on each server, manage version conflicts, environment differences between dev/prod.
With Docker: the container includes the OS layer, runtime, libraries, and app. Ship the container — everything is consistent.
Core Concepts¶
Image → read-only blueprint (like a class definition)
Container → running instance of an image (like an object)
Dockerfile → instructions for building an image
Registry → storage for images (Docker Hub, AWS ECR)
Volume → persistent storage that outlives the container
Network → how containers talk to each other
Dockerfile — How to Build an Image¶
FROM python:3.11-slim # base image
WORKDIR /app # set working directory inside container
COPY requirements.txt . # copy deps file first (layer caching)
RUN pip install -r requirements.txt
COPY . . # copy app code
EXPOSE 8000 # document the port (doesn't publish it)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Layer caching: Docker caches each RUN/COPY step. Put things that change least often (deps) before things that change often (app code) — this makes rebuilds fast.
Key Instructions¶
| Instruction | What It Does |
|---|---|
FROM |
Base image to build on |
WORKDIR |
Set working directory |
COPY |
Copy files from host into image |
RUN |
Execute a command during build |
ENV |
Set environment variables |
EXPOSE |
Document which port the app uses |
CMD |
Default command when container starts |
ENTRYPOINT |
Fixed command (CMD becomes its arguments) |
Docker Compose — Multi-Container Stacks¶
Docker Compose lets you define and run multi-container applications from a single docker-compose.yml file.
ILT Lab — Flask + Redis¶
What this does:
- Builds the Flask app from the local Dockerfile
- Pulls Redis from Docker Hub
- Creates a shared network so web can reach redis by hostname
- Starts redis before web (due to depends_on)
Personal Lab — Realistic Stack¶
services:
web:
image: nginx:alpine
depends_on: [database, queue]
networks: [appnet]
database:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
volumes:
- dbdata:/var/lib/postgresql/data
networks: [appnet]
cache:
image: valkey # Redis-compatible
networks: [appnet]
queue:
image: rabbitmq:3-alpine
networks: [appnet]
networks:
appnet: {}
volumes:
dbdata: {}
Architecture:
Essential Commands¶
# Build and start all services
docker compose up -d # -d = detached (background)
# See running containers
docker compose ps
docker ps
# View logs
docker compose logs -f web # follow logs for 'web' service
docker logs <container-id>
# Stop everything (keep volumes)
docker compose down
# Stop and delete volumes
docker compose down -v
# Rebuild after code change
docker compose up -d --build
# Shell into a running container
docker exec -it <container-name> bash
docker exec -it <container-name> sh # if no bash
# Inspect a container
docker inspect <container-id>
# List images
docker images
# Remove unused images/containers
docker system prune
Networking in Docker¶
By default, Docker Compose creates a shared network for all services in the file. Containers can reach each other by service name as the hostname.
# Flask app connecting to Redis — no IP needed
import redis
r = redis.Redis(host='redis', port=6379)
host='redis' works because Docker's internal DNS resolves the service name.
Volumes — Persistent Data¶
Without a volume, data inside a container is lost when it stops.
volumes:
- dbdata:/var/lib/postgresql/data # named volume — managed by Docker
- ./config:/app/config # bind mount — maps host directory
- Named volumes (
dbdata:) — Docker manages storage, survivesdown - Bind mounts (
./config:) — maps a host directory, good for development
Mental Model¶
Docker Compose = mini infrastructure-as-code for local/dev environments
Instead of:
docker run postgres ...
docker run redis ...
docker network create ...
docker run --network ... my-app
You write one file and run:
docker compose up
This is exactly the same pattern as Terraform, CDK, CloudFormation —
just for containers instead of cloud resources.
Docker → AWS Mapping¶
| Docker Concept | AWS Equivalent |
|---|---|
| Container image | ECR (Elastic Container Registry) |
| Running containers | ECS Tasks / Fargate |
| Docker Compose (local) | ECS Task Definition + Service |
| Kubernetes (docker desktop) | EKS (Managed Kubernetes) |
| Volume | EFS or EBS attached to ECS task |
| Docker network | VPC + Security Groups |
SAA Patterns¶
| Scenario | Answer |
|---|---|
| Run containers without managing servers | ECS Fargate |
| Need Kubernetes specifically | EKS |
| Store private Docker images | ECR |
| Simple single-container web app | App Runner (pulls from ECR/GitHub) |
| Shared storage across containers | EFS mount |
30-Second Takeaway¶
- Image = blueprint. Container = running instance.
- Dockerfile defines how to build the image.
- Docker Compose orchestrates multi-container apps with one file.
- Containers reach each other by service name on the shared Compose network.
- In AWS: containers → ECR for storage, ECS/Fargate for running them.
Self-Quiz¶
- What's the difference between a Docker image and a container?
- Why do you copy
requirements.txtbefore the rest of the app code in a Dockerfile? - How does Flask connect to Redis in a Compose stack without knowing Redis's IP?
- What's the difference between a named volume and a bind mount?
docker compose downvsdocker compose down -v— what's the difference?- What AWS service would you use to run a containerised Flask app without managing servers?
- Where do you store Docker images in AWS?
- Why is Docker Compose similar to Terraform or CDK?