STREAMLINE YOUR CI/CD PIPELINE WITH GITHUB ACTIONS
Tags: docker, cicd, githubactions, devops
Published: 2024-04-12 14:23:18

In the current dynamic software development landscape, security and efficiency are critical. Companies and developers work hard to swiftly roll out apps while making sure they are safe from security flaws. This is where the industry-leading containerization technology Docker comes into play. By wrapping up applications in containers, Docker makes the deployment process simpler. However, if done manually, managing these containers, creating them, checking for vulnerabilities, and pushing them safely can be difficult and time-consuming. For this reason, it's critical to automate these procedures via a Continuous Integration/Continuous Deployment (CI/CD) pipeline.

![Architectural Diagram of the CI/CD Pipeline Workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gsun030ewqt40ru4rv2x.png)

**The Role of CI/CD in Modern Software Development**

Software release process automation is the goal of Continuous Integration (CI) and Continuous Deployment (CD). Building software that is reliable, safe, and quickly deployable is the aim.

**Continuous Integration:**

CI involves merging all developers' working copies to a shared mainline several times a day. The main objectives of CI include:

- **Reducing bugs**: Automated testing in CI helps detect and fix bugs quickly, improves software quality, and reduces the time it takes to validate and release new software updates.
- **Improving software quality**: Continuous integration leads to significantly reduced assumptions as integration issues are detected and fixed continuously.

**Continuous Deployment:**

CD extends CI by automatically deploying all code changes to a testing and/or production environment after the build stage. This means that on top of the automated testing, automated release processes further streamline the development lifecycle. Benefits of CD include:

- **Faster time to market**: Accelerated release cycles ensure features reach production faster.
- **Higher release rates**: Frequent releases promote smaller, more manageable changes and less deployment risk.
- **Improved customer satisfaction**: Continuous delivery of features addresses user feedback more promptly and enhances the user experience.

## Implementing GitHub Actions for Docker Management

From the first code change to the last production deployment, GitHub Actions is a CI/CD tool that streamlines the software process. Building processes that automatically create, test, and launch Docker containers is a requirement of using GitHub Actions for Docker administration.

### GitHub Actions Workflow: The "Complete Docker Workflow"

An extensive dissection of the "**Complete Docker Workflow,” a** system for managing Docker deployments with GitHub Actions, is given in this section. There are multiple stages in this workflow, all designed to improve security and expedite procedures across the container management lifecycle.

#### Workflow Activation Triggers
- **Scheduled Runs**: Set to trigger daily at 16:21 UTC, this ensures regular updates and checks, keeping the application up to date with the latest base image vulnerabilities addressed.
```schedule:
    - cron: '21 16 * * *'
```
- **Push Events**: Activates on pushes to specific branches or tags (specified branch and semantic versioned tags). This ensures that all changes undergo rigorous testing and security checks before deployment.
```
push:
    branches:
      - branch-name
```
- **Pull Requests**: Targets pull requests to the **dev-2** branch, allowing for automated reviews and tests and ensuring that new code integrations meet quality standards before merging.
```
pull_request:
    branches:
      - branch-name
```
### Environment Configuration

Using environment variables and secrets for configurations like Docker registry credentials secures sensitive information and streamlines the setup process across multiple environments or projects. **docker.io** is specified as our REGISTRY in this instance.
```
env:
  REGISTRY: ${{ secrets.REGISTRY }}
  IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.IMAGE_NAME }}
```
### Initial Setup and Configurations

**Checkout Repository**
- Uses GitHub's actions/checkout@v3.5.2 to clone the repository, with a minimal fetch depth of 1 to speed up the checkout process.
```
- name: Checkout repository
        uses: actions/checkout@v3.5.2
        with:
          fetch-depth: 1
```
**Install Cosign**
- Implements sigstore/cosign-installer@v3.4.0 to install Cosign, which is used later to sign Docker images.
```
- name: Install Cosign
        uses: sigstore/cosign-installer@v3.4.0
```
**Set up QEMU**
- Employs docker/setup-qemu-action@v2.1.0 to configure QEMU, facilitating the emulation of different architectures which is essential for cross-platform Docker builds.
```
- name: Set up QEMU
        uses: docker/setup-qemu-action@v2.1.0
```
**Set up Docker Buildx**
- Engages docker/setup-buildx-action@v2.5.0 to set up Docker Buildx, enhancing the ability to perform multi-platform builds directly from a single command.
```
- name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2.5.0
```
**Log in to Docker Registry**
- Utilizes docker/login-action@v2.1.0 for logging into the Docker registry using credentials stored in GitHub secrets.
```
- name: Log in to Docker Registry
        uses: docker/login-action@v2.1.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
```
**Extract Docker Metadata**
- Activates docker/metadata-action@v4.4.0 to generate and format Docker image metadata, such as tags, from the environment variables.
```
- name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v4.4.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
```

