What Is CI/CD? Your First GitHub Actions Workflow
What continuous integration, delivery and deployment mean, how GitHub Actions runs a workflow, and how to push your first workflow and read its logs.
Software breaks in boring ways. Someone forgets to run the tests before a merge, two changes that each work alone break when they meet, or a release depends on the one person who remembers the manual steps. CI/CD replaces those memories with automation that runs on every change.
In this part you learn what continuous integration, continuous delivery and continuous deployment mean, how GitHub Actions turns an event such as a push into a run on a fresh runner, and you push your first workflow and read its logs.
Prerequisites
- A GitHub account and a new repository with only a README in it. This guide calls it
cheatsheet; the companion repository isThe-DevOps-Daily/guide-cicd-demo-2026-10-24. - Git and the GitHub CLI on your computer, logged in with
gh auth login.
CI, Continuous Delivery and Continuous Deployment
Continuous integration (CI) means that everyone merges small changes into the main branch often, at least daily, and that an automated build and test run checks every change before and after it is merged. Problems show up while the change is small and fresh in your mind, not weeks later in a big merge.
Continuous delivery adds the release side: every change that passes CI produces something you can deploy, and the deployment itself is a script. A person still decides when to release, often with one click or an approval.
Continuous deployment removes that last manual decision: every change that passes the pipeline goes to production automatically. The safety comes from the tests, small changes and fast rollback, not from a person watching.
| Practice | What is automated | Who decides to release |
|---|---|---|
| Continuous integration | Build and tests on every change | Nobody releases yet |
| Continuous delivery | Build, tests and the deployment script | A person approves |
| Continuous deployment | Everything, up to production | The pipeline |
This guide builds continuous delivery: every change on main is deployed automatically, but only after a person approves the deployment (Part 6). Removing the approval later turns it into continuous deployment.
How GitHub Actions Works
GitHub Actions has five building blocks:
- An event is something that happens in or to the repository: a push, a pull request, a schedule, a click on "Run workflow".
- A workflow is a YAML file in
.github/workflows/. Itson:key lists the events that start it. - A workflow has one or more jobs. Each job runs on its own runner. A GitHub-hosted runner is a fresh machine that GitHub creates for that job and deletes afterwards: a virtual machine, or a container for the small
ubuntu-slimrunner. Self-hosted runners are machines you manage yourself. Jobs run in parallel unless oneneedsanother. - A job is a list of steps that run in order on the same runner. A step either runs shell commands (
run:) or uses an action (uses:), a reusable piece of automation such asactions/checkout, which downloads your code. - Each execution of a workflow is a run, with logs for every step.
Write the First Workflow
Clone your new repository and create the file .github/workflows/hello.yml. GitHub only looks for workflows in that directory, and the file name is up to you.
name: Hello
on:
push:
workflow_dispatch:
permissions:
contents: read
jobs:
say-hello:
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Say hello
run: echo "Hello from GitHub Actions"
- name: Show where this job runs
run: |
echo "Event: $GITHUB_EVENT_NAME"
echo "Branch: $GITHUB_REF_NAME"
echo "Commit: $GITHUB_SHA"
echo "Runner: $RUNNER_OS $RUNNER_ARCH, $ImageOS $ImageVersion"
ls -A
Line by line:
nameis what the Actions tab shows.onlists the triggers: everypushto any branch, andworkflow_dispatch, which adds a "Run workflow" button (Part 2 adds inputs to it).permissionssets what the automaticGITHUB_TOKENof this run may do.contents: readlets it read the code and nothing else. Set it in every workflow; Part 9 explains why.jobsholds one job,say-hello.runs-on: ubuntu-latestasks for a GitHub-hosted Linux runner.- The first step uses the
actions/checkoutaction at major version 7. The runner starts empty, and this step clones your repository into it.persist-credentials: falsestops it from leaving the token configured for latergitcommands, which this job does not need. - The other two steps run shell commands. On Linux runners,
run:uses Bash. GitHub sets environment variables such asGITHUB_SHAandRUNNER_OSin every job, and the runner image setsImageOSandImageVersion.
This guide references actions by major version tag, such as actions/checkout@v7, to keep the examples short. A tag can be moved to different code, so production workflows should pin each action to a full commit SHA. Part 9 shows how, and the companion repository is pinned that way.
Push It and Watch the Run
Commit the file and push it. The push is the event that starts the workflow.
git add .github/workflows/hello.yml
git commit -m "Add a first workflow"
git push
Open the Actions tab of the repository to watch the run, or use the GitHub CLI from inside your clone:
gh run view without an ID asks you to pick a run, and gh run watch follows a run until it finishes.
The run has one annotation, a notice from GitHub: the ubuntu-latest label is moving from Ubuntu 24.04 to Ubuntu 26.04. GitHub announced it on 17 September 2026, and the move rolls out gradually between 19 October and 19 November 2026, so for a few weeks a job can land on either image. This run still got Ubuntu 24.04. If a tool your build needs changed between the two releases, test with runs-on: ubuntu-26.04 first, or pin runs-on: ubuntu-24.04 until you are ready.
Read the Logs
The log of every step is kept with the run (for 90 days by default). gh run view --log prints it with the job name, step name and a timestamp on every line. Here are the last lines of the step that printed the environment, with cut dropping the job and step columns:
The workspace contains only what actions/checkout downloaded: .git, .github and the README. Nothing from earlier runs or other jobs is there.
The "Set up job" step at the top of every log tells you what the job ran on. This part of it names the runner version, the image and the token permissions:
The image link leads to the full list of preinstalled software for that image version: Node.js, Python, Java, Go, Docker, the cloud CLIs and many more. The token permissions match the permissions: block of the workflow.
The Runner
On GitHub-hosted runners, every job starts on a clean machine (a virtual machine, or a container for ubuntu-slim), and the machine is deleted when the job ends. That makes runs repeatable, but it also means a job knows nothing about earlier jobs: anything you want to keep, such as dependencies or the built app, has to be cached or uploaded (Part 4).
GitHub-hosted runners for public repositories have 4 vCPUs on Linux and Windows. These are the labels you will use in this guide (October 2026):
| Label | Machine |
|---|---|
ubuntu-latest |
Ubuntu 24.04, moving to 26.04, x64 |
ubuntu-24.04-arm |
Ubuntu 24.04 on arm64 |
windows-latest |
Windows Server 2025, x64 |
macos-latest |
macOS 26 on Apple silicon (arm64) |
ubuntu-slim |
1 vCPU container for small jobs, 15-minute limit |
In public repositories, these standard runners are free. In private repositories, they use the minutes included in your plan, then cost per minute; the overview lists the prices.
Common Mistakes
- The file is in the wrong place. Workflows must be in
.github/workflows/at the root of the repository, with a.ymlor.yamlextension. Anywhere else, GitHub ignores them silently. - Tabs in YAML. YAML indentation must be spaces. Part 2 shows how to check a workflow before you push it.
- Forgetting the checkout. Without
actions/checkout, the runner has none of your files. - Expecting state to survive. Files, installed packages and environment variables are gone when the job ends.
- Leaving out
permissions. Without it, the token gets the repository's default permissions, which can include write access.
Summary
- Continuous integration builds and tests every change; continuous delivery makes every passing change deployable by script; continuous deployment releases it automatically.
- An event starts a workflow, the workflow runs jobs on fresh runners, and each job runs its steps in order.
gh run list,gh run viewandgh run view --logshow runs, jobs and logs from the terminal.ubuntu-latestis moving to Ubuntu 26.04 between 19 October and 19 November 2026.
In Part 2 you extend this workflow with filters, manual inputs, a second job and a job output.
Keep Learning
Tags
Found an issue?