From Code to Deployment: How to Use Docker for Continuous Integration

Docker has become an essential tool for building and deploying modern applications. In a continuous integration and delivery (CI/CD) pipeline, Docker can help streamline the process of testing and deploying code changes. In this blog post, we’ll provide an overview of how to use Docker in a CI/CD pipeline, including how to automate testing and deployment. We’ll show you how to set up a basic CI/CD pipeline using Github Actions, how to build a Docker image for your application, and how to run automated tests and deploy your application using Docker.

Setting up the CI/CD pipeline with Github Actions

Github Actions is a powerful tool that allows you to automate your software development workflows. With Github Actions, you can create custom workflows that automatically build, test, and deploy your code changes. In this section, we’ll walk through the steps of setting up a basic CI/CD pipeline using Github Actions.

Before you begin, add your Docker Hub username and password as secrets in your GitHub repository by navigating to your repository’s settings page, selecting “Secrets” from the left sidebar, and clicking “New repository secret”. Add DOCKER_USERNAME and DOCKER_PASSWORD so your scripts can log in and use your Docker hub images.

We have used placeholders for the following values, make sure you replace these values before trying to run your scripts.

your-dockerhub-usernameThe username of your docker account
your-docker-image-nameThe name of your image on docker hub

Running automated tests with Docker

To get started, you’ll need to create a workflow file in your Github repository. A workflow file is a YAML file (e.g., .github/workflows/push.yml) that contains the instructions for Github Actions to follow. Here’s an example workflow file that sets up a basic CI/CD pipeline:

name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v2
      
    - name: Login to Docker Hub
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Run Codeception tests
      uses: docker://your-dockerhub-username/your-docker-image-name:latest
      with:
        entrypoint: vendor/bin/codecept run

In this example, the workflow defines a single job named test, which runs on the latest version of Ubuntu. The job has two steps:

  1. The Checkout code step checks out your code from your Github repository.
  2. The Run Codeception tests step uses the Docker image your-dockerhub-username/your-docker-image-name:latest to run your Codeception tests. The entrypoint option specifies the command to run inside the Docker container.

You’ll need to replace your-dockerhub-username and your-docker-image-name with the appropriate values for your Docker image.

If you need to run other Docker images in addition to your own Docker image, you can use the docker-compose command to define a multi-container environment and specify the environment variables that need to be passed to each container. Here’s an example workflow file that shows how to use docker-compose to run your own Docker image along with a MySQL container, and pass environment variables to both containers:

name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v2
      
    - name: Login to Docker Hub
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Start containers
      run: |
        docker-compose up -d
        sleep 5 # wait for containers to start up

    - name: Run Codeception tests
      run: docker exec my-image vendor/bin/codecept run

    - name: Stop containers
      run: docker-compose down

In this example, the db service uses the mysql:8.0 Docker image and sets the MYSQL_DATABASE, MYSQL_USER, and MYSQL_PASSWORD environment variables. These variables are set in the docker-compose.yml file.

version: '3.8'

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: my_database
      MYSQL_USER: root
      MYSQL_PASSWORD: secret

  tests:
    image: your-dockerhub-username/your-docker-image-name:latest
    environment:
      DB_HOST: db
      DB_USER: root
      DB_PASS: secret
    entrypoint: vendor/bin/codecept run

Note that in the Run Codeception tests step, we’re using the --network="host" option to connect to the MySQL container running on the host network. This allows us to access the MySQL container without exposing ports or setting up additional network configuration. However, you can also use other network modes and configurations to connect to your MySQL container, depending on your requirements

Building a Docker image

Create a new file in your project directory called Dockerfile with the following contents:

# Use an official PHP runtime as a parent image
FROM php:8.2-apache

# Set the working directory to /var/www/html
WORKDIR /var/www/html

# Copy the current directory contents into the container at /var/www/html
COPY . /var/www/html/

# Install any needed packages specified in requirements.txt
RUN apt-get update && apt-get install -y \
    git \
    unzip \
    libzip-dev \
    && docker-php-ext-install zip

# Expose port 80 for the web server
EXPOSE 80 443

