Video Thumbnail for Lesson
14.1: Continuous Integration (GitHub Actions)

Continuous Integration (using GitHub Actions)

Automating container builds ensures that every commit can be deployed. The full example workflow lives in image-ci.yml.

Workflow triggers

on:
  push:
    branches: ["main"]
    tags: "[0-9]*.[0-9]*.[0-9]*"
    paths:
      - "06-demo-application/**/*"  # only rebuild when app code changes

Generate an image tag

The job generate-image-tag runs task generate-version-tag from the Taskfile to produce tags like 0.5.0-44-g51b089 using git describe.

- id: generate-image-tag
  working-directory: 14-cicd/github-actions
  run: |
    image_tag=$(task generate-version-tag)
    echo "image_tag=$image_tag" >> $GITHUB_OUTPUT

Build and push images

build-tag-push builds all services in parallel using a matrix strategy. QEMU and Buildx enable multi-architecture images and each service uses the same Task to build and push to Docker Hub.

strategy:
  matrix:
    path:
      - 06-demo-application/api-golang
      - 06-demo-application/api-node
      - 06-demo-application/client-react
      - 06-demo-application/load-generator-python
      - 06-demo-application/postgresql
steps:
  - uses: docker/setup-qemu-action@v3
  - uses: docker/setup-buildx-action@v3
  - uses: docker/login-action@v3
    with:
      username: ${{ secrets.DOCKERHUB_USERNAME }}
      password: ${{ secrets.DOCKERHUB_TOKEN }}
  - name: Build Image
    working-directory: ${{ matrix.path }}
    env:
      IMAGE_TAG: ${{ needs.generate-image-tag.outputs.image_tag }}
    run: |
      task build-container-image-multi-arch IMAGE_TAG=${IMAGE_TAG}

Update manifests and open a PR

After the images are pushed, the update-tags job replaces the old image tags in the Kluctl manifests. Staging tags are updated on every push, while production tags change only for release tags.

- name: Update Image Tags
  working-directory: 14-cicd/github-actions
  env:
    IMAGE_TAG: ${{ needs.generate-image-tag.outputs.image_tag }}
  run: |
    task update-staging-image-tags NEW_TAG=${IMAGE_TAG}
    if [[ $GITHUB_REF == refs/tags/*.*.* ]]; then
      task update-production-image-tags NEW_TAG=${IMAGE_TAG}
    fi

Finally a pull request is created with the updated manifests:

- name: Create Pull Request
  uses: peter-evans/create-pull-request@v6
  with:
    base: main
    token: ${{ secrets.DEVOPS_DIRECTIVE_KUBERNETES_COURSE_GITHUB_ACTION_PAT }}
    title: "Update image tags to (${{ needs.generate-image-tag.outputs.image_tag }})"

This process converts code pushes directly into fresh container images and a PR that updates deployment manifests. Once merged, the GitOps workflow will deploy the new versions.