Skip to main content
  1. Posts/

Continuous Integration and Continuous Deployment (CI/CD): A Case for Docker Compose

· loading · loading ·
Jared Lynskey
Author
Jared Lynskey
Emerging leader and software engineer based in Seoul, South Korea
Table of Contents

In the modern landscape of software development, Continuous Integration and Continuous Deployment (CI/CD) are vital practices for delivering reliable and efficient software. CI/CD ensures that code changes are automatically tested and deployed, promoting rapid and consistent delivery. However, the current approach, especially with GitHub Actions, introduces several challenges that can hinder development.

The Problem with GitHub Actions and YAML
#

One of the primary issues with GitHub Actions is the heavy reliance on YAML configuration files. While YAML is a powerful and flexible way to define workflows, it comes with several drawbacks:

  1. Lack of Portability: YAML configurations in GitHub Actions are specific to GitHub. This creates a dependency that is not easily transferable to other CI/CD platforms like GitLab, Bitbucket, or Jenkins.
  2. Complexity and Maintainability: As projects grow, the YAML files become more complex and harder to maintain. This complexity can obscure the Single Responsibility Principle (SRP), where one script should handle one aspect of the build process.
  3. Developer Overhead: Developers need to understand not only Git but also the intricacies of YAML and the specific CI/CD tools of their chosen platform. This added learning curve can slow down development and introduce errors.

Proposing Docker Compose for CI/CD
#

To address these issues, I propose leveraging Docker Compose for building and testing code, while using CI/CD tools like GitHub Actions merely as orchestrators. Here’s how this approach can help:

  1. Portability: Docker Compose files are standard and can be run locally or on any CI/CD platform. This ensures that your build configurations are portable and not tied to a specific provider.
  2. Simplification: By isolating the build and test processes within Docker containers, the CI/CD configuration becomes simpler. Developers only need to maintain Docker Compose files, reducing the complexity of YAML configurations.
  3. Local Consistency: Docker Compose allows developers to run the exact same build and test processes locally as in the CI/CD pipeline. This reduces the “it works on my machine” problem and ensures consistency across environments.

Implementing Docker Compose in CI/CD
#

Here’s a step-by-step guide on how to integrate Docker Compose into your CI/CD pipeline:

  1. Define Dockerfile File: Create a Dockerfilefile that defines the steps to install and run your application. This will be used later in the docker compose file. The benefit of using a dockerfile is to seperate the resposiblity of building the docker image.
# Use an official Node.js runtime as the base image
FROM node:14

# Set the working directory in the container
WORKDIR /app

# Copy the package.json and package-lock.json files to the container
COPY package*.json ./

# Install the dependencies
RUN npm install

# Copy the rest of the application code to the container
COPY . .

# Expose the port the app runs on
EXPOSE 3000

# Define the command to run the application
CMD ["npm", "start"]
  1. Define Docker Compose File: Create a docker-compose.build.yaml file that defines all the services your application needs to build and run.

    services:
      app:
        image: myprivaterepo.com/image:latest
        build: .
        volumes:
          - .:/app
        command: sh -c "npm install && npm test"
    
  2. Local Testing: Run Docker Compose locally to ensure that the build works as expected.

    docker compose -f docker-compose.build.yaml build
    
  3. CI/CD Configuration: In your GitHub Actions (or any other CI/CD platform) workflow file, use Docker commands to run the Docker Compose file.

    name: CI/CD Pipeline
    on: [push, pull_request]
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - name: Set up Docker Buildx
            uses: docker/setup-buildx-action@v1
          - name: Build and Test with Docker Compose
            run: docker compose -f docker-compose.build.yaml build
          - name: Push the image referenced in the docker compose file
            run: docker compose -f docker-compose.build.yaml push
    

This setup ensures that the CI/CD tool is responsible only for orchestrating the Docker Compose process, while Docker Compose handles the actual building and testing of the application. This separation of concerns adheres to the Single Responsibility Principle and reduces the complexity of the CI/CD configuration.

  1. Pushing to Docker Image Repository: One of the benefits of using a Compose file is that you can easily push the image to the repository where it is hosted. Docker uses the image name to determine where the image is stored. For example, image: myprivaterepo.com/image:latest indicates that the image can be found at the public domain myprivaterepo.com. To push to a private repository, please refer to the instructions provided by your repository service to obtain the access token.

Conclusion
#

By using Docker Compose in your CI/CD pipeline, you can achieve greater portability, simplicity, and consistency. This approach allows developers to focus on writing code and defining build processes in a standard format, rather than getting bogged down in provider-specific configurations. Ultimately, this can lead to faster development cycles and more reliable software delivery.