CI Pipeline (GitHub Actions)

A minimal "build and test every PR" pipeline for a .NET solution — restore, build, test, nothing else. Small enough to actually read in full, and a natural base to extend once you want it to also publish a Docker image.

Build + test on every pull request

Scoped to pull_request against main only — it never runs on pushes directly to main, so the only way code lands there is through a PR that's already passed this check.

.github/workflows/ci-build-test.yml
name: CI + build only

on:
  pull_request:
    branches:
      - main

jobs:
  build-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore --configuration Release

      - name: Run unit tests
        run: dotnet test --no-build --configuration Release --verbosity normal

Each step deliberately reuses the previous one's output — --no-restore on build, --no-build on test — so nothing gets redone. If any step fails, the whole job fails and the PR shows a red ✗ instead of a merge button.

Extending it: build & push the Docker image on merge

A second workflow, triggered on push to main instead of on PR, builds the image from the Dockerfile and pushes it to GitHub Container Registry. Splitting this into its own file keeps "can this PR merge" and "deploy the result" as two independent concerns.

.github/workflows/cd-docker.yml
name: Build and push image

on:
  push:
    branches:
      - main

jobs:
  push-image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./YourApp.API/Dockerfile
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
Keep secrets (connection strings, JWT keys) out of the repo and out of workflow files — use Settings → Secrets and variables → Actions and reference them as ${{ secrets.NAME }}. secrets.GITHUB_TOKEN above is provided automatically by Actions; you never set it yourself.