Create a new GitHub Actions workflow file (e.g., .github/workflows/build-and-publish.yml) with the following contents:

name: Build and Publish Docker Image

on:
  push:
    branches: [main]

jobs:
  build-and-publish:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Get previous Docker image tag
        id: get_previous_tag
        run: |
          echo "::set-output name=previous_tag::$(curl -sS -u ${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }} https://registry.hub.docker.com/v2/repositories/your-dockerhub-username/your-docker-image-name/tags/?page_size=10000 | jq -r '.results[].name' | sort -r -V | head -n 1)"

      - name: Build Docker image
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: |
            your-dockerhub-username/your-docker-image-name:${{ steps.get_previous_tag.outputs.previous_tag }}
            your-dockerhub-username/your-docker-image-name:latest
          dockerfile: Dockerfile

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push Docker image to Docker Hub
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: |
            your-dockerhub-username/your-docker-image-name:${{ steps.get_previous_tag.outputs.previous_tag }}
            your-dockerhub-username/your-docker-image-name:latest

The on push part of the yml defines when this action is run. In this case, when a new push is made to the main branch, we automatically create a new docker image with a new tag, and overwrites the latest tag.

In this workflow file, the curl command uses the -u option to pass the Docker Hub username and password for authentication. This allows the API request to access non-public images. You will need to replace your-dockerhub-username and your-docker-image-name with your own values.

Also, make sure that you have set up the DOCKER_USERNAME and DOCKER_PASSWORD secrets in your GitHub repository. You can set these secrets by navigating to your repository settings, selecting “Secrets” from the left sidebar, and then clicking “New repository secret”

Note: This workflow assumes that you are using semver-style tags (e.g., v1.2.3). If you are using a different tag format, you may need to modify the jq command to extract the previous tag correctly.

Docker tagging

There are two common ways to tag a Docker image: using a version number or using the “latest” tag. A version number tag, such as “v1.0” or “1.0.1”, identifies a specific version of an image that can be referred to later. The “latest” tag, on the other hand, always refers to the most recently built version of an image, regardless of whether it is a new version or an existing version that has been updated.

When deploying a Docker image to a production environment, it is always recommended to use a specific version tag rather than the “latest” tag. This is because the “latest” tag is always changing, and there is no guarantee that the most recent version is stable or compatible with your environment. By using a specific version tag, you can ensure that the same version of the image is deployed every time, which makes it easier to troubleshoot issues and maintain consistency across your infrastructure.

Additionally, if you only use the “latest” tag and there are multiple versions of the image with the same tag, it can become difficult to track which version is currently deployed in production. If you use version number tags, it’s easier to keep track of which versions have been deployed and roll back to previous versions if necessary.

So while the “latest” tag can be convenient for development and testing environments, it is best to always use version number tags when deploying Docker images to production environments. This helps ensure stability, consistency, and easier management of your Docker images.

Key takeaways

  1. Docker is a powerful tool that can be used to streamline your CI/CD pipeline by providing a consistent environment for testing and deployment.
  2. You can use GitHub Actions to automate your CI/CD pipeline and easily integrate Docker into your workflow.
  3. Building Docker images can be automated using GitHub Actions and Docker Hub.
  4. Automated testing can be run in a Docker container using tools like Codeception.
  5. Deploying your application using Docker can be done with Docker Compose or Kubernetes.
  6. It’s important to tag your Docker images with version numbers and deploy using specific tags to ensure consistency and avoid issues with the latest tag.

Fuse Web can help

Docker is a powerful platform with lots of advantages for businesses, but it can also be daunting to use. The process of containerizing apps and maintaining containers can be complicated and time-consuming for many businesses. Furthermore, businesses may be worried about the security and scalability of their containerized apps, and they may even lack the skills or resources to operate their Docker environment successfully.

Fuse Web can assist businesses in overcoming these challenges by offering professional guidance and support for their Docker-based initiatives. Our team has considerable Docker knowledge and can assist businesses with containerizing their apps, managing their containers, and optimizing their Docker environment for speed and scalability. Don’t hesitate, contact us now to see how we can help.

Related content