Elena' s AI Blog

CI/CD and Docker

26 Sep 2026 (updated: 16 Sep 2026) / 11 minutes to read

Elena Daehnhardt

Generated by Midjourney. Prompt: Abstract cloud computing architecture illustration.


TL;DR:
  • CI/CD with Docker Compose becomes reliable when builds are reproducible, secrets are isolated, and deployment gates are explicit.

Previous: Part 7 — Docker Compose CI/CD with GitHub Actions: A Reliable Flask Workflow

Introduction

I have written before about using Docker and Docker Compose for multi-container orchestration. That covers your laptop. It does not cover what happens when someone else pushes a change and you are not there to watch it build. That is what CI/CD is for, and in this post I wire Docker Compose into GitHub Actions so builds, tests, and deployments happen automatically, reproducibly, and without me babysitting them.

CI/CD and Docker

Continuous Integration and Continuous Deployment

CI/CD stands for Continuous Integration and Continuous Deployment (or Continuous Delivery), and it is a set of practices in software development aimed at automating and streamlining the processes of building, testing, and deploying code changes.

  • Continuous Integration (CI): Developers frequently integrate code changes into a shared repository. With CI, automated build and test processes are triggered whenever code changes are pushed to the repository. This ensures that new code integrates smoothly with the existing codebase and helps catch integration issues early in the development cycle.

  • Continuous Deployment (CD) / Continuous Delivery (CD):

    • Continuous Delivery: After successful integration and testing, the code is automatically prepared and delivered to a staging or pre-production environment. In continuous delivery, the deployment to production is a manual process triggered by the development team when they decide the code is ready for release.

    • Continuous Deployment: In continuous deployment, the entire process is automated, and changes that pass all tests are automatically deployed to the production environment without manual intervention. This results in a faster and more efficient release cycle.

CI/CD practices contribute to the following benefits:

  1. Early Detection of Issues: Automated testing during the CI phase helps identify bugs and integration problems early in the development process, reducing the chances of defects reaching production.

  2. Faster Release Cycles: Automation of build, test, and deployment processes speeds up the delivery of new features and bug fixes, enabling more frequent releases.

  3. Consistency: CI/CD ensures consistency in the development, testing, and deployment environments, reducing the “it works on my machine” problem.

  4. Risk Reduction: With automated testing and deployment, there is a lower risk of human error during manual processes, leading to more reliable and stable software.

  5. Scalability: CI/CD practices are crucial in scalable and agile development, allowing teams to efficiently manage and release software updates.

Tools like Jenkins, Travis CI, GitLab CI/CD, and GitHub Actions are commonly used to implement CI/CD pipelines, integrating seamlessly with version control systems and other development tools.

GitHub Actions with Docker Compose

Using GitHub Actions with Docker Compose involves creating workflows that define the steps to build, test, and deploy your application.

Below is a simple example of a GitHub Actions workflow for a Docker Compose project. This assumes you have a docker-compose.yml file in your repository.

  1. Create a Workflow File:
    • Create a directory named .github/workflows inside your GitHub repository.
    • In that directory, create a YAML file, for example, docker-compose.yml.
  2. Define the Workflow:
    • Define the workflow steps in the YAML file. Below is a basic example:

      name: Docker Compose Build and Test
      
      on:
        push:
          branches:
            - main
      
      jobs:
        build:
          runs-on: ubuntu-latest
      
          steps:
            - name: Checkout Repository
              uses: actions/checkout@v4
      
            - name: Build and Test
              run: |
                docker compose up -d
                docker compose exec -T your-service-name pytest
              working-directory: path/to/your/app
      
            - name: Stop Docker Compose
              run: docker compose down
              working-directory: path/to/your/app
      
    • Customize the workflow according to your project structure. Replace your-service-name and path/to/your/app with the actual service name and the path to your application.

  3. Commit and Push:
    • Commit the workflow file to your repository.
    • Push the changes to trigger the GitHub Actions workflow.

This example workflow does the following:

  • It runs on each push to the main branch.
  • It checks out the repository, builds and tests the application using Docker Compose, and then stops the Docker Compose services.

A couple of things worth calling out, since I got both wrong the first time I wrote a workflow like this:

  • ubuntu-latest runners already ship with Docker and the Compose v2 plugin installed. You do not need a services: block running a separate docker:dind container to get Docker inside your job - that pattern is a GitLab CI idiom, and dragging it into GitHub Actions just gets you a second, unauthenticated Docker daemon listening on port 2375 for no reason. Skip it.
  • Use docker compose (a space, no hyphen), not docker-compose. The standalone Python docker-compose v1 binary reached end-of-life in mid-2024 and GitHub removed it from the runner images in 2025. If a tutorial you’re reading still uses the hyphenated form, it is out of date.
  • docker compose exec -T disables pseudo-TTY allocation, which matters in CI - without -T, exec can hang or fail on a runner that has no TTY attached.