### Building and Pushing Images

The procedure ensures interoperability across various hardware settings by supporting the development of images for many architectures using Docker Buildx and QEMU. Developers' work is made easier by automating the push to registries, freeing them up to concentrate on essential features rather than operational setups. Here, **linux/amd64** is the chosen OS. Please be aware that you can use multiple operating systems.
```
- name: Build and Push container images
        uses: docker/build-push-action@v4.0.0
        with:
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
```

### Security Scanning with Trivy

Preventing potential security risks before they arise in production requires integrating Trivy scans to evaluate image vulnerabilities. This scan is essential for keeping a secure deployment as it looks for vulnerabilities at the OS and library levels. If there are multiple tags for the image, you can duplicate the below step and specify each of the tags.
```
- name: Scan Docker image with Trivy (specifying a tag)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:tag
          format: 'table'
          severity: 'CRITICAL,HIGH'
          vuln-type: 'os,library'
```

### Digital Signing of Images with Cosign

By confirming the source of the images and signing them using Cosign, GitHub's OIDC integration adds an extra degree of security to ensure that only validated images are released.
```
- name: Sign the images with GitHub OIDC Token (Non-interactive)
        run: |
          IFS=',' read -ra ADDR <<< "${{ steps.meta.outputs.tags }}"
          for tag in "${ADDR[@]}"; do
            echo "Signing $tag"
            cosign sign --oidc-issuer=https://token.actions.githubusercontent.com --yes "$tag"
          done
        env:
          COSIGN_EXPERIMENTAL: "true"
```

### Complete CI/CD Pipeline
```
name: Complete Docker Workflow

on:
  schedule:
    - cron: '21 16 * * *'
  push:
    branches:
      - branch-name
    tags:
      - 'v*.*.*'
  pull_request:
    branches:
      - branch-name

env:
  REGISTRY: ${{ secrets.REGISTRY }}
  IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.IMAGE_NAME }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write # needed for signing the images with GitHub OIDC Token
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3.5.2
        with:
          fetch-depth: 1

      - name: Install Cosign
        uses: sigstore/cosign-installer@v3.4.0

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2.1.0

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2.5.0
      
      - name: Log in to Docker Registry
        uses: docker/login-action@v2.1.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v4.4.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and Push container images
        uses: docker/build-push-action@v4.0.0
        with:
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta.outputs.tags }}

      - name: Scan Docker image with Trivy (tag1)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:tag1
          format: 'table'
          severity: 'CRITICAL,HIGH'
          vuln-type: 'os,library'

      - name: Scan Docker image with Trivy (tag2)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:tag2
          format: 'table'
          severity: 'CRITICAL,HIGH'
          vuln-type: 'os,library'

      - name: Sign the images with GitHub OIDC Token (Non-interactive)
        run: |
          IFS=',' read -ra ADDR <<< "${{ steps.meta.outputs.tags }}"
          for tag in "${ADDR[@]}"; do
            echo "Signing $tag"
            cosign sign --oidc-issuer=https://token.actions.githubusercontent.com --yes "$tag"
          done
        env:
          COSIGN_EXPERIMENTAL: "true"
```

## Advantages over Traditional Methods

There are several benefits to automating these procedures over more conventional manual ones.

- **Reduced Human Error**: Automating repetitive tasks lowers the possibility of human error, including deployment script misconfiguration or omission of stages.
- **Consistency and Reliability**: Automation guarantees consistency and repeatability by ensuring that each step is carried out in the same way. This makes the process of developing, testing, and deploying software dependable and predictable.
- **Security**: By methodically identifying and addressing security concerns, automated vulnerability scans considerably lower the risk of releasing vulnerable code.

## Conclusion
This pipeline comprehensively covers all aspects of Docker image management, from build to security checks to deployment, ensuring high standards of automation and security using GitHub Actions. This setup not only automates the build and deployment process but also incorporates critical security practices like scanning and signing images, pivotal for maintaining the integrity and trustworthiness of software in a CI/CD environment.