Skip to content

Latest commit

 

History

History
317 lines (240 loc) · 10.5 KB

008-docker-compose.md

File metadata and controls

317 lines (240 loc) · 10.5 KB

Chapter 8: Docker Compose

Docker Compose is a powerful tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services, networks, and volumes. Then, with a single command, you create and start all the services from your configuration.

Note: Docker Compose is now integrated into Docker CLI. The new command is docker compose instead of docker-compose. We'll use the new command throughout this chapter.

Key Benefits of Docker Compose

  1. Simplicity: Define your entire application stack in a single file.
  2. Reproducibility: Easily share and version control your application configuration.
  3. Scalability: Simple commands to scale services up or down.
  4. Environment Consistency: Ensure development, staging, and production environments are identical.
  5. Workflow Improvement: Compose can be used throughout the development cycle for testing, staging, and production.

The docker-compose.yml File

The docker-compose.yml file is the core of Docker Compose. It defines all the components and configurations of your application. Here's a basic example:

version: '3.8'
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
    environment:
      FLASK_ENV: development
  redis:
    image: "redis:alpine"

Let's break down this example:

  • version: Specifies the Compose file format version.
  • services: Defines the containers that make up your app.
  • web: A service based on an image built from the Dockerfile in the current directory.
  • redis: A service using the public Redis image.

Key Concepts in Docker Compose

  1. Services: Containers that make up your application.
  2. Networks: How your services communicate with each other.
  3. Volumes: Where your services store and access data.

Basic Docker Compose Commands

  • docker compose up: Create and start containers

    docker compose up -d  # Run in detached mode
  • docker compose down: Stop and remove containers, networks, images, and volumes

    docker compose down --volumes  # Also remove volumes
  • docker compose ps: List containers

  • docker compose logs: View output from containers

    docker compose logs -f web  # Follow logs for the web service

Advanced Docker Compose Features

1. Environment Variables

You can use .env files or set them directly in the compose file:

version: '3.8'
services:
  web:
    image: "webapp:${TAG}"
    environment:
      - DEBUG=1

2. Extending Services

Use extends to share common configurations:

version: '3.8'
services:
  web:
    extends:
      file: common-services.yml
      service: webapp

3. Healthchecks

Ensure services are ready before starting dependent services:

version: '3.8'
services:
  web:
    image: "webapp:latest"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 1m30s
      timeout: 10s
      retries: 3
      start_period: 40s

Practical Examples

Example 1: WordPress with MySQL

version: '3.8'
services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress

  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress

volumes:
  db_data: {}

Let's break this down in detail:

  1. Version: version: '3.8' specifies the version of the Compose file format. Version 3.8 is compatible with Docker Engine 19.03.0+.

  2. Services: We define two services: db and wordpress.

    a. db service:

    • image: mysql:5.7: Uses the official MySQL 5.7 image.
    • volumes: Creates a named volume db_data and mounts it to /var/lib/mysql in the container. This ensures that the database data persists even if the container is removed.
    • restart: always: Ensures that the container always restarts if it stops.
    • environment: Sets up the MySQL environment variables:
      • MYSQL_ROOT_PASSWORD: Sets the root password for MySQL.
      • MYSQL_DATABASE: Creates a database named "wordpress".
      • MYSQL_USER and MYSQL_PASSWORD: Creates a new user with the specified password.

    b. wordpress service:

    • depends_on: Ensures that the db service is started before the wordpress service.
    • image: wordpress:latest: Uses the latest official WordPress image.
    • ports: Maps port 8000 on the host to port 80 in the container, where WordPress runs.
    • restart: always: Ensures the container always restarts if it stops.
    • environment: Sets up WordPress environment variables:
      • WORDPRESS_DB_HOST: Specifies the database host. Note the use of db:3306, where db is the service name of our MySQL container.
      • WORDPRESS_DB_USER, WORDPRESS_DB_PASSWORD, WORDPRESS_DB_NAME: These match the MySQL settings we defined in the db service.
  3. Volumes: db_data: {}: This creates a named volume that Docker manages. It's used to persist the MySQL data.

