########################## 8.4 Creating Custom Images ########################## .. image:: ../diagrams/docker_arch.png :alt: A diagram showing the Dockerfile build process and image layers :width: 800 px **From Consumer to Creator** So far, you've been running containers from images created by others. Now comes the transformative moment: learning to build your own custom images. This is where containers become truly powerful - you can package your applications exactly how you want them, ensuring consistency across all environments. Think of this as learning to cook rather than just ordering takeout. Once you master Dockerfiles, you control every ingredient in your application's environment. =================== Learning Objectives =================== By the end of this section, you will: • **Write** production-ready Dockerfiles following security and efficiency best practices • **Understand** Docker's layered filesystem and how to optimize build performance • **Build** images for different types of applications (web apps, APIs, databases) • **Implement** multi-stage builds to create smaller, more secure images • **Debug** build failures and optimize Docker image sizes • **Apply** security hardening techniques to your container images **Prerequisites:** Understanding of basic container operations and command-line familiarity ======================= Dockerfile Fundamentals ======================= **What is a Dockerfile?** A Dockerfile is a text file containing a series of instructions that Docker uses to automatically build an image. It's like a recipe that specifies: - The base operating system or runtime - Application dependencies to install - Files to copy into the image - Environment variables to set - Commands to run when the container starts **The Anatomy of a Dockerfile:** .. code-block:: dockerfile # Syntax: FROM : FROM python:3.11-slim # Syntax: LABEL = LABEL maintainer="yourname@company.com" LABEL version="1.0" # Syntax: WORKDIR WORKDIR /app # Syntax: COPY COPY requirements.txt . # Syntax: RUN RUN pip install --no-cache-dir -r requirements.txt # Syntax: COPY COPY . . # Syntax: EXPOSE EXPOSE 8000 # Syntax: CMD ["executable", "param1", "param2"] CMD ["python", "app.py"] ======================= Your First Custom Image ======================= **Building a Python Web Application** Let's create a complete web application image step by step: **Step 1: Create the Application** .. code-block:: python # app.py - Simple Flask web application from flask import Flask, jsonify import os import platform app = Flask(__name__) @app.route('/') def home(): return jsonify({ 'message': 'Hello from containerized Flask!', 'hostname': platform.node(), 'python_version': platform.python_version(), 'environment': os.environ.get('APP_ENV', 'development') }) @app.route('/health') def health(): return jsonify({'status': 'healthy'}), 200 if __name__ == '__main__': app.run(host='0.0.0.0', port=8000, debug=False) **Step 2: Define Dependencies** .. code-block:: text # requirements.txt Flask==2.3.3 gunicorn==21.2.0 **Step 3: Write the Dockerfile** .. code-block:: dockerfile # Use Python 3.11 on slim Debian base (smaller than full Python image) FROM python:3.11-slim # Add metadata to the image LABEL maintainer="devops-team@company.com" LABEL description="Flask web application demo" LABEL version="1.0.0" # Create a non-root user for security RUN groupadd -r appuser && useradd -r -g appuser appuser # Set working directory WORKDIR /app # Copy requirements first (for better caching) COPY requirements.txt . # Install Python dependencies RUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir -r requirements.txt # Copy application code COPY app.py . # Change ownership to non-root user RUN chown -R appuser:appuser /app USER appuser # Expose port (documentation only, doesn't actually publish) EXPOSE 8000 # Health check to monitor container status HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 # Set environment variables ENV APP_ENV=production ENV FLASK_APP=app.py # Use exec form for proper signal handling CMD ["python", "app.py"] **Step 4: Build and Test** .. code-block:: bash # Build the image docker build -t my-flask-app:1.0 . # Run the container docker run -d -p 8000:8000 --name flask-demo my-flask-app:1.0 # Test the application curl http://localhost:8000 curl http://localhost:8000/health # Check container health docker ps docker logs flask-demo ================================= Dockerfile Instructions Deep Dive ================================= **Essential Instructions** **FROM - Choose Your Foundation** .. code-block:: dockerfile # Official language runtimes FROM python:3.11-slim # Python with minimal OS FROM node:18-alpine # Node.js on Alpine Linux (tiny) FROM openjdk:17-jre-slim # Java runtime only # Operating systems FROM ubuntu:22.04 # Full Ubuntu system FROM alpine:3.18 # Minimal Linux (5MB) FROM scratch # Empty image (for static binaries) # Application-specific bases FROM nginx:alpine # Web server ready FROM postgres:15 # Database ready **RUN - Execute Commands** .. code-block:: dockerfile # Single command RUN apt-get update # Multiple commands (creates multiple layers) RUN apt-get update RUN apt-get install -y curl RUN apt-get clean # Better: Chain commands (single layer) RUN apt-get update && \ apt-get install -y curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Use here documents for complex scripts RUN <