Docker Compose: Simplifying Multi-Container Docker Applications

Docker Compose: Simplifying Multi-Container Docker Applications

In today's world of containerized applications, managing multiple containers efficiently is crucial. Docker Compose is a powerful tool that addresses this need by simplifying the deployment and management of multi-container Docker applications. In this article, we'll explore what Docker Compose is, how it works, and provide a step-by-step guide on deploying a Flask application using Docker Compose on an Amazon EC2 instance.

What is Docker Compose?

Docker Compose is a tool that allows developers to define and manage multi-container Docker applications using a single configuration file. It is especially useful when your application requires multiple services (e.g., a web server, a database, a message broker) that need to work together. Instead of managing each container separately, Docker Compose allows you to define all the services your application needs in a docker-compose.yml file, making it easier to build, run, and manage complex applications.

Key Features:

  • Multi-Container Management: Define and manage multiple containers as a single application.

  • Declarative Configuration: Use a YAML file to define the services, networks, and volumes your application needs.

  • Portability: The same configuration can be used across different environments, ensuring consistency.

  • Orchestration: Start, stop, and rebuild services with a single command.

How Does Docker Compose Work?

Docker Compose works by reading the docker-compose.yml file, which contains the configuration for your services. The file describes the services that make up your application, including their images, networks, volumes, and other configurations.

Here’s a breakdown of how Docker Compose works:

  1. Define Services: Each service in your application is defined in the docker-compose.yml file. A service is typically one container, such as a web server, database, or worker process.

  2. Configure Networks and Volumes: Docker Compose allows you to define custom networks and volumes for your services, ensuring they can communicate and share data effectively.

  3. Orchestrate the Application: With a single command, Docker Compose can build, start, or stop all the services defined in the configuration file. It can also handle service dependencies, ensuring that services start in the correct order.

  4. Scaling: Docker Compose allows you to scale your services up or down with a single command, making it easy to manage applications that need to handle varying loads.


Step-by-Step Guide: Deploying a Flask Application Using Docker Compose on EC2

Now that we understand what Docker Compose is and how it works, let’s dive into a practical example: deploying a Flask application using Docker Compose on an Amazon EC2 instance.

Step 1: Launch an EC2 Instance and Connect

We will set up an AWS EC2 instance where the Docker container will be running.


Step 2: Install Docker and Docker Compose

  1. Install Docker:

    Follow these instructions to install Docker on your operating system ->

    Check Here

  2. Install Docker Compose:

     sudo curl -L "https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep -Po '"tag_name": "\K.*\d')/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
     sudo chmod +x /usr/local/bin/docker-compose
    

    Breaking Down the bash script:

    Line 1:

     sudo curl -L "https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep -Po '"tag_name": "\K.*\d')/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    
    • sudo: runs the command with superuser privileges.

    • curl: downloads a file from the specified URL.

    • -L: follows redirects (in case the URL redirects to another location).

    • The URL is constructed dynamically using several commands:

      • curl -s https://api.github.com/repos/docker/compose/releases/latest: fetches the latest release information from the Docker Compose GitHub repository. The -s flag silences the output.

      • grep -Po '"tag_name": "\K.*\d': extracts the latest version number (e.g., "v2.3.4") from the JSON response using a Perl-compatible regular expression. The \K syntax discards the matched text before the \K, and .*\d matches any characters followed by a digit.

      • The resulting version number is used to construct the download URL.

      • docker-compose-$(uname -s)-$(uname -m): appends the platform-specific suffix to the download URL. uname -s returns the operating system name (e.g., "Linux"), and uname -m returns the machine architecture (e.g., "x86_64").

    • -o /usr/local/bin/docker-compose: saves the downloaded file to /usr/local/bin/docker-compose.

Line 2:

    sudo chmod +x /usr/local/bin/docker-compose
  • sudo: runs the command with superuser privileges.

  • chmod: changes the file permissions.

  • +x: adds execute permissions to the file.

  • /usr/local/bin/docker-compose: specifies the file to modify.

In summary, this script downloads the latest version of Docker Compose from GitHub, saves it to /usr/local/bin/docker-compose, and makes the file executable.


Step 3: Prepare Your Flask Application

Already prepared "A Simple Weather Application" Flask app - Check here for detailed blog.

Check the GitHub Repository

We will deploy this application using docker-compose.


Step 4: Create a Dockerfile

Add a Dockerfile to define how the Flask app should be containerized:

# Use an official Python image as a base
FROM python:3.9-slim

# Set the working directory in the container
WORKDIR /app

# Copy the requirements file
COPY requirements.txt .

# Install the dependencies
RUN pip install -r requirements.txt

# Copy the application code
COPY . .

# Load environment variables from .env file
RUN pip install python-dotenv

# Expose the port the app will run on
EXPOSE 5000

# Run the command to start the development server when the container launches
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]