Pin actions/checkout to at least @v4 (it moved to a Node 20 runtime; the old @v2 tag is stuck on a deprecated Node version and increasingly triggers warnings), and check for a newer major version periodically - actions/checkout picked up a security-relevant default change in mid-2026 around fork pull requests, which is exactly the kind of thing you want without having to notice it yourself.

Customize the workflow according to your specific needs, such as adding deployment steps or handling secrets. Additionally, consider versioning your workflows and using the appropriate GitHub Actions for your use case.

The provided example is a starting point; you may need to adjust it based on your project structure and requirements.

GitHub Actions for the Flask App

Below is a simplified example of a GitHub Actions workflow for a “Hello World!” Flask app using Docker Compose. This example assumes a Flask app with a basic structure and a docker-compose.yml file.

  1. Create a Workflow File:
    • Create a directory named .github/workflows inside your GitHub repository.
    • In that directory, create a YAML file, for example, flask-ci-cd.yml.
  2. Define the Workflow:
    • Define the workflow steps in the YAML file:

      name: Flask CI/CD with Docker Compose
      
      on:
        push:
          branches:
            - main
      
      jobs:
        build:
          runs-on: ubuntu-latest
      
          steps:
            - name: Checkout Repository
              uses: actions/checkout@v4
      
            - name: Build and Test
              run: |
                docker compose up -d
                docker compose exec -T web pytest
              working-directory: path/to/your/flask-app
      
            - name: Stop Docker Compose
              run: docker compose down
              working-directory: path/to/your/flask-app
      
    • Customize the workflow according to your Flask app structure. Replace path/to/your/flask-app with the path to your Flask application.

  3. Commit and Push:
    • Commit the workflow file to your repository.
    • Push the changes to trigger the GitHub Actions workflow.

This simplified example assumes your Flask app has a service named web in the docker-compose.yml file, and it runs tests using pytest. Adjust the commands and paths according to your specific setup.

This workflow will execute on each push to the main branch, and it performs the following steps:

  • Checks out the repository.
  • Builds and tests the Flask app using Docker Compose.
  • Stops the Docker Compose services after testing.

Remember to adapt the workflow to your specific project structure, dependencies, and testing procedures.

Environment separation and release gating

A build-and-test workflow is only half the job. Two things turned my early pipelines from “it runs” into something I would trust with production:

  • Environment separation. I keep a docker-compose.yml for shared service definitions and a docker-compose.override.yml (or separate -f files per environment) for the bits that differ - ports, volumes, debug flags. Compose merges them automatically in local dev, and CI passes explicit -f flags so it never accidentally picks up a dev override. Secrets never live in the compose file itself; they come from GitHub Actions secrets or a .env file that is .gitignored, injected at docker compose up time.
  • Release gating. Do not let “tests passed” mean “deployed.” Add a required status check on the branch protection rule for the build-and-test job, and put the deploy job behind a GitHub Actions environment with manual approval for production. That gives you continuous delivery (everything is ready to ship) without continuous deployment (everything ships automatically) until you actually want it.

Conclusion

Docker gets your Flask app into a consistent, portable container. Docker Compose coordinates it with whatever else it needs - a database, a cache, a worker. Wire both into GitHub Actions and you get the payoff: every push is built and tested the same way, on someone else’s machine, without you lifting a finger.

Flask is just the example I used here. The same pipeline shape - checkout, docker compose up, run tests, tear down, gate the deploy - works just as well for an API, a database migration, or a machine learning model behind a serving container. Reproducible builds and explicit gates are what make “it works in CI” actually mean something.

References

  1. Docker Compose CLI reference
  2. GitHub Actions: actions/checkout
  3. Using environments for deployment (GitHub Actions)

CI/CD Compose Controls

  1. Pin base image digests in production.
  2. Separate build/test/deploy compose profiles.
  3. Keep secrets out of compose files.
  4. Add health-check gates before promotion.
  5. Preserve rollback images for fast recovery.
desktop bg dark

About Elena

Elena, a PhD in Computer Science, simplifies AI concepts and helps you use machine learning.





Citation
Elena Daehnhardt. (2026) 'CI/CD and Docker', daehnhardt.com, 26 September 2026. Available at: https://daehnhardt.com/blog/2026/09/26/docker-compose-continues-integration-ci-cd/
All Posts