Git
Linux
Cloud Native

CI CD Pipelines with GitHub Actions

Continuous integration (CI) is a practice to automate the process of code management. It usually means integrating your code changes into a specific branch.
July 3, 2024

Introduction

CI/CD combines continuous integration (CI) and continuous delivery and/or deployment (CD) to streamline the software development lifecycle. It has become a crucial component as it can automate everything from coding to deployment. In the world of efficiency, automation by CI/CD means releasing new features, and bugfixes can be much more frequent and reliable as human intervention is no longer necessary in most cases.

Since the popularity of CI/CD, more tools have appeared on the market to build and maintain your own CI/CD pipelines. The tool we are talking about today, GitHub Actions, is also one of the most popular.

Understanding CI and CD

Continuous Integration

Continuous integration (CI) is a practice to automate the process of code management. It usually means integrating your code changes into a specific branch in a repository, then testing the changes per commit or merge, and finally kicking off a build. It includes but is not limited to code compilation, automated unit tests, static code analysis, and building artifacts.

During CI, testing and customized validations are automatically triggered by merge. It saves time and resources on manual testing. By the traditional way, you may probably wait for a few days for QA or security teams to evaluate your changes. With CI, they could be done in hours or even minutes. You can still do manual checks for big changes, but automated CI can handle all the static checks for small changes.

Continuous Delivery

Continuous delivery is the stage that takes the artifacts produced during CI and ensures they can be released to production at any time. Its focus is more on automating the release process. It includes but is not limited to deploying to staging environments and additional tests like end-to-end tests.

Passing CI and moving to CD usually means the changes to the application are bug-tested, and its artifacts have been uploaded to a repository, like Docker images to any container registry. After passing the final validation at this stage, it is ready for any manual deployment or moving to the continuous deployment stage where deployments are automated.

Continuous Deployment

Continuous deployment is usually the final stage for deploying the new version of the application automatically. It requires the DevOps teams to set the criteria for triggering a deployment in advance. When those criteria have been fulfilled and validated, the ready-to-deploy artifacts from the previous CI stage will be deployed into the production environment. 

Deployment is always the most important part of the software development lifecycle as it is public-facing. Without a strictly designed CI, it is dangerous to integrate continuous deployment into your solution. Therefore, you can apply CI without CD, but you should not do it vice versa.

GitHub Actions

Introduction

GitHub Actions is a powerful automation tool integrated into GitHub, which allows developers to automate any workflows directly within their Git repositories hosted on GitHub. It is designed for CI/CD processes and streamlines them into pipelines. You can also set up other automation tasks related to your software development lifecycle or daily operations.

In GitHub Actions, a workflow defines everything you want to automate. For CI/CD, code merging, automated testing, and artifacts packaging are written as Jobs into one or multiple Workflows. The necessary steps for your software development lifecycle will be done automatically by setting up different triggers and runners.

By providing credentials and valid steps to the workflows, you can integrate any third-party software into your processes, like authenticating to AWS for infrastructure provisioning using Terraform, sending notifications to a Slack channel, or opening an issue on a Jira project.

Create Your First Workflows

GitHub Actions is enabled by default in any GitHub repositories. To run your workflow, you just need to create a workflow file in YAML format and place it in a specific location.

The following is an example of creating a simple workflow that prints the title of this article.

  1. Create a new or use an existing GitHub repository.
  2. Click the `Actions` button on the navigation bar.
Figure 1 Create Your First Workflows | `Action` button on the navigation bar.
Figure 1 Create Your First Workflows | `Action` button on the navigation bar.
  1. Click the `Configure` button of  `Simple workflow`
Figure 2 Create Your First Workflows | Configure a simple workflow.
Figure 2 Create Your First Workflows | Configure a simple workflow.
  1. You can see an editing panel for a new workflow file in YML format which will be committed under `.github/workflows` directory.
A screenshot of a computerDescription automatically generated
Figure 3 Create Your First Workflows | A new workflow file in YML format.
  1. Use the following code snippet to replace the default content, then commit to your branch.
# Name of the workflow
name: Simple Workflow by Tony
# Trigger of the workflow
on:  
  push:
# Jobs in the workflow
jobs:  
  build:    
    runs-on: ubuntu-latest    
    steps:      
      # Print the article title using `echo` command      
      - name: Print article title        
        run: echo 'CI/CD Pipelines with GitHub Actions'
  1. The workflow file has been committed to the designated directory.