To run this setup:

  1. Save the above YAML in a file named docker-compose.yml.
  2. In the same directory, run docker compose up -d.
  3. Once the containers are running, you can access WordPress by navigating to http://localhost:8000 in your web browser.

This setup provides a complete WordPress environment with a MySQL database, all configured and ready to use. The use of environment variables and volumes ensures that the setup is both flexible and persistent.

Example 2: Flask App with Redis and Nginx

version: '3.8'
services:
  flask:
    build: ./flask
    environment:
      - FLASK_ENV=development
    volumes:
      - ./flask:/code

  redis:
    image: "redis:alpine"

  nginx:
    image: "nginx:alpine"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "80:80"
    depends_on:
      - flask

networks:
  frontend:
  backend:

volumes:
  db-data:

Let's break this down:

  1. Version: As before, we're using version 3.8 of the Compose file format.

  2. Services: We define three services: flask, redis, and nginx.

    a. flask service:

    • build: ./flask: This tells Docker to build an image using the Dockerfile in the ./flask directory.
    • environment: Sets FLASK_ENV=development, which enables debug mode in Flask.
    • volumes: Mounts the local ./flask directory to /code in the container. This is useful for development as it allows you to make changes to your code without rebuilding the container.

    b. redis service:

    • image: "redis:alpine": Uses the official Redis image based on Alpine Linux, which is lightweight.

    c. nginx service:

    • image: "nginx:alpine": Uses the official Nginx image based on Alpine Linux.
    • volumes: Mounts a local nginx.conf file to /etc/nginx/nginx.conf in the container. The :ro flag makes it read-only.
    • ports: Maps port 80 on the host to port 80 in the container.
    • depends_on: Ensures that the flask service is started before Nginx.
  3. Networks: We define two networks: frontend and backend. This allows us to isolate our services. For example, we could put Nginx and Flask on the frontend network, and Flask and Redis on the backend network.

  4. Volumes: db-data: This creates a named volume. Although it's not used in this configuration, it's available if we need persistent storage, perhaps for a database service we might add later.

To use this setup:

  1. You need a Flask application in a directory named flask, with a Dockerfile to build it.
  2. You need an nginx.conf file in the same directory as your docker-compose.yml.
  3. Run docker compose up -d to start the services.

This configuration sets up a Flask application server, with Redis available for caching or as a message broker, and Nginx as a reverse proxy. The Flask code is mounted as a volume, allowing for easy development. Nginx handles incoming requests and forwards them to the Flask application.

The use of Alpine-based images for Redis and Nginx helps to keep the overall image size small, which is beneficial for deployment and scaling.

This setup is particularly useful for developing and testing a Flask application in an environment that closely mimics production, with a proper web server (Nginx) in front of the application server (Flask) and a caching/messaging system (Redis) available.

Best Practices for Docker Compose

  1. Use version control for your docker-compose.yml file.
  2. Keep development, staging, and production environments as similar as possible.
  3. Use build arguments and environment variables for flexibility.
  4. Leverage healthchecks to ensure service dependencies are met.
  5. Use .env files for environment-specific variables.
  6. Optimize your images to keep them small and efficient.
  7. Use docker-compose.override.yml for local development settings.

Scaling Services

Docker Compose can scale services with a single command:

docker compose up -d --scale web=3

This command would start 3 instances of the web service.

Networking in Docker Compose

By default, Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.

You can also specify custom networks:

version: '3.8'
services:
  web:
    networks:
      - frontend
      - backend
  db:
    networks:
      - backend

networks:
  frontend:
  backend:

Volumes in Docker Compose

Compose also lets you create named volumes that can be reused across multiple services:

version: '3.8'
services:
  db:
    image: postgres
    volumes:
      - data:/var/lib/postgresql/data

volumes:
  data:

Conclusion

Docker Compose simplifies the process of managing multi-container applications, making it an essential tool for developers working with Docker. By mastering Docker Compose, you can streamline your development workflow, ensure consistency across different environments, and easily manage complex applications with multiple interconnected services.

Remember to always use the latest docker compose command instead of the older docker-compose, as it's now integrated directly into Docker CLI and offers improved functionality and performance.