CI/CD with GitHub Actions
Automatically build, test and deploy every change with a GitHub Actions pipeline.
12 min read
You will learn
- Describe a CI pipeline for a .NET solution
- Write a GitHub Actions workflow that builds and tests
- Publish an artifact or container image and deploy it
- Protect the main branch with required checks
Automate the path to production
Manual releases are slow and error-prone ("did you run the tests?", "which config did you use?"). A CI/CD pipeline runs the same steps automatically on every change:
push / pull request
└─▶ restore → build → test → (analyze) ─── CI
└─▶ publish → deploy staging → smoke test → deploy production ─── CDGitHub Actions runs pipelines defined as YAML files in .github/workflows/. Azure DevOps Pipelines and GitLab CI work the same way with different syntax.
A CI workflow for .NET
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore -c Release
- name: Test
run: dotnet test --no-build -c Release --logger trx --collect:"XPlat Code Coverage"
- name: Check for vulnerable packages
run: dotnet list package --vulnerable --include-transitiveEvery push and pull request now gets a ✓ or ✗ in GitHub. Integration tests using Testcontainers work too — Docker is available on the Ubuntu runners.
Protect the main branch
In the repository settings, add branch protection for main: require pull requests, require the CI check to pass, and require a review. Now main is always buildable and tested — the precondition for deploying it at any time.
Continuous delivery
Add a deployment job that runs only on main, after CI succeeds:
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production # can require manual approval in GitHub
permissions:
id-token: write # OpenID Connect login to the cloud, no stored password
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with: { dotnet-version: 8.0.x }
- run: dotnet publish Shop.Api -c Release -o ./publish
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: azure/webapps-deploy@v3
with:
app-name: shop-api
package: ./publishFor containers, the job instead builds the image, pushes it to a registry (tagged with the commit SHA), and tells the platform to roll out the new tag.
Secrets in pipelines
- Store credentials as encrypted repository/environment secrets, referenced as
${{ secrets.NAME }}— never in the YAML itself. - Prefer OpenID Connect federation (as above): the cloud trusts GitHub's identity token, so there's no long-lived password to leak.
- Give workflows the minimum
permissionsthey need.
Database migrations in the pipeline
Schema changes must ship with the code that needs them. Common approaches: generate an idempotent SQL script (dotnet ef migrations script --idempotent) and apply it as a pipeline step, or use migration bundles (dotnet ef migrations bundle). Design migrations to be backwards compatible (add columns before using them, remove only after no code needs them) so old and new versions can run side by side during rollout.
Small and often
Teams that deploy small changes many times a day have fewer and smaller incidents than teams that ship big releases monthly. Automation is what makes frequent deployment safe.
Key takeaways
- CI builds and tests every push and pull request; CD deploys passing changes automatically.
- GitHub Actions workflows are YAML: triggers (
on), jobs, and steps using actions and shell commands. - Protect
mainwith required checks and reviews; keep credentials in secrets or use OIDC federation. - Ship database migrations with the code, designed to be backwards compatible.
Quiz
4 questions · 3 correct to pass1.When does a typical CI workflow run?
2.Where should deployment credentials (cloud keys, registry passwords) be stored for GitHub Actions?
3.What do branch protection rules with required status checks do?
4.Why deploy to a staging environment before production?
To complete this lesson
- ○ Pass the quiz (3/4 correct)