11. CI/CD Integration with Docker | The Complete Docker Handbook.
Welcome to Article 11 of The Complete Docker Handbook.
In Article 10, we secured our containers against vulnerabilities and resource abuse. Now, let's talk about automation.
So far, you have been building images manually (docker build) and pushing them to registries manually (docker push). This works for learning, but in a professional environment, manual deployments are risky. They are slow, prone to human error, and hard to reproduce.
Enter CI/CD (Continuous Integration and Continuous Deployment).
In this article, you will learn how to integrate Docker into your development workflow. We will set up an automated pipeline using GitHub Actions that builds, tests, and pushes your Docker image every time you commit code.
What is CI/CD?
- Continuous Integration (CI): Developers frequently merge code changes into a central repository. Automated builds and tests run to verify the changes.
- Continuous Deployment (CD): Once the code passes tests, it is automatically released to production (or a staging environment).
Where does Docker fit in? Docker ensures that the environment used during the CI build is identical to the production environment. You aren't just testing code; you are testing the containerized application.
The Workflow: From Commit to Container
We will build a pipeline that does the following:
- Trigger: You push code to GitHub.
- Checkout: The pipeline downloads your code.
- Login: The pipeline logs into Docker Hub securely.
- Build: The pipeline builds your Docker image.
- Push: The pipeline uploads the image to Docker Hub with a unique tag.
[Visual Idea: Diagram showing Code Commit -> GitHub Actions -> Docker Hub -> Production Server]
Step 1: Prerequisites
- GitHub Account: Your code is hosted here.
- Docker Hub Account: Where your images will be stored.
- Docker Hub Access Token: Do not use your main password.
- Go to Docker Hub -> Account Settings -> Security.
- Create a new Access Token. Copy it safely; you won't see it again.
Step 2: Storing Secrets in GitHub
Never hardcode credentials in your code or workflow files. GitHub provides a secure vault called Secrets.
- Go to your GitHub Repository.
- Click Settings > Secrets and variables > Actions.
- Click New repository secret.
- Add the following:
- Name:
DOCKER_USERNAME - Value: Your Docker Hub username.
- Name:
- Add another secret:
- Name:
DOCKER_PASSWORD - Value: Your Docker Hub Access Token (not your login password).
- Name:
Step 3: Creating the Workflow File
GitHub Actions workflows are defined in YAML files stored in .github/workflows/.
- In your project root, create the folder structure:
.github/workflows/. - Create a file named
docker-image.yml.
The Workflow Configuration
Yaml1name: Docker Image CI
2
3on:
4 push:
5 branches: [ "main" ]
6 pull_request:
7 branches: [ "main" ]
8
9jobs:
10 build-and-push:
11 runs-on: ubuntu-latest
12
13 steps:
14 # 1. Checkout the code
15 - name: Checkout code
16 uses: actions/checkout@v3
17
18 # 2. Login to Docker Hub
19 - name: Login to Docker Hub
20 uses: docker/login-action@v2
21 with:
22 username: ${{ secrets.DOCKER_USERNAME }}
23 password: ${{ secrets.DOCKER_PASSWORD }}
24
25 # 3. Build and Push
26 - name: Build and push Docker image
27 uses: docker/build-push-action@v4
28 with:
29 context: .
30 push: true
31 tags: |
32 ${{ secrets.DOCKER_USERNAME }}/my-app:latest
33 ${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }}
Breakdown of the Workflow
on: Triggers the workflow on every push or pull request to themainbranch.runs-on: ubuntu-latest: Runs the pipeline on a fresh Ubuntu virtual machine provided by GitHub.actions/checkout@v3: Downloads your repository code so the pipeline can access the Dockerfile.docker/login-action@v2: Securely logs into Docker Hub using the Secrets you created.docker/build-push-action@v4: The heavy lifter. It builds the image and pushes it.tags::latest: Overwrites the latest tag (convenient for dev).:${{ github.sha }}: Tags the image with the unique commit hash (e.g.,:a1b2c3d). This is crucial for production because it allows you to roll back to a specific commit version.
Step 4: Running the Pipeline
- Commit and push your code to GitHub.
Plain Text
1bash 2git add . 3git commit -m "Add CI pipeline" 4git push - Go to your GitHub Repository -> Actions tab.
- You will see the workflow running. Click on it to view logs.
- If successful, you will see green checkmarks.
- Check your Docker Hub repository. You should see the new image with the tags
latestand the commit hash.
Step 5: Optimizing CI Builds (Caching)
Building Docker images in CI can be slow because every run starts fresh. You can speed this up by caching layers.
Update your docker/build-push-action step:
Yaml1- name: Build and push Docker image
2 uses: docker/build-push-action@v4
3 with:
4 context: .
5 push: true
6 tags: ${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }}
7 cache-from: type=gha
8 cache-to: type=gha,mode=max
type=gha: Uses GitHub Actions cache storage.- Benefit: Subsequent builds will download cached layers instead of rebuilding them from scratch, reducing build time from minutes to seconds.
Step 6: Tagging Strategies for Production
Using :latest is dangerous in production. If something breaks, you don't know which version caused it.
Recommended Tagging Strategy:
- Commit SHA:
my-app:8f3a2b1(Unique to every build). - Semantic Version:
my-app:1.0.1(When you create a Git release tag). - Branch Name:
my-app:dev(For development builds).
Example: Tagging on Git Release
You can modify the on section to trigger only on Git tags:
Yaml1on:
2 push:
3 tags:
4 - 'v*'
Then set the tag to ${{ github.ref_name }} (which will be v1.0.1).
Security Best Practices in CI
- Least Privilege: Your Docker Hub Access Token should only have write permissions to repositories, not account-wide permissions.
- Scan in CI: Integrate security scanning into the pipeline. You can add a step using
docker scanortrivybefore pushing. If vulnerabilities are found, fail the build. - Protect Branches: Enable GitHub Branch Protection Rules to prevent pushing directly to
mainwithout a passing CI build.
Summary Checklist
By the end of this article, you should be able to:
- Explain the role of Docker in CI/CD.
- Store secrets securely in GitHub Actions.
- Write a workflow file to build and push Docker images.
- Tag images using commit SHAs for version control.
- Optimize build speed using GitHub Actions caching.
- Understand the risk of using
:latestin production.
What's Next?
You now have a fully automated pipeline building secure Docker images. But what happens when things go wrong?
- Why did the container exit immediately?
- Why is the network connection timing out?
- How do you debug a container that won't start?
In Article 12, we will focus on Troubleshooting and Debugging. You will learn how to interpret logs, inspect container states, and solve the most common Docker errors.
Link: Read Article 12: Debugging and Troubleshooting Containers
Challenge: Set up the GitHub Actions workflow for your project. Make a small change to your code (e.g., change a print statement), push it, and verify that a new image appears in Docker Hub with the new commit hash tag.