CI/CD Pipeline to Automate Docker Image Build
Docker has revolutionized the way developers and system administrators build and run applications. It has become an essential tool in the DevOps toolkit, enabling teams to automate the deployment of their applications with ease. In this article, we will explore how to automate the Docker image build process on every commit on the Git repo using Ansible.
In this article, we will use Ansible to automate the Docker image build process on every commit on the GitHub repo. We will use a playbook that will build a Docker image from a Flask app repository, push it to Docker Hub, and run it on a test server.
Set Up the Environment
Before we can automate the Docker image build process, we need to set up our environment. We need the following components:
- A Git repository containing the code.
- An Ansible control node with Jenkins installed and configured.
- A Docker Hub account.
Create the Ansible Playbook
We will create a deployment directory and move all the necessary files.
Remember to change the ownership of the directory to “jenkins” as the jenkins process will be run under the privilege of this user, making it easier for the user to access the files.
sudo mkdir /var/deploy
sudo mv main.yml dockerhub_creds.yml aws.pem hosts /var/deploy/
sudo chown -R jenkins:jenkins /var/deploy/
Next, we need to create an Ansible playbook that will automate the Docker image build process.
The following is the content of main.yml file
---
- name: "building docker image from github repo"
hosts: build
become: true
vars_files:
- dockerhub_creds.yml
vars:
packages:
- git
- pip
- docker
repo_url: https://github.com/sreehariskumar/flask-app.git
clone_dir: "/var/flask_app"
image_name: "sreehariskumar/flask-app"
tasks:
- name: "installing packages"
yum:
name: "{{ packages }}"
state: present
- name: "adding ec2-user to docker group"
user:
user: "ec2-user"
groups: docker
append: true
- name: "installing python extension for docker"
pip:
name: docker-py
- name: "restarting & enabling docker service"
service:
name: docker
state: restarted
enabled: true
- name: "creating cloning directory"
file:
path: "{{ clone_dir }}"
state: directory
- name: "cloning from repo"
git:
repo: "{{ repo_url }}"
dest: "{{ clone_dir }}"
register: clone_status
- name: "login to docker hub"
when: clone_status.changed
docker_login:
username: "{{ docker_username }}"
password: "{{ docker_password }}"
state: present
- name: "building docker image and pushing image to dockerhub"
when: clone_status.changed
docker_image:
source: build
build:
path: "{{ clone_dir }}"
pull: yes
name: "{{ image_name }}"
tag: "{{ item }}"
push: true
force_tag: yes
force_source: yes
with_items:
- "{{ clone_status.after }}"
- latest
- name: "logout from dockerhub"
when: clone_status.changed
docker_login:
username: "{{ docker_username }}"
password: "{{ docker_password }}"
state: absent
- name: "running image on test server"
hosts: test
become: true
vars:
image_name: "sreehariskumar/flask-app"
packages:
- docker
- pip
tasks:
- name: "installing packages"
yum:
name: "{{ packages }}"
state: present
- name: "adding ec2-user to docker group"
user:
user: "ec2-user"
groups: docker
append: true
- name: "installing python extension for docker"
pip:
name: docker-py
- name: "restarting & enabling docker service"
service:
name: docker
state: restarted
enabled: true
- name: "pulling docker image"
docker_image:
name: "{{ image_name }}"
source: pull
force_source: true
register: pull_status
- name: "running container"
when: pull_status.changed
docker_container:
name: flask-app
image: "{{ image_name }}:latest"
recreate: yes
pull: yes
published_ports: "80:5000"
Explanation:
The playbook is organized into two plays, each of which targets a different set of hosts. The first play targets the “build” hosts and contains tasks to build the Docker image and push it to Docker Hub. The second play targets the “test” hosts and contains tasks to pull the image from Docker Hub and run it in a container.
Here’s a breakdown of the tasks in each play:
Play 1: Building Docker Image and Pushing to Docker Hub
installing packages
: installs required packages such as git, pip, and docker.adding ec2-user to docker group
: adds theec2-user
to thedocker
group, which grants permission to use Docker withoutsudo
.installing python extension for docker
: installs thedocker-py
Python library, which allows Ansible to interact with Docker.restarting & enabling docker service
: restarts the Docker service and ensures it is set to start on boot.creating cloning repo
: creates a directory to store the cloned repository.cloning from repo
: clones the GitHub repository specified inrepo_url
to theclone_dir
.login to docker hub
: log in to Docker Hub using the credentials specified in thedockerhub_creds.yml
variable file.building docker image and pushing image to dockerhub
: builds a Docker image from the cloned repository and pushes it to Docker Hub with the specified name and tags.logout from dockerhub
: logs out of Docker Hub to prevent unauthorized access.
Play 2: Running Docker Image on Test Server
installing packages
: installs required packages such as docker and pip.adding ec2-user to docker group
: adds theec2-user
to thedocker
group, which grants permission to use Docker withoutsudo
.installing python extension for docker
: installs thedocker-py
Python library, which allows Ansible to interact with Docker.restarting & enabling docker service
: restarts the Docker service and ensures it is set to start on boot.pulling docker image
: pulls the Docker image from Docker Hub with the specified name and tag.running container
: runs the Docker image in a container with the specified port mappings.
By using Ansible to automate the process of building and deploying the Docker image, this playbook simplifies the deployment process and helps ensure consistency and repeatability across multiple hosts.
Creating Jenkins Project
- Create a “Freestyle project” with any name.
eg: docker-image-build-from-git-repo - Give a description for the project you’re building.
- Mention the repository URL which we need to fetch. The credentials field should be none.
- Select “GitHub hook” among the list of Build Triggers.
- Select “Invoke Ansible Playbook” as Build Steps and define the absolute path of the ansible-playbook which we need to execute.
- To initiate a build, Click on the “Build now” icon on the job page.
- You can click on the build to view the console output.
As you can see the build has completed without any error.
Integrate with Git
Now that we have created the Ansible playbook, we need to integrate it with Git to automate the Docker image build process on every commit.
To do this, we will use a Git webhook that will trigger the Ansible playbook whenever a commit is made to the Git repository. We will use the ansible-playbook
command to run the playbook on the Ansible control node.
To set up the webhook, we need to create a new webhook on the Git repository settings page. We will configure the webhook to trigger on push events, and we will provide the URL of the Ansible control node along with the path to the Ansible playbook.
- Go to the settings page of your GitHub project and select “Webhook” option. Now add a new webhook with the public IP of the Jenkins server.
Test the Automation
Finally, we need to test the automation to ensure that the Docker image is built and pushed to Docker Hub on every commit.
To test the automation, we will make a commit to the GitHub repository containing the Flask app code. We will then check Docker Hub to ensure that the Docker image has been built and pushed successfully.
Conclusion
In this article, we have explored how to automate the Docker image build process on every commit on the Git repo using Ansible. We have created an Ansible playbook that builds a Docker image from a Flask app repository, pushes it to Docker Hub, and runs it on a test server. We have also integrated the playbook with Git using a webhook to automate the Docker image build process on every commit.
By automating the Docker image build process, we can ensure that our applications are always up-to-date and consistent across all environments. It also allows us to focus on developing and improving our applications without worrying about the deployment process.