CodingNic

CI/CD Fundamentals

Exercises

CI/CD Fundamentals 25 min read

Exercises

Objectives

By the end of this lesson, you should be able to:

  • Test an application against multiple Node.js versions in a single workflow
  • Publish a Docker image with more than one tag
  • Explain the complete path from a pushed commit to a published image

⚠️ A note on verification: as throughout this module, the YAML below was validated with a real parser for correctness, its execution reflects GitHub Actions’ stable, current, documented behavior. This is a genuinely hands-on exercise, push it to a real GitHub repository to see it run.

Exercise: A Matrix Build with Multiple Tags

a) Test against more than one Node.js version, using a build matrix:

text
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: ['18', '20']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

strategy: matrix runs the entire job once per value, here twice, once against Node 18, once against Node 20, ${{ matrix.node-version }} substitutes the current value into setup-node, catching a bug that only appears on one specific Node.js version, something a single-version workflow would miss entirely.

b) Publish an image with two tags, both the commit SHA and latest:

text
build-and-push:
  needs: test
  if: github.ref == 'refs/heads/main'
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    - uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: |
          ghcr.io/${{ github.repository }}:${{ github.sha }}
          ghcr.io/${{ github.repository }}:latest

Two tags, on the exact same image, latest for anything that just wants the newest build (a staging environment, tracking main automatically), the commit SHA for anything that needs to reference a specific, exact, traceable version (a production deployment that should never move without a deliberate action).

c) Trace the complete path. Write out, in your own words, every step between a developer running git push, and a new Docker image existing in the registry, naming which job handles each step, and what would stop that path partway through if a test failed.

d) Extend it. Add a linting step (run: npm run lint, assuming a lint script exists) to the test job, before the test step, and explain why catching a lint failure in CI is useful even on a project where every developer already has a linter configured in their editor.

e) Reflect. Explain, in one or two sentences, why running the matrix build (two Node.js versions) makes the most sense in the test job specifically, and why duplicating it in build-and-push would be wasteful.

Recap

This module replaced “run tests and build a Docker image by hand, when someone remembers to” with a complete, automated pipeline: a workflow triggered on every push and pull request, tests running across multiple Node.js versions, and a Docker image built and published, tagged both by commit and as latest, but only once tests have actually passed, and only for code merged to main. Every piece from Course 3 and this course’s own Modules 2 and 3 now runs automatically, continuously, not just when someone remembers to run it.

Next module: deploying that published image to a real, live cloud platform.