Skip to content

2025

CI & CD

This article is a summary of my understanding and use of Continuous Integration, Continuous Delivery and Continuous Deployment. There are several interpretations of these terms, some of which I'd argue are more correct than others. My interpretation is based on my experience and the processes I've followed and also articles by companies like AWS and by experts in the field like Dave Farley.

Continuous Integration

I always use a trunk-based approach, whether that be when building software, pipelines or Infrastructure as Code (IaC). I usually use short-lived feature branches rather than committing directly to the main branch, especially when working with others. I never use long-running branches, opting for feature toggles to manage the rollout of new features.

I then typically use pull requests to:

  • Verify the changes are the ones I intended
  • Run automated tests
  • Get feedback from others

Once the pull request is approved, I merge it into the main branch, usually using a squash commit. This triggers a build pipeline that will also run automated tests but it also builds packages, containers, etc... Once the build is successful, the deployment phase(s) of the pipeline begin.

Continuous Delivery

Every successful commit to main not only triggers the build process but also the deployment phase(s) of the pipeline, ultimately pushing code all the way to production.

Regardless of how many environments I have in place, the process tends to be the same for them all:

  • If environment is subject to change management, raise a change request
  • Deploy the change to the environment - if code:
    • Database changes first
    • Code changes second
  • Run automated tests (these may vary between environments)
  • If no automated tests exist, show a manual approval gate
  • Close the change as successful or failed as appropriate

If a deployment fails, sometimes excluding the development environment, then all changes are automatically reverted. This is to get the environment back to a known good state as quickly as possible.

Continuous Deployment

Continuous deployment is effectively the same as continuous deliver but with any manual gates removed. When building pipelines, I typically build them all to support continuous deployment but, when automated testing isn't available, revert to using a manual approval gate.

I'd say the biggest barrier to going to continuous deployment is the lack of automated testing or at least a lack of trust in your automated testing. If you can't trust your tests to catch issues, you can't trust your deployment to be successful. Once you have highly reliable and high confidence (and ideally high speed) automated testing in place, you can move to continuous deployment with confidence.

Delete a tag in ACR repository using CLI

If you have a tag in an ACR repository that you no longer need, you can delete it using the Azure CLI. It's a simple process and just requires remembering a few property names.

If you've not logged into Azure, do that first:

az login

Then, list the repositories in the registry to find the tag you want to delete:

az acr repository list --name acrname

Next list the tags for the repository you're interested in:

az acr repository show-tags --name acrname --repository your.repository

Once you've found the repository and tag you want to delete, you can delete it:

az acr repository delete --name acrname --image your.repository:1.2.3-alpha.456789

Obviously you'll need to replace acrname, your.repository and your.repository:1.2.3-alpha.456789 with your own values.

Output variables in PowerShell and GitHub Actions

When running a PowerShell script within a GitHub Actions workflow, you may wish to output variables from one step and access them in another.

Below is a simple example of outputting a variable in one step and accessing it in another:

name: Pass value from one step to another
on: 
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  PassTheParcel:
    runs-on: ubuntu-latest
    steps:
    - name: Determine some values
      id: calculate_values
      shell: pwsh
      run: |
        $ErrorActionPreference = "Stop" # not required but I always include it so errors don't get missed

        # Normally these would be calculated rather than just hard-coded strings
        $someValue = "Hello, World!"
        $anotherValue = "Goodbye, World!"

        # Outputting for diagnostic purposes - don't do this with sensitive data!
        Write-Host "Some value: $someValue"
        Write-Host "Another value: $anotherValue"

        "SOME_VALUE=$someValue" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
        "ANOTHER_VALUE=$anotherValue" | Out-File -FilePath $env:GITHUB_OUTPUT -Append

    - name: Use the values
      shell: pwsh
      env: 
        VALUE_TO_USE_1: ${{ steps.calculate_values.outputs.SOME_VALUE }}
        VALUE_TO_USE_2: ${{ steps.calculate_values.outputs.ANOTHER_VALUE }}
      run: |
        $ErrorActionPreference = "Stop" # not required but I always include it so errors don't get missed

        Write-Host "Values received were `"$($env:VALUE_TO_USE_1)`" and `"$($env:VALUE_TO_USE_2)`""

Whilst both examples use PowerShell, there is no requirement that both steps use the same shell.