Figure 4 Create Your First Workflows | Workflow file at a designated directory.
Figure 4 Create Your First Workflows | Workflow file at a designated directory.
  1. Go back to the Actions page and see your first workflow execution.
Figure 5 Create Your First Workflows | First workflow execution.
Figure 5 Create Your First Workflows | First workflow execution.
  1. Go to the first execution detail, and you will see an area that visualize your workflow.
Figure 6 Create Your First Workflows | Visualized workflow.
Figure 6 Create Your First Workflows | Visualized workflow.
  1. Go to the job detail, and you will see the article title has been printed.
Figure 7 Create Your First Workflows | Result.
Figure 7 Create Your First Workflows | Result.

As the workflow has been written to trigger by any Git push to the repository, you can commit changes to the workflow file to execute more steps and jobs.

Figure 8 Create Your First Workflows | More commits to the workflow file.
Figure 8 Create Your First Workflows | More commits to the workflow file.
Figure 9 Create Your First Workflows | Workflow execution per commit.
Figure 9 Create Your First Workflows | Workflow execution per commit.

Build a Docker Image

For CI/CD, building artifacts like Docker images is a common usage. There are native supports from GitHub Actions to build and push a Docker image. In this section, we will build a Docker image from a simple Dockerfile using GitHub Actions.

  1. Create a Dockerfile and commit it to the root directory. You can use the following example.
FROM node:18-alpineWORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
EXPOSE 3000
  1. Create a new workflow file with the following code snippet.
name: Build a Docker image
on:  
  push:
jobs:  
  build:    
    runs-on: ubuntu-latest    
    steps:      
      - name: Checkout the repostiory        
        uses: actions/checkout@v4      
      - name: Build the image        
        uses: docker build -t node-app:latest .      
      - name: List the built Docker image        
        run: docker image ls
  1. Commit the Dockerfile and the workflow file.
  2. Check the result under the Actions page.
Figure 10 Build a Docker Image | Successful execution.
Figure 10 Build a Docker Image | Successful execution.
Figure 11 Build a Docker Image | Job execution log.
Figure 11 Build a Docker Image | Job execution log.

Best Practices

While implementing CI/CD pipelines with GitHub Actions, there are some best practices to ensure reliability, maintainability, and security.

  • Reusable workflows
    Break down a complicated workflow into several smaller and reusable workflows. Using the `caller` and `called` workflow features to build pipelines for different repositories without duplication. It can also increase the maintainability. For example, a reusable workflow for running tests that can be used across multiple repositories.
  • Environment-specific configurations
    Use the `environment` feature to group variables and secrets for handling environment-specific configurations. It avoids hard-coding sensitive information into the workflow directly as they are centralized at the project or organization level on GitHub. Moreover, using it with reusable workflows helps to simply define pipelines for different environments by using the same set of workflow files.
  • Dependency management
    GitHub Actions supports a caching mechanism to save and restore dependencies from the cache. It can speed up the build times. For example, caching `node_modules` in a Node.js project to accelerate the frequent build during the CI stage.
  • Parallel builds
    Set up parallel builds to test multiple configurations simultaneously. It is good practice for teams to maintain applications in multiple versions of software. This is a common scenario that you have to run the same set of test cases on multiple versions of Python or Node.js concurrently.
  • Security and access control
    Allow least privilege principles for workflow permissions. Granting only necessary permission to GitHub Actions and its workflows. It reduces the risk of security breaches. For example, using different sets of AWS access key pairs with the least privileges on production and staging environments.
  • Error handling
    Integrate comprehensive testing, such as unit, integration, and end-to-end tests in your CI workflows. Generate reports using the artifacts feature to manage failure and provide readable feedback. For example, running tests against every commit to a feature branch can discover bugs and errors at an early stage.

Conclusion

CI/CD is a core practice in DevOps. It connects the gap between development and operation by automating processes. By streamlining the build and test stage, developers can focus on coding as the dependency builds and testing are automated. They don’t need to spend extra time finding potential errors manually. On the other hand, operation teams can focus on monitoring and incident response without doing manual deployment.

Under the rapid growth of DevOps, GitHub Actions is undeniably one of the best tools to build CI/CD pipelines for your DevOps solution. Its native support on GitHub saves time and resources when integrating third-party tools to retrieve code from your existing repositories. By leveraging GitHub Actions effectively, you can automate complex processes while maintaining high code quality and accelerating the software development lifecycle.