What is CI/CD? (And Why You Need It)
44sExplains a fundamental concept that many developers are curious about, with a clear distinction between CI and CD that sparks interest.
▶ Play Clip"Delivers a solid, practical walkthrough of a free CI/CD pipeline, though the 'for free' promise is slightly oversold given AWS costs."
This video explains how to set up a CI/CD pipeline for free using GitHub Actions, covering both continuous integration (running tests) and continuous delivery (building and pushing Docker images). The presenter demonstrates with a .NET Core API that includes unit and integration tests, and shows how to push the built image to AWS ECR.
CI (continuous integration) automatically runs unit and integration tests for each build, often on pull requests, to prevent broken code from merging. CD (continuous delivery) builds and packages the application, while continuous deployment also deploys to environments. Most companies prefer manual approval for production.
The example project is a simple .NET Core API that writes to a MySQL database, with unit and integration tests. The goal is to have GitHub Actions build a Docker image and push it to AWS ECR.
The API has three layers: API layer (controllers, validation), service layer (business logic), and persistence layer (database). An SDK using Refit is used for integration tests to call the API.
A multi-stage Dockerfile is used: first stage uses the SDK image to restore and build, second stage uses the runtime image. Separating restore from build allows Docker to cache dependencies, speeding up builds.
Docker Compose defines a MySQL 8 database service and the API service, mapping port 5275 to 5200 to avoid clashes. This allows running the database and API locally for testing.
GitHub Actions offers free minutes for Linux runners on public repositories, and a limited amount for private repositories. Windows runners cost 2x minutes, macOS 10x. This example uses Linux.
The workflow is named 'build test and push', triggers on push, and has a single job 'build' running on ubuntu-latest. Steps include checkout, fetching tags, installing GitVersion, setting up .NET 7, restoring, building, running unit tests, spinning up Docker Compose, running integration tests, and pushing to ECR.
Unit tests are run with 'dotnet test' filtered by category=unit, using Xunit traits. Test results are saved to a test results folder using the VSTest logger.
Integration tests require a running database, so Docker Compose is used to start containers. A 30-second sleep ensures the API is ready. Tests are run with category=integration and an environment variable for the API URL.
The Test Reporter action from the Marketplace provides a nice UI showing test results (pass/fail/skipped) and works with .NET, Dart, Flutter, Java, and JavaScript.
The Docker image is pushed to AWS ECR using GitHub Secrets for credentials. The step only runs on the main branch to avoid pushing from feature branches.
GitHub Actions provides a free, integrated CI/CD solution for GitHub-hosted projects, enabling automated testing and container image delivery. The video demonstrates a complete pipeline from code push to Docker image upload, with test reporting and branch-based deployment control.
What does CI stand for and what does it do?
Continuous Integration: automatically runs unit and integration tests for each build, often on pull requests, to prevent broken code from merging.
00:15
What is the difference between continuous delivery and continuous deployment?
Continuous delivery builds and packages the application but does not deploy it; continuous deployment also covers the deployment to environments.
01:12
Why does the presenter recommend using the Docker image for testing?
Because if you plan to deploy your application using Docker, using the same image for testing ensures consistency and simplifies the pipeline.
03:56
What is the purpose of separating 'dotnet restore' in the Dockerfile?
To leverage Docker's layer caching: if project files don't change, restore won't re-run, speeding up builds.
04:22
How many free minutes do private repositories get on GitHub Actions?
2,000 minutes per month for private repositories (Linux runners).
06:25
What is the cost multiplier for Windows runners compared to Linux?
Windows runners use 2x the minutes of Linux runners.
06:39
Why is 'git fetch --unshallow' needed in the workflow?
GitVersion requires the full git history including tags to compute semantic versions.
07:55
How does the presenter filter unit tests in the workflow?
Using 'dotnet test --filter Category=unit' with Xunit traits set to category=unit.
08:35
What is the purpose of the 30-second sleep before integration tests?
To give the Docker containers time to start up so the API is ready to accept requests.
09:56
Which action is used to publish test results in GitHub Actions?
The Test Reporter action from the GitHub Actions Marketplace.
10:23
How are AWS credentials stored securely in GitHub Actions?
Using GitHub Secrets, which are encrypted and not visible in build logs.
11:16
CI prevents broken code merging
Explains the core value of continuous integration as a quality gate.
00:15CD vs. continuous deployment distinction
Clarifies a common confusion between delivery and deployment.
01:12Docker layer caching for restore
A practical optimization that speeds up CI builds significantly.
04:22GitHub Actions free tier details
Provides concrete numbers on free minutes, helping users estimate costs.
06:25Branch-based deployment control
Shows how to restrict CD to main branch, a common best practice.
11:01[00:01] so in today's video we're going to look at cicd pipelines and in particular how we can create them for free using GitHub actions CI CD is not something I've going to quickly explain what it is and why we use it so CI stands for
[00:15] continuous integration and it's the process of automatically running all of your unit tests and integration tests for each of your builds now you don't integration tests running on every single commit it can just be whenever
[00:29] you create a pull request your test run automatically and at least this way if the tests fail you can stop that code being merged into develop or Master you your unit tests on your machine before you raise your pull request but having
[00:42] server is a good quality control to have in place and it also prevents the running on your machine and not anyone else's in the past I've used tools such as teamcity and Jenkins to run my CI pipelines but more and more teams are
[00:58] already on GitHub then it's a bit of a no-brainer to run your tests on there as well and it will also save money on licensing and server costs now CD can continuous deployment they're both quite
[01:12] continuous delivery is really about building and packaging up your on the server but it doesn't actually include the deployment of your application continuous deployment on the other hand also covers the deployment of
[01:27] environment most companies don't like having applications going straight to production without any manual intervention it's quite typical to have the dev and QA environments but when it comes to production generally you want
[01:41] some manual controls in place in the past I've always used octopus deploy to different environments but it's actually possible to do that in GitHub actions as functionality of GitHub actions I've created a very simple.net core API that
[01:57] writes to a mySQL database this project include unit and integration tests and those as well as show off the results in a nice format for the continuous to have GitHub actions build out a Docker image and have it push it to AWS
[02:12] ECR for me I'm not going to cover how to do any of those manual approval a future video all the code that you're going to see in this video is available to my paid newsletter subscribers my newsletter is otherwise free at the
[02:25] channel then that's the best way to do it I'll leave a link in the description so you can check it out so the dotnet core API that I've put together for this project is a very simple library application so think something similar
[02:37] to Goodreads so this application is really made up of three layers we have our API layout which has all the controllers all the endpoints and handles things like validation we then have the service layer that handles all
[02:49] our persistence layout which is saving everything to the database I also have an SDK which I've written using a great.net core package called refit and this is what I'm then using in my integration test to be able to call my
[03:02] API so I've got some endpoints for adding authors to the database as well as getting them either singularly or as a paged result I then have another controller that lets you add books as well as retrieve them in a similar way
[03:14] got unit tests I haven't done a complete set but there's unit tests for both the service layer and then for the that show that you can create an author and retrieve it as well as creating a
[03:29] integration tests aren't using any of the API code they're just relying on the SDK so in order to be able to run the integration tests in GitHub actions we the database and the way we do this is with Docker so because I've built my
[03:43] application with net core I've put together a multi-stage Docker file to be able to build and run my application now there's probably other ways depending on application and GitHub actions but I'd recommend if you're going to be
[03:56] deploying your application using Docker anyway that you use that Docker image for testing your application so there's a few things that make up a net or Docker file we have first we're going to use the SDK image from Microsoft so this
[04:08] application we're then going to copy across all of our project files and this against them now the reason that we separate this instead of just copying across everything is because.net restore is quite a lengthy process you've got to
[04:22] download all the new get packages and by separating it out we can basically have in Docker so provided you don't change any of these project files by adding in any additional dependencies it means that when you run.net restore it
[04:36] run it again even if you change the other files in your application once.net restore is done we then copy across all the other files and then run.net publish in release mode to be able to build our application now of course we don't want
[04:49] part of our Docker image because it's going to make our Docker image quite large we're then going to get the.net runtime image and we're going to copy built in the other container and you can see here I'm exposing Port 5275 which
[05:03] we're then going to use later on to call our API so as I mentioned earlier to be need a database running in the back end so I'm using MySQL for this and I'm just using the standard MySQL image so you can see here in my Docker compose file I
[05:17] have a database service set up using MySQL 8 and I have some very basic and not very secure passwords set up here just for use for testing and then I'll have our API set up and you can see here
[05:30] files in the same folder and then we're tagging this image with GitHub actionsdemo.api and then version and I'll cover where that version is coming from in a bit so you can see here I've got the port set to 5275 by then mapping
[05:45] that 5200 in the docker compose file and this is so you can run the docker compose file with the database and not worry about any port clashes when you run the API locally on your machine so if we run Docker compose up we can see
[05:57] this is going to spin up our mySQL database as well as our API as well we be able to call IPI and make sure that everything works for the paid project I've also included this Postman collection as part of it so you can
[06:11] easily test out the API so that's how this API works next we're going to have see how we can put everything together so we can run our tests in GitHub so GitHub actions does have a very generous free tier you can run the standard Linux
[06:25] Runners for free if you've got a public repository for private repositories you you can use for free this is only for the Linux Runners if you opt for the windows Runners then you're going to use twice as many minutes and if you opt for
[06:39] use 10 times the amount so you're going to see the amount of three minutes you have go down pretty quickly so for this example I can use Linux as.net core as on what you're using so the first thing we need to set up is a name so I've just
[06:53] called this build test and push we then specify when we want this to run so just for this example I'm doing on push so every time I push something to my repository it's going to run this test but you could set it up to just do it on
[07:05] pull request or on merge for example and you can even specify particular branches that you want it to run on now we need to set up our jobs for this example I'm just setting up one job called build so you can create multiple jobs in GitHub
[07:17] actions but it gets a bit complicated you can't just use variables and data that you've had in one job to be used into the next job you have to use the artifacts and upload them and download them between each of the jobs again I'll
[07:29] but for now it's easier just to keep everything in one job next we specify where our job runs so this case I'm just using Ubuntu latest again this is the cheapest option if you're using GitHub actions so first we need to check out
[07:42] our code now GitHub obviously comes with a standard action to be able to do this part of this setup I want to get the semantic version of my application to be able to tag my Docker image and to do that I'm using Git Version but I found
[07:55] Git Version doesn't actually work unless you go and grab the tags using git fetch fetch an unshallow copy of my code I can then install Git Version and then run Git Version execute to be able to get the semantic version of my code next
[08:10] we're going to set up.net as I'm using.net and here we specify the version so I'm using.net7 we then need to run the same commands that we would on our machine so in this case we run.net restore to build our
[08:23] dependencies and net build to build our project so now we've got our.net code built on the GitHub Runner so next I'm going to run my unit tests so I'm running.net test and I've got a filter here with a category equals unit now I
[08:35] can do this because I'm using X unit and I've set up a trait to specify the category so when I run my unit tests this is going to produce a test results file and this is because I've got this vs test logger set up in my project now
[08:48] I've set this up so it will put all the test results into a test results folder inside the test project and then later I'm going to use the test reporter to be able to show these test results in GitHub so I I have to create a test
[09:01] results folder in my root project and copy across the tests results now the reason I'm doing this is because when I come to run my integration tests it's going to overwrite the unit test results for some reason when you use a filter it
[09:13] produces the test result file which is a bit annoying so now that we've run all integration tests now to be able to do this we need to spin up a Docker container and we can do that using our Docker compose file now you can see here
[09:27] specifying that we should always build our project and I've got the minus D's can see here I'm specifying a version earlier in our Docker compose file and this is going to get the nuget version
[09:41] and this is going to make sure that when we build our application in Docker with the correct version now in The Next Step we're just going to sleep for 30 do this you could call the API to see whether it's up and running but this is
[09:56] reason we have to do this because if we jump on onto running our integration containers might not be up yet and it will just fail now for our integration tests we're just running.net test again because they're just X unit tests and
[10:10] this time we're filtering by category integration and here I'm specifying the that it knows to call the API that's once that's run we're going to copy across the integration test results into
[10:23] our test results folder next I'm using this test reporter action from the GitHub actions Marketplace this gives you a nice view of all of the tests from your application and whether they pass failed or been skipped and how long each
[10:35] about this test reporter GitHub action is it also works with lots of different languages it works obviously with net we've got X unit and unit and Ms test but also works with dart flutter Java and JavaScript so if you're using any of
[10:48] those you can get a nice report from your test results and you can see here we've got if success or failure which basically just means this is always integration tests passed or failed now for the continuous delivery part of this
[11:01] workflow I'm going to push the docker image that we previously created up to AWS ECR now I've already gone ahead and created an AWS ECR repository and I've key and a secret key now to make sure that these Keys can't be seen I'm using
[11:16] the GitHub secrets that stores them encrypted in your repository and then they don't appear when you build now I've created another if statement here which means that this step is only going to run if you're running on the main
[11:29] branch if we run it on develop or on a pull request Branch it's not going to push anything up to AWS ECR if we run all this on GitHub actions you can see it all goes through and passes we've then got a nice test report that shows
[11:41] our unit tests on our integration test results and finally if we look on AWS you can see here that the image is uploaded successfully now if you want to project if you become a paid subscriber of my newsletter you'll get complete
[11:54] to all of my future videos and I'm also planning on releasing a few courses in generous discounts for all of my paid subscribers thank you for watching this video I hope it's been helpful and I'll see you in the next video
⚡ Saved you 0h 12m reading this? Transcribe any YouTube video for free — no signup needed.