This Dockerfile builds a Python 3.9 image for a Flask app:

  1. Uses python:3.9-slim as the base image.

  2. Sets the working directory to /app.

  3. Copies the requirements.txt file from the current directory into the container's working directory (/app).

  4. Installs dependencies from requirements.txt.

  5. Copies the app code into the container.

  6. Installs python-dotenv to load environment variables.

  7. Exposes port 5000 for the app.

  8. Runs the Flask development server with flask run when the container launches.


Step 5: Create a Docker Compose File

Add a docker-compose.yml file:

services:
  web:
    build: .
    ports:
      - "80:5000"
    environment:
      - API_KEY=${API_KEY}

This Docker Compose file defines and runs multi-container Docker applications. Here's a breakdown:

  • services: Top-level key defining a list of services.

    • web: Name of the web application service.

      • build:

        • Context: . (current directory).

        • Uses the Dockerfile in the current directory.

      • ports:

        • "80:5000": Maps port 80 on the host to port 5000 in the container.

        • Requests to http://localhost:80 are forwarded to port 5000 in the container.

      • environment:

        • API_KEY=${API_KEY}: Sets the API_KEY environment variable inside the container.

        • Uses the value of API_KEY from the host machine.

Summary:

  • Builds a Docker image using the Dockerfile in the current directory.

  • Maps port 80 on the host to port 5000 in the container.

  • Sets the API_KEY environment variable inside the container using the host's API_KEY value.


Step 6: Deploy the Flask Application Using Docker Compose

  1. Transfer Files to EC2:

    • Use scp to transfer your files to the EC2 instance:

        scp -i "your-key-file.pem" -r /path/to/your/flask-app ec2-user@your-ec2-public-ip:/home/ec2-user/
      
    • You can use git to clone your repository:

        git clone https://github.com/your-username/your-repo.git
      
    • You can also use WinSCP to upload your project files:

  2. Navigate to the Project Directory:

     cd /home/ec2-user/your-flask-app-name
    
  3. Build and Run the Application:

     docker-compose up -d
    


Step 7: Access Your Flask Application

  • Open your browser and navigate to http://your-ec2-public-ip.


Common Web Services Defined in Docker Compose: Examples and Configurations

In Docker Compose, you can define various types of web services, each tailored to specific needs. Here are some common types and examples of web services you might define:

Web Application Servers:

These services run your web application code. Examples include:

  • Node.js/Express Server:

      version: '3'
      services:
        web:
          image: node:14
          working_dir: /app
          volumes:
            - .:/app
          command: node server.js
          ports:
            - "3000:3000"
    
  • Python/Flask Application:

      version: '3'
      services:
        web:
          image: python:3.8
          working_dir: /app
          volumes:
            - .:/app
          command: flask run --host=0.0.0.0
          ports:
            - "5000:5000"
    

Reverse Proxies:

These services route requests to other services, often used for load balancing and SSL termination.

  • Nginx:

      version: '3'
      services:
        web:
          image: nginx:latest
          ports:
            - "80:80"
          volumes:
            - ./nginx.conf:/etc/nginx/nginx.conf
    

Databases:

Services that provide persistent storage for your application data.

  • MySQL:

      version: '3'
      services:
        db:
          image: mysql:5.7
          environment:
            MYSQL_ROOT_PASSWORD: rootpassword
            MYSQL_DATABASE: mydatabase
          ports:
            - "3306:3306"
    
  • PostgreSQL:

      version: '3'
      services:
        db:
          image: postgres:13
          environment:
            POSTGRES_USER: user
            POSTGRES_PASSWORD: password
            POSTGRES_DB: mydatabase
          ports:
            - "5432:5432"
    

Cache Systems:

Services that cache data to improve performance.

  • Redis:

      version: '3'
      services:
        cache:
          image: redis:latest
          ports:
            - "6379:6379"
    

Message Brokers:

Services that handle messaging and queuing for asynchronous communication.

  • RabbitMQ:

      version: '3'
      services:
        message_broker:
          image: rabbitmq:3-management
          ports:
            - "5672:5672"
            - "15672:15672"
    

Background Workers:

Services that perform tasks in the background, often used for processing jobs or handling asynchronous tasks.

  • Celery (with Redis as a broker):

      version: '3'
      services:
        worker:
          image: python:3.8
          working_dir: /app
          volumes:
            - .:/app
          command: celery -A myapp worker --loglevel=info
    

Development Tools:

Services used for development and testing purposes.

  • Development Environment with Docker:

      version: '3'
      services:
        dev:
          image: node:14
          working_dir: /app
          volumes:
            - .:/app
          command: npm start
          ports:
            - "3000:3000"
    

Each service can be customized with different configurations such as volumes, networks, environment variables, and more, depending on the requirements of your application.


Conclusion

Docker Compose is a game-changer when it comes to managing multi-container Docker applications. By defining your services in a single docker-compose.yml file, you can easily deploy, manage, and scale your applications. This guide walked you through the basics of Docker Compose and demonstrated how to deploy a Flask application on an EC2 instance. With these skills, you can now take on more complex applications and orchestrations with confidence. Happy deploying!