
Your First CI/CD Pipeline with GitHub Actions
July 5, 2026 · 11 min read
CI/CD is the practice of automatically testing and preparing code changes for release. CI (Continuous Integration) builds and tests your code on every commit; CD (Continuous Delivery/Deployment) automatically ships what passes. The goal: catch bugs early and eliminate repetitive manual work.
How GitHub Actions works
You put a YAML file under .github/workflows/ in your repo. It defines the jobs and steps to run on certain events (push, pull request, etc.). GitHub runs those jobs on its own servers (runners).
A working example
A minimal workflow that installs dependencies and runs tests on every push:
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm testCore concepts
- workflow: the YAML file defining the whole automation
- job: a unit of work that runs in parallel or in sequence
- step: a single command or action inside a job
- action: a reusable prebuilt step (e.g. checkout)
- runner: the virtual machine that runs jobs
The next step
Complete CD by adding a job that builds and deploys to a cloud service (e.g. Cloud Run, S3, Vercel) once tests pass. Keep secrets in GitHub Secrets — never in YAML.
Until the pipeline is green: debug a failing build
First pipelines rarely go green on the first try. The most common reds and their fixes:
# In the Actions log:Run npm cinpm error: The `npm ci` command can only install with an existing package-lock.json# → no package-lock.json in the repo. Fix: use 'npm install', or commit the lockfile.Run npm testsh: 1: jest: not found# → dev dependency not installed. Fix: setup-node cache + 'npm ci' (includes devDependencies).
- 1Click the red job → expand the failing step, read the full message
- 2If it 'worked locally': the runner is a clean env — suspect a missing lockfile/env/secret
- 3Does the step need a secret? Check ${{ secrets.X }} is defined (shown masked in logs)
- 4Fix, push, confirm the job turns green
Warning
Common mistake: hardcoding an API key in YAML. Action logs and fork PRs leak it. Always use GitHub Secrets; and remember secrets do NOT flow to forks on the pull_request trigger.
Mini task
Set up the CI workflow above in a small repo. Push a commit that deliberately breaks the test → inspect the red job and its log. Then fix it and turn it green. Bonus: add setup-node caching and compare the second run's duration.
Hands-on task — try it in your browser
Setting up and running these flows in a real repo is the fastest way to learn — Cloudpuz's CI/CD labs make it hands-on.
Official sources
Last verified: 2026-07-17
Frequently Asked Questions
What is CI/CD?
CI/CD is the practice of automatically testing and preparing code changes for release. CI (Continuous Integration) builds and tests your code on every commit; CD (Continuous Delivery/Deployment) automatically ships what passes. The goal is to catch bugs early and eliminate repetitive manual work.
How does GitHub Actions work?
You put a YAML file under `.github/workflows/` in your repository. It defines the jobs and steps to run on certain events, such as push or pull request. GitHub runs those jobs on its own servers, called runners.
What do workflow, job, step, action, and runner mean in GitHub Actions?
A workflow is the YAML file defining the whole automation; a job is a unit of work that runs in parallel or in sequence; a step is a single command or action inside a job. An action is a reusable prebuilt step (e.g. checkout), and a runner is the virtual machine that runs jobs.
How do you store API keys and secrets in GitHub Actions?
Secrets should always be kept in GitHub Secrets and never hardcoded in YAML, otherwise action logs and fork PRs can leak them. You access them in a workflow with `${{ secrets.X }}`, where they appear masked in logs. Also note that secrets do not flow to forks on the pull_request trigger.
How do you fix the 'npm ci can only install with an existing package-lock.json' error in GitHub Actions?
This error means there is no `package-lock.json` in the repo. The fix is to either use `npm install` instead of `npm ci`, or commit the lockfile. Similarly, a 'jest: not found' error means dev dependencies weren't installed; using setup-node caching with `npm ci` installs them, including devDependencies.
Reading isn't enough — do it.
Practice these topics in an interactive terminal in your browser.