exit lab
Hands-on labs
Intermediate

Set Up a GitHub Actions CI/CD Pipeline

Run tests on every pull request, and build + push a Docker image automatically on every merge to main.

1 hr

Prerequisites

  • A GitHub repository with a Spring Boot project
  • A container registry to push to (Docker Hub or GHCR)

Architecture

Two workflows, two triggers

Pull Requesttrigger
Run Testsmvn test
Merge to maintrigger
Build + Push Imagetagged by commit SHA

Build checklist

Steps

0 of 3 complete

Step 1

PR workflow — .github/workflows/test.yml

yaml
name: Test
on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
      - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: 'maven'
      - run: mvn test
Step 2

Deploy workflow — .github/workflows/deploy.yml

yaml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
      - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
        with:
          push: true
          tags: myorg/myapp:${{ github.sha }}
Step 3

Protect main so the test workflow is required

Repo Settings → Branches → Branch protection rule → require the Test workflow to pass before merging.

Expected result

What success looks like

Every pull request automatically runs the test suite and blocks merge on failure. Every merge to main automatically builds and pushes a new image tagged with the exact commit SHA that produced it.

Lessons learned

Tip

cache: 'maven' on setup-java cuts test-workflow time noticeably by reusing the dependency cache between runs instead of re-downloading the whole tree.

Tip

Tagging images by commit SHA (not just latest) means any deployed image can be traced back to the exact commit — essential when you need to know exactly what's running in production.

Tip

Pin third-party actions to reviewed full commit SHAs and retain a version comment for readability; use dependency automation to propose reviewed updates.

Tip

Splitting test and deploy into separate workflow files keeps PR feedback fast — a broken deploy step can't accidentally block or slow down the test run every contributor waits on.