CI/CD Pipeline to Automate Docker Image Build

CI/CD Pipeline to Automate Docker Image Build

CI/CD Pipeline

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.

cat main.yml

---

- 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

  1. installing packages: installs required packages such as git, pip, and docker.

  2. adding ec2-user to docker group: adds the ec2-user to the docker group, which grants permission to use Docker without sudo.

  3. installing python extension for docker: installs the docker-py Python library, which allows Ansible to interact with Docker.

  4. restarting & enabling docker service: restarts the Docker service and ensures it is set to start on boot.

  5. creating cloning repo: creates a directory to store the cloned repository.

  6. cloning from repo: clones the GitHub repository specified in repo_url to the clone_dir.

  7. login to docker hub: log in to Docker Hub using the credentials specified in the dockerhub_creds.yml variable file.

  8. 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.

  9. logout from dockerhub: logs out of Docker Hub to prevent unauthorized access.

Play 2: Running Docker Image on Test Server

  1. installing packages: installs required packages such as docker and pip.

  2. adding ec2-user to docker group: adds the ec2-user to the docker group, which grants permission to use Docker without sudo.

  3. installing python extension for docker: installs the docker-py Python library, which allows Ansible to interact with Docker.

  4. restarting & enabling docker service: restarts the Docker service and ensures it is set to start on boot.

  5. pulling docker image: pulls the Docker image from Docker Hub with the specified name and tag.

  6. 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.

eg: 1.2.3.4:8080/github-webhook

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.

A build being initiated automatically on a commit

Checking the console output of a successful build

All the tasks from the playbook have been executed without any errors.

The images have been uploaded to the docker hub on successful execution of the playbook

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.