The DevOps Cycle¶
The DevOps cycle is a set of practices that combines software development (Dev) and IT operations (Ops). The goal is to shorten the software development life cycle (SDLC) and provide continuous delivery with high software quality.
The 8 phases of the DevOps cycle are:
Plan
Code
Build
Test
Release
Deploy
Operate
Monitor
To implement DevOps, development teams apply different best practices. An important one is CI/CD, which stands for continuous integration and continuous delivery or deployment.
Continuous Integration (CI)¶
Continuous Integration encourages developers to integrate their changes into a main branch frequently. Each push is automatically built and tested, reducing integration issues and allowing teams to develop cohesive software more rapidly.
Continuous Delivery and Deployment¶
Continuous Delivery extends CI by ensuring that your software can be released to your customers at any moment. It involves automatically deploying all changes to a testing or staging environment after the build stage.
Continuous Deployment takes this a step further by automatically deploying every change that passes through the pipeline to production, eliminating manual steps in the deployment process.
How does GitHub Actions CI/CD work?¶
GitHub Actions is the CI/CD platform built into GitHub. It lets us automate tasks such as building, testing, and deploying our code directly from our repository, without relying on any external service.
The core building block is the workflow: a YAML file stored under .github/workflows/ that describes what should happen and when. Each workflow is triggered by an event, such as a push to a branch, a pull request, a release, or a scheduled time. When the event occurs, GitHub starts the workflow automatically.
A workflow is organized into one or more jobs, and each job is a sequence of steps that run commands or invoke reusable actions from the GitHub Marketplace. By default, jobs run in parallel, but we can declare dependencies between them with the needs keyword to enforce an order, for example: build → test → deploy.
Every job runs on a runner: a fresh virtual machine (Ubuntu, Windows, or macOS) provisioned by GitHub for that job. Because the environment is created from scratch each time, jobs are isolated and reproducible. The flip side is that nothing persists between jobs by default; if we need to share files, we have to use artifacts or a cache.
To make our scripts aware of their context, GitHub Actions exposes a set of predefined environment variables and expression contexts (such as ${{ github.actor }}, ${{ github.ref_name }}, or $GITHUB_WORKSPACE) that describe the repository, the event, and the runner. We can use these values to log information, parameterize commands, or write conditional logic, such as running a deployment step only when the workflow is triggered by a push to main.
Getting Started with GitHub Actions¶
To get started with GitHub Actions, we will now create our first workflow in a new repository. We will use the GitHub web interface to create a new repository named first-pipeline that will only contain a README file, and then we will define a simple workflow in a .github/workflows/ci.yml file.
Let’s now add a workflow file to the repository. This file will define a workflow with three jobs. To do this, click on “Add file” > “Create new file” in the repository, name the file .github/workflows/ci.yml, and paste the following content into the file. Then, commit the changes.
# filepath: .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build the project
run: |
echo "Building the project..."
echo "Hello, ${{ github.actor }}!"
test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@main
- name: Run tests
run: echo "Running tests..."
deploy:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@main
- name: Deploy to production
run: |
echo "Deploying to production..."
echo "This job deploys something from the ${{ github.ref_name }} branch."Initialized project `first-pipeline` at `/home/runner/work/msdp-book/home/ch51/first-pipeline`
[main (root-commit) 7b8ecca] Add CI workflow
1 file changed, 35 insertions(+)
create mode 100644 .github/workflows/ci.yml
Enumerating objects: 5, done.
Counting objects: 20% (1/5)
Counting objects: 40% (2/5)
Counting objects: 60% (3/5)
Counting objects: 80% (4/5)
Counting objects: 100% (5/5)
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 50% (1/2)
Compressing objects: 100% (2/2)
Compressing objects: 100% (2/2), done.
Writing objects: 20% (1/5)
Writing objects: 40% (2/5)
Writing objects: 60% (3/5)
Writing objects: 80% (4/5)
Writing objects: 100% (5/5)
Writing objects: 100% (5/5), 597 bytes | 597.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To https://github.com/mcallara/first-pipeline.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
To inspect the workflow, click on the “Actions” tab in the repository. You will see the workflow running with the three jobs. You can inspect the logs of each job by clicking on the job name.
To gain a better understanding of what is going on in each job, let’s add some commands to the steps that will show us more information about the environment where the job is executed. For example, we can add commands to show the list of files in the current directory, the output of the git config --list command, and the current working directory. We can also add a command to create a new file in the current directory and then show the list of files again. To do this, edit the .github/workflows/ci.yml file and add the following content:
# filepath: .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build the project
run: |
echo "Building the project..."
echo "Hello, ${{ github.actor }}!"
git config --list
git log --oneline
git status
pwd
ls -a
touch test-file.txt
ls -a
test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
echo "Running tests..."
git config --list
git log --oneline
git status
pwd
ls -a
touch test-file2.txt
ls -a
deploy:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Deploying to production..."
echo "This job deploys something from the ${{ github.ref_name }} branch."
git config --list
git log --oneline
git status
pwd
ls -a
echo $GITHUB_REPOSITORY
echo $GITHUB_WORKSPACE
echo $GITHUB_REF_NAME