Continuous Integration with Docker
# CHAPTER 9
Continuous Integration with Docker
1. Introduction
Historically, configuring a CI/CD server was a nightmare. If Project A required PHP 7.4 and Project B required PHP 8.1, installing both on the same Jenkins server often caused massive software conflicts. Furthermore, if the CI server's operating system differed slightly from the Production server, code that compiled cleanly in CI might crash in Production. The solution to this environmental chaos is Docker. In this chapter, we will learn how to containerize our CI workflows. By running our builds and tests inside Docker containers, we guarantee absolute environmental consistency and isolate our build pipelines from the underlying CI runner infrastructure.2. Learning Objectives
By the end of this chapter, you will be able to:- Understand the operational benefits of executing CI pipelines inside Docker containers.
- Differentiate between the CI Runner host and the Docker Container environment.
- Use Docker to execute test suites in isolated environments.
- Build a Docker Image as the final Build Artifact of a CI pipeline.
- Push a Docker Image to a Container Registry (e.g., Docker Hub) using GitHub Actions.
3. Beginner-Friendly Explanation
Imagine a Chef (The CI Pipeline) working in a shared Kitchen (The CI Runner).- The Problem: The Chef is baking a delicate cake, but someone else just chopped onions on the same cutting board. The cake tastes like onions. (Software conflicts on a shared CI server).
- The Docker Solution: The Chef brings their own impenetrable, plastic bubble to work. Inside the bubble is a brand new, perfectly clean cutting board, the exact brand of flour they need, and an oven set to the exact temperature. They bake the cake inside the bubble. When the cake is done, the bubble is destroyed.
Docker ensures your code is built and tested in a perfectly sterile, custom-made environment, completely isolated from the CI server it runs on.
4. Running Tests Inside Docker
Instead of asking GitHub Actions to install PHP and Composer directly onto its Ubuntu server, we can ask GitHub to spin up a Docker container that *already* has PHP installed, and run our tests *inside* that container.5. Docker as the Final Artifact
In Chapter 6, our final artifact was arelease.zip file containing our code.
In modern DevOps, the artifact is a Docker Image. We don't just package the code; we package the code *and* the operating system *and* the Nginx web server into a single, immutable container image.
If we build a Docker image, the goal of the CI pipeline is to execute docker build, and then upload that image to a Container Registry (like Docker Hub or AWS ECR) so the Production servers can download it later.
6. Mini Project: Build and Push Docker CI Workflow
Let's build a pipeline that compiles our application into a Docker Image and securely pushes it to Docker Hub.Step-by-Step Architecture Concept:
-
1.
Ensure your repository has a valid
Dockerfileat its root.
- 2. Store your Docker Hub username and password in GitHub Secrets.
- 3. Create the Workflow:
*When this finishes, your fully compiled application, along with its entire operating system environment, is safely stored in Docker Hub, ready to be deployed anywhere in the world.*
7. Real-World Scenarios
A development team was tasked with maintaining a legacy Python 2.7 application. Python 2.7 was end-of-life and removed from most modern Linux servers. Every time the CI runner was updated by the IT department, the Python 2.7 build pipeline would break because the software was missing. The DevOps engineer containerized the build process. They instructed the CI pipeline to run inside a legacypython:2.7 Docker container. It no longer mattered what software the IT department installed on the underlying Jenkins server; the build happened inside the isolated Docker bubble, securing the legacy build pipeline permanently.
8. Best Practices
-
Tagging with the Commit Hash (
github.sha): In the mini-project, we tagged the Docker image with${{ github.sha }}. This is a crucial best practice. Never just tag an image aslatest. If you deploylatest, and it breaks, and you try to rollback to the previouslatest... you can't, because it was overwritten. Tagging images with the unique Git commit hash creates a permanent, traceable link between the Docker Image in production and the exact code commit in GitHub.
9. Security Recommendations
-
Scanning Docker Images: Just because an application is in a container doesn't mean it is secure. The base image (e.g.,
ubuntu:20.04) might contain critical vulnerabilities. Modern CI pipelines include a step *after* thedocker buildbut *before* thepushto run a container vulnerability scanner like Trivy or Clair. If the scanner finds a critical CVE (vulnerability) in the Docker image, it fails the pipeline and blocks the upload to the registry.
10. Troubleshooting Tips
-
Docker in Docker (DinD): If you are running a self-hosted Jenkins server *inside* a Docker container, and you try to run
docker buildinside your Jenkins pipeline, it will fail because a container cannot easily run Docker commands inside itself. You must map the host's Docker socket (/var/run/docker.sock) into the Jenkins container to allow the pipeline to communicate with the host's Docker engine.
11. Exercises
-
1.
Explain the operational difference between compiling a
release.zipArtifact and compiling a Docker Image Artifact in a CI pipeline.
- 2. What is the operational purpose of a Container Registry (like Docker Hub) in the CI/CD lifecycle?
12. FAQs
Q: Does GitHub Actions support Docker Compose? A: Yes! You can rundocker-compose up -d in a GitHub Actions run: step to spin up complex, multi-container environments (like an app and a database) specifically to run Integration Tests against them, before tearing them down.
13. Interview Questions
- Q: Describe the environmental and consistency benefits of executing CI testing and build stages inside a Docker Container rather than directly on the bare-metal CI Runner.
-
Q: Explain the enterprise best practice for versioning/tagging Docker images during a CI build phase. Why is the
latesttag considered an anti-pattern for production deployments?
14. Summary
In Chapter 9, we isolated our CI/CD workflows from infrastructure dependencies by embracing Containerization. We demonstrated how executing pipeline stages inside Docker containers ensures an absolute "clean room" environment, eliminating software conflicts and the "It works on my machine" paradigm. We revolutionized our Build Phase, transitioning from packaging zip files to compiling immutable Docker Images. Finally, we automated the authentication and distribution of these images, pushing our final containerized artifacts to a centralized Docker Registry, perfectly positioning them for modern, highly-scalable cloud deployment.15. Next Chapter Recommendation
Our pipeline builds Docker images and pushes them to the web. But to do that, it needed a password (secrets.DOCKER_PASSWORD). Managing these passwords securely is the most critical security aspect of CI. Proceed to Chapter 10: Environment Variables and Secrets.