Day 6: Working with Docker Images - pull, build, tag
Learning Objectives: By the end of this lesson, you will be able to:
- Pull images from registries with different strategies and options
- Build custom images using Dockerfiles with best practices
- Tag images effectively for version control and distribution
- Manage local image storage and understand image layers
- Push images to registries and share your custom containers
1. Welcome and Introduction (5 minutes)
Icebreaker Activity
Think about how you create a custom photo album: you might start with existing photos (pull), combine and edit them to create something new (build), label them with meaningful names (tag), and then share your album with friends (push). Docker image management works very similarly!
Lesson Overview
- Duration: 60-75 minutes
- Prerequisites: Docker basics, understanding of containers and Docker Hub
- Materials Needed: Docker installed, text editor, sample application code
2. Learning Objectives and Session Outline (5 minutes)
What We’ll Cover Today:
- Image Pulling Strategies - Efficient downloading and caching
- Dockerfile Fundamentals - Writing instructions to build custom images
- Building Images - Creating containers from source code
- Tagging Systems - Organizing and versioning your images
- Image Management - Storage, cleanup, and optimization
- Sharing Images - Pushing to registries for distribution
Success Criteria:
By the end of this session, you should be able to create, manage, and distribute custom Docker images following industry best practices.
3. Foundation Knowledge (15-20 minutes)
Understanding Docker Images
Docker images are read-only templates used to create containers. They consist of multiple layers, each representing a set of file changes. Understanding this layered architecture is crucial for efficient image management.
Real-World Analogy: Think of Docker images like a cake recipe and its layers. Each layer adds ingredients (files, configurations, software), and the final image is the complete recipe that can be used to bake identical cakes (containers).
Why This Matters: Efficient image management reduces storage costs, improves deployment speed, and enables better collaboration in development teams.
The Image Lifecycle
- Pull - Download base images or dependencies
- Build - Create custom images from Dockerfiles
- Tag - Label images for organization and versioning
- Push - Upload images to registries for sharing
- Deploy - Run containers from images
Layer Architecture Benefits
- Caching - Unchanged layers are reused, speeding up builds
- Storage Efficiency - Common layers are shared between images
- Network Optimization - Only changed layers need to be transferred
- Version Control - Each layer represents a specific change
Key Terminology
- Base Image: Starting point for building custom images
- Layer: Individual instruction result in a Dockerfile
- Image ID: Unique identifier for each image
- Tag: Human-readable label for image versions
- Registry: Storage and distribution system for images
- Repository: Collection of related images with different tags
4. Detailed Explanation and Examples (25-30 minutes)
Image Pulling Strategies
Pulling images efficiently involves understanding tags, platform considerations, and caching mechanisms.
Pull Command Variations
- Latest Version:
docker pull nginx(defaults tolatesttag) - Specific Version:
docker pull nginx:1.23.1 - Platform Specific:
docker pull --platform linux/amd64 nginx - All Tags:
docker pull -a nginx(downloads all available tags)
Strategic Pulling Considerations
- Base Image Selection: Choose appropriate base images for your needs
- Version Pinning: Use specific versions for reproducible builds
- Platform Compatibility: Ensure images match your deployment architecture
- Cache Utilization: Leverage Docker’s layer caching for faster subsequent pulls
Dockerfile Fundamentals
Dockerfiles are text files containing instructions to build images. Each instruction creates a new layer in the image.
Essential Instructions
- FROM: Specifies the base image
- RUN: Executes commands during build
- COPY/ADD: Adds files from build context
- WORKDIR: Sets working directory
- EXPOSE: Documents which ports the container uses
- CMD/ENTRYPOINT: Defines container startup behavior
Best Practices for Dockerfiles
- Start with minimal base images
- Combine multiple RUN commands to reduce layers
- Order instructions from least to most likely to change
- Use .dockerignore to exclude unnecessary files
- Set appropriate user permissions
Tagging Strategy and Version Management
Effective tagging enables organized image management and deployment strategies.
Common Tagging Patterns
- Semantic Versioning:
myapp:1.2.3,myapp:1.2,myapp:1 - Environment Tags:
myapp:dev,myapp:staging,myapp:prod - Build Information:
myapp:commit-abc123,myapp:build-456 - Date-based:
myapp:2025-06-27,myapp:20250627
Professional Tagging Strategies
- Always tag images with specific versions
- Use
latesttag sparingly and only for stable releases - Implement consistent naming conventions across projects
- Include metadata in tags when helpful
5. Live Coding Demonstration (20-25 minutes)
Example 1: Basic Image Operations
# Pull different versions of an image
docker pull alpine:3.18
docker pull alpine:3.17
docker pull alpine:latest
# List local images
docker images alpine
# Inspect image details
docker inspect alpine:3.18
# Check image layers
docker history alpine:3.18
Explanation:
- Different Alpine versions show how tags work
docker imagesdisplays local image inventorydocker inspectreveals detailed image metadatadocker historyshows the layer composition
Example 2: Building a Simple Custom Image
First, let’s create a simple web application:
# Create project directory
mkdir my-web-app
cd my-web-app
# Create a simple HTML file
cat > index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>My Docker App</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.highlight { color: #0066cc; }
</style>
</head>
<body>
<div class="container">
<h1>Welcome to My Docker App!</h1>
<p>This is a <span class="highlight">custom image</span> built with Docker.</p>
<p>Build time: <span id="datetime"></span></p>
</div>
<script>
document.getElementById('datetime').textContent = new Date().toLocaleString();
</script>
</body>
</html>
EOF
Now create a Dockerfile:
# Dockerfile
FROM nginx:alpine
# Copy our custom HTML file
COPY index.html /usr/share/nginx/html/index.html
# Expose port 80
EXPOSE 80
# The base nginx image already has a CMD, so we don't need to specify one
Build and test the image:
# Build the image with a tag
docker build -t my-web-app:1.0 .
# Run a container from our custom image
docker run -d -p 8080:80 --name my-app my-web-app:1.0
# Test the application
curl http://localhost:8080
# Clean up
docker stop my-app
docker rm my-app
Key Points:
FROMspecifies the base image (nginx:alpine)COPYadds our custom HTML file-tflag tags the image during build- The build context (
.) contains files needed for the build
Example 3: Advanced Dockerfile with Multiple Stages
# Multi-stage Dockerfile example
FROM node:18-alpine AS builder
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM nginx:alpine AS production
# Copy built application from builder stage
COPY --from=builder /app/dist /usr/share/nginx/html
# Add custom nginx configuration if needed
# COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Example 4: Comprehensive Tagging Examples
# Build with multiple tags
docker build -t my-web-app:1.0 -t my-web-app:latest .
# Tag an existing image with new tags
docker tag my-web-app:1.0 my-web-app:stable
docker tag my-web-app:1.0 my-web-app:prod-ready
# Create environment-specific tags
docker tag my-web-app:1.0 my-web-app:dev
docker tag my-web-app:1.0 my-web-app:staging
# List all tags for the image
docker images my-web-app
# Remove specific tags
docker rmi my-web-app:dev
docker rmi my-web-app:staging
Debugging Demonstration
# Common build errors and solutions
# Error: Build context too large
# Solution: Use .dockerignore file
cat > .dockerignore << 'EOF'
node_modules
.git
*.log
.env
EOF
# Error: COPY failed - file not found
# Solution: Ensure files exist in build context
ls -la # Verify files are present
# Error: Layer caching issues
# Solution: Use --no-cache flag
docker build --no-cache -t my-web-app:1.1 .
# Debug: Inspect failed builds
docker build -t debug-build . || docker run --rm -it $(docker images -q | head -1) sh
6. Hands-On Practice (30-40 minutes)
Exercise 1: Custom Python Application Image (15 minutes)
Objective: Build a custom image for a Python web application
Instructions:
- Create a simple Python Flask application
- Write a Dockerfile for the application
- Build the image with appropriate tags
- Test the containerized application
Starter Code:
# app.py
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return f"""
<h1>Hello from Docker!</h1>
<p>Environment: {os.environ.get('ENV', 'development')}</p>
<p>Version: {os.environ.get('VERSION', '1.0.0')}</p>
"""
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Solution:
# Create project structure
mkdir python-docker-app
cd python-docker-app
# Create app.py (use code above)
cat > app.py << 'EOF'
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return f"""
<h1>Hello from Docker!</h1>
<p>Environment: {os.environ.get('ENV', 'development')}</p>
<p>Version: {os.environ.get('VERSION', '1.0.0')}</p>
"""
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
# Create requirements.txt
echo "Flask==2.3.2" > requirements.txt
# Create 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
ENV ENV=production
ENV VERSION=1.0.0
CMD ["python", "app.py"]
EOF
# Build with multiple tags
docker build -t python-app:1.0.0 -t python-app:latest .
# Test the application
docker run -d -p 5000:5000 --name python-test python-app:1.0.0
# Verify it works
curl http://localhost:5000
# Clean up
docker stop python-test
docker rm python-test
Exercise 2: Image Optimization Challenge (15 minutes)
Objective: Optimize a Docker image for size and build speed
Challenge: Take the Python application from Exercise 1 and optimize it using:
- Multi-stage builds
- Alpine base images
- Efficient layer ordering
- Build cache optimization
Requirements:
- Reduce final image size by at least 50%
- Implement build caching for dependencies
- Add health check functionality
Solution:
# Optimized Dockerfile
FROM python:3.11-alpine AS base
# Install system dependencies in one layer
RUN apk add --no-cache gcc musl-dev
FROM base AS dependencies
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.11-alpine AS production
# Copy only the installed packages
COPY --from=dependencies /root/.local /root/.local
WORKDIR /app
COPY app.py .
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/ || exit 1
EXPOSE 5000
ENV ENV=production
ENV VERSION=1.0.0
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
# Build optimized version
docker build -f Dockerfile.optimized -t python-app:1.0.0-optimized .
# Compare image sizes
docker images python-app
# Test optimized image
docker run -d -p 5001:5000 --name python-optimized python-app:1.0.0-optimized
# Verify health check
docker ps # Should show health status
# Clean up
docker stop python-optimized
docker rm python-optimized
Exercise 3: Image Registry Simulation (10 minutes)
Objective: Practice tagging for different environments and sharing strategies
Challenge: Create a tagging strategy for a microservice that will be deployed across multiple environments.
Requirements:
- Create tags for dev, staging, and production environments
- Implement version-based tagging
- Simulate pushing to different registry namespaces
Solution:
# Build base image
docker build -t microservice:base .
# Create environment-specific tags
docker tag microservice:base microservice:dev-latest
docker tag microservice:base microservice:staging-1.0.0
docker tag microservice:base microservice:prod-1.0.0
# Create registry-namespace tags (simulate different registries)
docker tag microservice:base localhost:5000/company/microservice:1.0.0
docker tag microservice:base registry.company.com/team/microservice:1.0.0
# Create feature branch tag
docker tag microservice:base microservice:feature-user-auth
# List all tags
docker images microservice
# Simulate registry operations (would normally push to actual registry)
echo "Would push to registries:"
echo "docker push localhost:5000/company/microservice:1.0.0"
echo "docker push registry.company.com/team/microservice:1.0.0"
# Clean up tags
docker rmi microservice:dev-latest
docker rmi microservice:staging-1.0.0
docker rmi microservice:feature-user-auth
7. Common Pitfalls and Troubleshooting (10 minutes)
Frequent Mistakes
-
Problem: Large image sizes due to inefficient Dockerfiles Cause: Including unnecessary files, not using multi-stage builds, or inefficient layer ordering Solution: Use .dockerignore, multi-stage builds, and combine RUN commands
-
Problem: Build cache not working effectively Cause: Copying files before installing dependencies or poor instruction ordering Solution: Order Dockerfile instructions from least to most likely to change
-
Problem: Tag confusion and image proliferation Cause: Inconsistent tagging strategy and not cleaning up old images Solution: Implement consistent tagging conventions and regular cleanup
Debugging Tips
- Use
docker build --progress=plainfor detailed build output - Inspect intermediate layers with
docker run --rm -it <layer-id> sh - Use
docker system dfto monitor disk usage - Leverage BuildKit for advanced build features and better caching
- Use
docker scout(if available) for vulnerability scanning
8. Best Practices and Professional Tips (5 minutes)
Industry Standards
- Dockerfile Organization: Keep Dockerfiles in version control with your application code
- Image Naming: Use consistent naming conventions:
registry/namespace/repository:tag - Security: Run containers as non-root users when possible
- Size Optimization: Use multi-stage builds and minimal base images
Production Guidelines
- Pin base image versions for reproducibility
- Implement automated image scanning in CI/CD pipelines
- Use semantic versioning for application images
- Maintain image documentation and changelogs
- Implement image retention policies to manage storage costs
Next Steps in Your Learning Journey
- Tomorrow: Create your first complete containerized application
- Practice: Build images for different types of applications (web, API, database)
- Advanced: Explore BuildKit features, image signing, and security scanning
9. Summary and Review (5 minutes)
Key Takeaways
- Image Management Lifecycle: Pull, build, tag, and push form the core of image management
- Dockerfile Best Practices: Efficient layer ordering and multi-stage builds optimize images
- Tagging Strategy: Consistent tagging enables organized deployment and version control
Quick Review Questions
- Why should you order Dockerfile instructions from least to most likely to change?
- What’s the difference between COPY and ADD in Dockerfiles?
- How do multi-stage builds help optimize images?
Answers:
- To maximize Docker’s layer caching, reducing build times when only application code changes
- COPY simply copies files; ADD has additional features like URL downloads and archive extraction
- Multi-stage builds allow you to use different base images for building and runtime, keeping only necessary files in the final image
For Instructors
Teaching Tips
- Demonstrate the visual difference in image sizes before and after optimization
- Show real build times to emphasize the importance of caching
- Use
docker historyto illustrate layer composition and size impact - Encourage students to experiment with different base images
Assessment Ideas
- Image optimization challenge: Students optimize a provided inefficient Dockerfile
- Tagging strategy presentation: Students design and present a tagging strategy for a real-world scenario
- Build troubleshooting: Provide broken Dockerfiles for students to fix
Adaptation Notes
- For beginners: Focus on basic build and tag operations, save multi-stage builds for later
- For advanced students: Include BuildKit features, image signing, and advanced optimization techniques
- Online delivery: Use screen sharing to show build progress and terminal output
- Time management: Exercises can be extended or made homework based on available time