Welcome to the Docker Workshop! In this hands-on session, you'll learn to containerize a full-stack Todo application, manage multi-container environments, and debug Docker deployments like a pro.
By the end of this workshop, you'll be able to:
- Build and optimize Docker images for frontend and backend applications
- Run single containers with proper configuration
- Orchestrate multi-container applications using Docker Compose
- Debug containers using Docker CLI commands
- Use LazyDocker for visual container management
- Understand Docker networking, volumes, and best practices
- Docker Desktop installed and running
- Basic knowledge of command line
- Text editor (VS Code recommended)
- Git (for cloning/pushing changes)
# Install LazyDocker (macOS)
brew install lazydocker
# Install LazyDocker (Linux)
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
# Install LazyDocker (Windows)
# Download from: https://github.com/jesseduffield/lazydocker/releasesdocker-workshop/
โโโ backend/ # Flask API with SQLAlchemy
โ โโโ app.py # Main Flask application
โ โโโ requirements.txt
โ โโโ Dockerfile # โ ๏ธ TO BE COMPLETED
โ โโโ Dockerfile.solution # ๐ก Solution reference
โโโ frontend/ # React Todo App
โ โโโ src/
โ โโโ package.json
โ โโโ Dockerfile # โ ๏ธ TO BE COMPLETED
โ โโโ Dockerfile.solution # ๐ก Solution reference
โโโ docker-compose.yml # โ ๏ธ TO BE COMPLETED
โโโ docker-compose.yml.solution # ๐ก Solution reference
โโโ README.md # This file
First, let's understand what we're containerizing:
Backend (Flask API):
- RESTful API for managing todos
- Supports both PostgreSQL and SQLite databases
- Health check endpoint
- CORS enabled for frontend communication
Frontend (React App):
- Modern React application
- Communicates with backend API
- Environment variable configuration for API URL
Your Task: Create a backend/Dockerfile to containerize the Flask application.
Requirements:
- Use Python 3.9 slim base image
- Set working directory to
/app - Copy
requirements.txtfirst (for better layer caching) - Install Python dependencies
- Copy application code
- Expose port 5000
- Run the application with
python app.py
Hints:
- Use
COPYto copy files into the container - Use
RUNto execute commands during build - Use
CMDto specify the default command - Use
EXPOSEto document the port (doesn't publish it) - Use
WORKDIRto set the working directory
Need help? Check backend/Dockerfile.solution for the complete solution.
Build and test the backend image:
# Navigate to backend directory
cd backend
# Build the image
docker build -t todo-backend .
# Run the container
docker run -p 5000:5000 todo-backend
# Test the API
curl http://localhost:5000/healthYour Task: Create a frontend/Dockerfile using a multi-stage build to optimize the React application.
Requirements:
-
Stage 1 (Builder): Use Node.js 18 Alpine image
- Set working directory to
/app - Copy
package*.jsonfiles first - Install dependencies with
npm install - Copy source code and build the app with
npm run build
- Set working directory to
-
Stage 2 (Production): Use nginx Alpine image
- Copy built app from builder stage (
/app/buildโ/usr/share/nginx/html) - Expose port 80
- nginx starts automatically
- Copy built app from builder stage (
Hints:
- Use
FROM node:18-alpine AS builderfor the first stage - Use
FROM nginx:alpinefor the second stage - Use
COPY --from=builderto copy between stages - The React build output goes to
/app/build - Nginx serves static files from
/usr/share/nginx/html
Need help? Check frontend/Dockerfile.solution for the complete solution.
Build and test the frontend:
# Navigate to frontend directory
cd frontend
# Build the image
docker build -t todo-frontend .
# Run the container
docker run -p 3000:80 todo-frontend
# Open http://localhost:3000 in your browserYour Task: Create a docker-compose.yml file in the root directory to orchestrate all services.
Requirements:
PostgreSQL Service:
- Use
postgres:15image - Set environment variables:
POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD - Create a named volume for data persistence
- Expose port 5432
- Add health check using
pg_isready
Backend Service:
- Build from
./backenddirectory - Set
DATABASE_URLenvironment variable to connect to postgres - Expose port 5000
- Add dependency on postgres with health condition
- Mount
./backend:/appfor development - Add health check using the
/healthendpoint
Frontend Service:
- Build from
./frontenddirectory - Set
REACT_APP_API_URLenvironment variable - Expose port 3000 (map to container port 80)
- Add dependency on backend service
Hints:
- Use
version: '3.8' - Use
build:to build from local Dockerfiles - Use
depends_on:withcondition: service_healthyfor proper startup order - Use
volumes:for both bind mounts and named volumes - Database URL format:
postgresql://user:password@host:port/database - Health check test format:
["CMD-SHELL", "command"]
Need help? Check docker-compose.yml.solution for the complete solution.
Run the full stack:
# Start all services
docker-compose up --build
# Run in detached mode
docker-compose up -d --build
# View logs
docker-compose logs -f
# Stop all services
docker-compose down
# Stop and remove volumes
docker-compose down -v# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# View container logs
docker logs <container-name>
docker logs -f <container-name> # Follow logs
# Execute commands in running container
docker exec -it <container-name> /bin/bash
docker exec -it <container-name> /bin/sh # For alpine images
# Inspect container details
docker inspect <container-name>
# View container resource usage
docker stats
docker stats <container-name># List images
docker images
# Remove unused images
docker image prune
# Remove specific image
docker rmi <image-name>
# View image layers
docker history <image-name>
# Build with no cache
docker build --no-cache -t <image-name> .# List networks
docker network ls
# Inspect network
docker network inspect <network-name>
# List volumes
docker volume ls
# Inspect volume
docker volume inspect <volume-name>
# Remove unused volumes
docker volume prune# View service logs
docker-compose logs <service-name>
# Scale services
docker-compose up -d --scale backend=3
# Restart specific service
docker-compose restart <service-name>
# View service status
docker-compose ps
# Execute command in service
docker-compose exec <service-name> <command>LazyDocker provides a terminal UI for managing Docker:
# Start LazyDocker
lazydockerKey LazyDocker Features:
- Tab Navigation: Switch between containers, images, volumes, networks
- Real-time Logs: View and follow container logs
- Resource Monitoring: CPU, memory, and network usage
- Quick Actions: Start, stop, restart containers
- Exec into Containers: Easy shell access
- Prune Operations: Clean up unused resources
LazyDocker Shortcuts:
x: Execute command in containerl: View logss: Stop containerr: Restart containerd: Delete container/imagep: Prune unused resources
Your Task: Create a development-friendly backend setup.
Requirements:
- Create
backend/Dockerfile.devfor development - Add development dependencies (like
flask-debugtoolbar) - Set environment variables for debug mode
- Configure for live code reloading with bind mounts
Key Development Features:
- Set
FLASK_ENV=development - Set
FLASK_DEBUG=1 - Install additional dev dependencies
- Use bind mounts in docker-compose for instant code changes
Hints:
- Base it on your working
Dockerfilebut add dev-specific changes - Use
ENVto set environment variables - Add
RUN pip install flask-debugtoolbarfor enhanced debugging - Create a
docker-compose.dev.ymlthat uses bind mounts
Your Task: Create a development setup with hot reloading.
Requirements:
- Create
frontend/Dockerfile.devfor development - Configure for development mode (no build step, no nginx)
- Add bind mounts for source code changes
- Use the React development server
Key Development Features:
- Use
npm startinstead of building and serving with nginx - Expose port 3000 (React dev server default)
- Mount source code for instant hot reloading
- Skip the multi-stage build (development is single-stage)
Hints:
- Use Node.js base image (no nginx needed)
- Use
CMD ["npm", "start"]for development server - Create a
docker-compose.dev.ymlthat mounts./frontend:/app - Make sure to mount
node_modulesas an anonymous volume
Your Task: Optimize your Docker images for production.
Requirements:
- Use multi-stage builds to minimize image size
- Add security improvements (non-root user)
- Configure proper health checks
- Optimize for production performance
Backend Production Features:
- Multi-stage build: builder stage + runtime stage
- Non-root user for security
- Minimal dependencies in final image
- Built-in health checks
- Optimized layer caching
Frontend Production Features:
- Already uses multi-stage build (builder + nginx)
- Minimal nginx-alpine runtime
- Optimized static file serving
- Security headers (via nginx config)
Hints:
- Use
FROM python:3.9-slim AS builderfor dependencies - Create system user:
RUN groupadd -r appuser && useradd -r -g appuser appuser - Use
USER appuserto switch to non-root - Add
HEALTHCHECKinstruction for monitoring - Use
--no-cache-dirwith pip to reduce image size
Your Task: Create an advanced compose setup for production.
Requirements:
- Environment-specific configurations
- Secrets management for sensitive data
- Resource limits and constraints
- Service scaling and load balancing
Advanced Features to Implement:
- Secrets Management: Use Docker secrets for database passwords
- Resource Limits: Set CPU and memory limits for services
- Service Scaling: Configure backend replicas
- Load Balancing: Add nginx as reverse proxy
- Production Dockerfiles: Use optimized production images
Key Components:
docker-compose.prod.ymlfor production configurationsecrets/directory for sensitive datanginx.conffor reverse proxy configuration- Resource constraints for each service
- Health checks and restart policies
Hints:
- Use
secrets:top-level key andPOSTGRES_PASSWORD_FILE - Set
deploy.resources.limitsfor CPU/memory constraints - Use
deploy.replicasfor scaling services - Configure nginx to proxy to backend services
- Use
dockerfile: Dockerfile.prodfor optimized builds
Issue: Container won't start
# Check logs
docker logs <container-name>
# Check container configuration
docker inspect <container-name>
# Try running interactively
docker run -it <image-name> /bin/bashIssue: Cannot connect to database
# Check if database is running
docker-compose ps
# Check database logs
docker-compose logs postgres
# Test database connection
docker-compose exec postgres psql -U todouser -d todosIssue: Frontend can't reach backend
# Check network connectivity
docker network ls
docker network inspect <network-name>
# Test API endpoint
docker-compose exec frontend curl http://backend:5000/healthIssue: Changes not reflected
# Rebuild images
docker-compose build --no-cache
# Remove containers and restart
docker-compose down
docker-compose up --build# Analyze image size
docker images
# Check container resource usage
docker stats
# Optimize builds with buildkit
DOCKER_BUILDKIT=1 docker build .
# Use .dockerignore to exclude unnecessary files
echo "node_modules" >> .dockerignore
echo "*.log" >> .dockerignore-
Use specific base image tags
FROM node:18-alpine # Not node:latest -
Leverage layer caching
# Copy package.json first COPY package*.json ./ RUN npm install # Then copy source code COPY . .
-
Use multi-stage builds
FROM node:18-alpine AS builder # Build stage FROM nginx:alpine AS production # Production stage
-
Run as non-root user
RUN addgroup -g 1001 -S nodejs RUN adduser -S nextjs -u 1001 USER nextjs
- Use health checks
- Configure resource limits
- Use secrets for sensitive data
- Separate development and production configs
# Create custom network
docker network create todo-network
# Run containers in custom network
docker run --network todo-network <image>
# Connect container to network
docker network connect todo-network <container># Create named volume
docker volume create todo-data
# Use volume in container
docker run -v todo-data:/app/data <image>
# Backup volume
docker run --rm -v todo-data:/backup busybox tar czf /backup/backup.tar.gz /backup# Monitor container stats
docker stats --no-stream
# Check container health
docker inspect --format='{{.State.Health.Status}}' <container>
# View container processes
docker exec <container> ps aux- Built backend Docker image
- Built frontend Docker image
- Created working docker-compose.yml
- Tested full-stack application
- Used Docker debugging commands
- Explored LazyDocker interface
- Implemented health checks
- Created development environment
- Optimized production images
- Troubleshot common issues
- Docker Official Documentation
- Docker Compose Reference
- LazyDocker GitHub
- Docker Best Practices
- Dockerfile Reference
You've completed the Docker workshop! You now have hands-on experience with:
- Building optimized Docker images
- Managing multi-container applications
- Debugging containerized applications
- Using modern Docker tooling
Keep practicing and exploring more advanced Docker features like Docker Swarm, Kubernetes integration, and CI/CD pipelines!
Happy Dockerizing! ๐ณ