⏱ Duration: 5 Hours
📚 Learning Objectives
- Understand Docker images and layers
- Write effective Dockerfiles
- Master FROM, COPY, RUN, CMD, ENTRYPOINT instructions
- Build and tag custom images
- Understand image caching and optimization
📖 Core Concepts (2 Hours)
What is a Docker Image?
A Docker image is a read-only template containing instructions for creating a container. Images are built in layers, where each instruction adds a new layer.
Image Structure:
┌─────────────────────────────┐
│ Layer 4: CMD (entry point) │ ← Your app command
├─────────────────────────────┤
│ Layer 3: COPY app files │ ← Your application
├─────────────────────────────┤
│ Layer 2: RUN install deps │ ← Dependencies
├─────────────────────────────┤
│ Layer 1: Base OS (Ubuntu) │ ← FROM instruction
└─────────────────────────────┘
Each layer is cached - unchanged layers are reused!
Dockerfile Basics
A Dockerfile is a text file with instructions to build an image. Each instruction creates a layer.
# Simple Dockerfile structure
FROM base_image:tag # Starting point
WORKDIR /app # Set working directory
COPY source destination # Copy files
RUN command # Execute during build
ENV VAR=value # Set environment variable
EXPOSE port # Document port usage
CMD ["executable", "args"] # Default command at runtime
Essential Dockerfile Instructions
# FROM - Base image (always first)
FROM ubuntu:22.04
FROM python:3.11-slim
FROM node:18-alpine
# WORKDIR - Set working directory
WORKDIR /app
WORKDIR /home/user/project
# COPY - Copy files from host to image
COPY . . # Copy all to WORKDIR
COPY package.json . # Copy specific file
COPY src/ /app/src/ # Copy directory
# ADD - Like COPY but can extract archives & fetch URLs
ADD app.tar.gz /app/ # Auto-extracts
ADD https://example.com/file /app/
# RUN - Execute commands during build
RUN apt-get update && apt-get install -y curl
RUN pip install -r requirements.txt
RUN npm install
# ENV - Set environment variables
ENV NODE_ENV=production
ENV PATH="/app/bin:$PATH"
# EXPOSE - Document which ports are used
EXPOSE 80
EXPOSE 3000 8080
# CMD - Default command (can be overridden)
CMD ["python", "app.py"]
CMD ["npm", "start"]
# ENTRYPOINT - Fixed command (args appended)
ENTRYPOINT ["python"]
CMD ["app.py"] # docker run image other.py → python other.py
CMD vs ENTRYPOINT
# CMD - Default command, easily overridden
FROM python:3.11
CMD ["python", "app.py"]
# docker run image → python app.py
# docker run image bash → bash (CMD replaced)
# ENTRYPOINT - Fixed executable
FROM python:3.11
ENTRYPOINT ["python"]
CMD ["app.py"]
# docker run image → python app.py
# docker run image test.py → python test.py (args to ENTRYPOINT)
# Common pattern: ENTRYPOINT for executable, CMD for default args
ENTRYPOINT ["./script.sh"]
CMD ["--help"]
Building Images
# Build image from Dockerfile
docker build -t myapp .
docker build -t myapp:v1.0 .
docker build -t username/myapp:latest .
# Build with different Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
# Build with build arguments
docker build --build-arg VERSION=1.0 -t myapp .
# View build history
docker history myapp
# List images
docker images
docker images myapp
# Remove images
docker rmi myapp
docker rmi myapp:v1.0
docker image prune # Remove dangling images
Understanding Layers & Caching
# BAD - Cache invalidated on any code change
FROM python:3.11
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
# GOOD - Dependencies cached separately
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
# Order matters! Put frequently changing files last
🔬 Hands-on Lab (2.5 Hours)
Lab 1: Build a Python Web App Image
- Create a simple Flask application
- Write a Dockerfile
- Build and run the container
# Create project directory
mkdir flask-app && cd flask-app
# app.py
cat > app.py << 'EOF'
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from Docker!
'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
# requirements.txt
echo "flask==3.0.0" > requirements.txt
# Dockerfile
cat > Dockerfile << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
EOF
# Build and run
docker build -t flask-app .
docker run -d -p 5000:5000 --name myflask flask-app
curl http://localhost:5000
docker logs myflask
docker stop myflask && docker rm myflask
Lab 2: Build a Node.js App Image
- Create an Express.js application
- Optimize Dockerfile for caching
- Use build arguments
# Create project
mkdir node-app && cd node-app
# package.json
cat > package.json << 'EOF'
{
"name": "docker-node-app",
"version": "1.0.0",
"main": "server.js",
"scripts": { "start": "node server.js" },
"dependencies": { "express": "^4.18.2" }
}
EOF
# server.js
cat > server.js << 'EOF'
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello from Dockerized Node.js!', version: process.env.APP_VERSION || 'dev' });
});
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
EOF
# Dockerfile
cat > Dockerfile << 'EOF'
FROM node:18-alpine
ARG APP_VERSION=1.0.0
ENV APP_VERSION=$APP_VERSION
WORKDIR /app
# Copy package files first (better caching)
COPY package*.json ./
RUN npm install --production
COPY server.js .
EXPOSE 3000
CMD ["npm", "start"]
EOF
# Build with version
docker build --build-arg APP_VERSION=2.0.0 -t node-app:2.0 .
docker run -d -p 3000:3000 --name mynode node-app:2.0
curl http://localhost:3000
docker stop mynode && docker rm mynode
Lab 3: Inspect Image Layers
- View image history
- Understand layer sizes
- Compare different base images
# View image layers
docker history flask-app
docker history node-app:2.0
# Inspect image details
docker inspect flask-app
docker inspect --format='{{.Config.Env}}' flask-app
# Compare base image sizes
docker pull python:3.11
docker pull python:3.11-slim
docker pull python:3.11-alpine
docker images python
# See the size difference!
# python:3.11 ~1GB
# python:3.11-slim ~150MB
# python:3.11-alpine ~50MB
✅ Day 2 Checklist
- Understand Docker images and layers
- Can write Dockerfiles with FROM, COPY, RUN, CMD
- Know difference between CMD and ENTRYPOINT
- Can build and tag images
- Understand layer caching and optimization