Build CI/CD pipelines using artifacts with GitHub Actions

This guide will differ from other guides about GitHub Actions in that it will be focused on how to transfer the build artifacts & outputs across multiple steps.

If you know what you're doing, you can skip the introduction and go straight to the Building CI/CD pipelines with GitHub Actions guide.

What is a CI/CD pipeline?

CI/CD pipeline is a sequence of steps that are executed to build, test, and deploy your website. It stands for Continuous Integration/Continuous Deployment.

Typically CI/CD pipelines are built by DevOps professionals using variety of tools such as GitHub Action, AWS CodePipeline, Jenkins, and others.

CI/CD pipeline

The above figure shows a typical CI/CD pipeline but depending on use case there could be fewer or more steps.

CI/CD pipelines makes it easier for a developer to build, test, and deploy their website or app by automating the steps associated with the deployement, e.g., in the above workflow, as soon as the developer pushes their code to the repository, the pipeline starts executing the steps associated with the build, test, and deployement.

What is a GitHub Action?

GitHub Action is GitHub's native CI/CD workflow management tool. It uses YAML files to declaratively define the steps that needs to be executed.

Like other tools, GitHub Actions provides an extensive set of tools and options to build CI/CD pipelines.

Deploying a Static Website to firebase

CloudBytes/dev> is built on JAMStack architecture and uses GitHub Actions to deploy the static website generated by Pelican to Firebase Hosting.

The simplest way to do that is by using the below GitHub Workflow.

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches:
      - main

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: rehanhaider/pelican-build-action@v0.1.10
        env:
          PELICAN_CONFIG_FILE: app/publishconf.py
          PELICAN_CONTENT_FOLDER: app/content
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: "${{ secrets.GITHUB_TOKEN }}"
          firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_CLOUDBYTES_PROD }}"
          channelId: live
          projectId: cloudbytes-prod

The above workflow is execetud on the push event of the main branch. It has only one job that does both build and deployment of the website. However, this will present a problem if we wanted to introduce additional jobs in the pipeline because each job runs on a separate container. So if we wanted to split build and deployment into two jobs, we would need to repeat the build process twice because the first job would build the website but the second job will not have access to the output.

So solve this, you need to use a GitHub Actions feature called GitHub Actions Artifacts to transfer the build artifacts from one job to another.

Using GitHub Artifacts

GitHub Artifacts can be used to transfer the build outputs and artifacts between two jobs. To do that you need to 1. Upload you build artifacts that includes the "output" and configuration files 2. Then add the build step as a dependency on the next job 3. And finally download the artifact

The above workflow can be modified to the following:

name: Deployment

on:
  push:
    branches:
      - main

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Build Pelican Website
        uses: rehanhaider/pelican-build-action@v0.1.10
        env:
          PELICAN_CONFIG_FILE: app/publishconf.py
          PELICAN_CONTENT_FOLDER: app/content
      - name: Upload the build output
        uses: actions/upload-artifact@v2
        with:
          name: build-output
          path: |
            output/
            .firebaserc
            firebase.json
          retention-days: 1
  deploy:
    name: Deploy
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - name: Download the build output
        uses: actions/download-artifact@v2
        with:
          name: build-output
      - name: Deploy to Firebase
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: "${{ secrets.GITHUB_TOKEN }}"
          firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_CLOUDBYTES_PROD }}"
          channelId: live
          projectId: cloudbytes-prod

In the above example, we used upload-artifact@v2 action, named it build-output and added the files & directories under path. For pelican we needed the output folder, in this case output, and the two Firebase configuration files.

Then during the deployment step, we used download-artifact@v2 action to download the build-output and subsequently deployed the result to Firebase using FirebaseExtended/action-hosting-deploy@v0 action.

Once the above workflow is defined, any code that is pushed to the main branch will trigger a GitHub Action and the workflow will look something like this:

Github Action Output

Benefits of using GitHub Action artifacts

The biggest benefit of using GitHub Actions artifacts is that it allows you to transfer the build artifacts from one job to another. This is useful when you want to split the piepline into several steps and add or remove steps in future. E.g. in the above workflow, we can add a new job called test that runs the tests without having to change the existing steps in the workflow.

Need Help? Open a discussion thread on GitHub.