Course topics

By WebNest Studio

Spring Boot Tutorial

CI/CD for Spring Boot with GitHub Actions

Continuous integration means every push is automatically built and tested; continuous delivery means every passing build produces a deployable artifact — and, ideally, is deployed automatically. For a Spring Boot team, a good pipeline catches broken tests, vulnerable dependencies and failed migrations minutes after a push instead of days later in production.

This lesson builds a complete GitHub Actions pipeline for a Spring Boot project: build and test with Maven caching and Testcontainers, publish test reports, scan dependencies, build and push a container image, and deploy to Kubernetes with an approval step for production.

Pipeline Stages

A typical pipeline runs: build and unit tests on every push and pull request; integration tests with Testcontainers (GitHub-hosted Linux runners include Docker); quality and security checks — dependency vulnerability scanning, static analysis, code coverage; package a container image tagged with the commit SHA; and deploy to staging automatically and to production after approval.

Speed Matters

A slow pipeline gets ignored. Cache the Maven or Gradle repository (actions/setup-java has built-in caching), run independent jobs in parallel, reuse the Spring test context across tests, and share Testcontainers containers. Aim for feedback on pull requests within 10 minutes.

Secrets and Environments

Store registry tokens, cloud credentials and kubeconfig in GitHub secrets, never in the repository. Use GitHub environments ("staging", "production") with protection rules such as required reviewers, so production deployments need an explicit approval. Prefer short-lived OIDC credentials for cloud providers over long-lived keys.

Examples

Build and test on every push and pull request

Java
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'
          cache: maven

      - name: Build and test (unit + Testcontainers integration tests)
        run: ./mvnw -B verify

      - name: Publish test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-reports
          path: target/surefire-reports/
Output
✓ build  (6m 12s)
  Tests run: 412, Failures: 0, Errors: 0, Skipped: 3
  Containers started: postgres:17, redis:8, apache/kafka:4.1.0

Security scanning and building/pushing the image

Java
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: '21', cache: maven }
      - name: Dependency vulnerability scan
        run: ./mvnw -B org.owasp:dependency-check-maven:check -DfailBuildOnCVSS=7

  image:
    needs: [build, security]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: '21', cache: maven }
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build image with buildpacks and push
        run: |
          IMAGE=ghcr.io/${{ github.repository }}:${{ github.sha }}
          ./mvnw -B -DskipTests spring-boot:build-image -Dspring-boot.build-image.imageName=$IMAGE
          docker push $IMAGE
Output
✓ security (3m 40s) — no vulnerabilities with CVSS >= 7
✓ image    (4m 05s) — pushed ghcr.io/webnest/shop:3f9c2e1

Deploying to staging automatically and production with approval

Java
  deploy-staging:
    needs: image
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: azure/setup-kubectl@v4
      - name: Deploy
        env:
          KUBECONFIG_DATA: ${{ secrets.STAGING_KUBECONFIG }}
        run: |
          echo "$KUBECONFIG_DATA" | base64 -d > kubeconfig
          kubectl --kubeconfig kubeconfig set image deployment/webnest-shop app=ghcr.io/${{ github.repository }}:${{ github.sha }}
          kubectl --kubeconfig kubeconfig rollout status deployment/webnest-shop --timeout=5m

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production          # requires reviewer approval in repository settings
    steps:
      - uses: azure/setup-kubectl@v4
      - name: Deploy
        env:
          KUBECONFIG_DATA: ${{ secrets.PROD_KUBECONFIG }}
        run: |
          echo "$KUBECONFIG_DATA" | base64 -d > kubeconfig
          kubectl --kubeconfig kubeconfig set image deployment/webnest-shop app=ghcr.io/${{ github.repository }}:${{ github.sha }}
          kubectl --kubeconfig kubeconfig rollout status deployment/webnest-shop --timeout=5m
Output
✓ deploy-staging     (1m 20s)
⏸ deploy-production  waiting for approval from @webnest/leads
✓ deploy-production  (1m 31s) — approved by vansh

Common Mistakes

  • Skipping integration tests in CI because "Docker is complicated" — GitHub-hosted runners support Testcontainers.
  • Tagging images only with latest, making it impossible to know which commit is running.
  • Storing credentials in workflow files instead of GitHub secrets.
  • Deploying to production automatically with no approval or smoke test.
  • Letting the pipeline grow to 40+ minutes, so developers stop waiting for it.

Key Points to Remember

  • Build and test every push and pull request; include Testcontainers integration tests.
  • Cache dependencies and parallelise jobs for fast feedback.
  • Scan dependencies and fail on serious vulnerabilities.
  • Build images tagged with the commit SHA and push to a registry.
  • Deploy to staging automatically and to production through a protected environment.

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.