How to set up CI/CD with GitHub Actions
Set up a CI/CD pipeline with GitHub Actions: create your first workflow, configure triggers, build and test your code on runners, use actions and secrets, and deploy automatically — open-license sources.
On this page
- What CI/CD and GitHub Actions mean
- Prerequisites
- Step 1 — Create your first workflow file
- Step 2 — Configure triggers with on
- Step 3 — Build and test your code
- Step 4 — Store secrets and use them in workflows
- Step 5 — Deploy automatically (continuous delivery)
- Step 6 — Check, debug, and extend your workflow
- Common mistakes to avoid
- FAQ
- Is GitHub Actions free?
- What is the difference between CI and CD?
- How do I run a workflow only on the main branch?
- What is a GitHub Actions runner?
- How do I use secrets without exposing them in logs?
- Summary
CI/CD (continuous integration and continuous delivery) automates building, testing, and deploying your code so every change is verified before it ships. GitHub Actions is a CI/CD platform built into GitHub: you define workflows in YAML files, and GitHub runs them in the cloud whenever you push code, open a pull request, or hit a schedule. This guide walks through creating your first workflow, wiring triggers, building and testing a Node.js project on hosted runners, using actions and secrets, and deploying the result automatically — with real workflow YAML you can copy and adapt.
What CI/CD and GitHub Actions mean
CI/CD is the combined practice of continuous integration (CI) and continuous delivery (CD). Continuous integration means merging small changes into your main branch frequently and verifying each change with automated builds and tests. Continuous delivery means releasing software in short cycles so reliable software can be deployed at any time with a simple, repeatable process. When CI and CD run together they form a pipeline.
GitHub Actions provides the platform to build that pipeline. Its four core components are:
- Workflow — a configurable automated process defined in a YAML file in the
.github/workflowsdirectory. - Event — what triggers the workflow: a push, a pull request, a schedule, or a manual dispatch.
- Job — a set of steps executed on the same runner.
- Runner — a machine that runs a job; GitHub provides Linux, Windows, and macOS virtual machines (GitHub-hosted runners), or you can host your own.
A repository can have multiple workflows, each doing a different job: build and test every pull request, deploy on release, or add a label when an issue opens.
Prerequisites
- A GitHub repository (public or private) with your code committed.
- A project with defined build/test commands (
npm testfor Node.js,python -m pytestfor Python, etc.). - Basic familiarity with YAML.
GitHub Actions is free for public repositories, and free minutes are included for private repositories on the free plan. You do not need a separate CI server.
Step 1 — Create your first workflow file
Workflows live in a .github/workflows directory at the root of your repository. Create the file .github/workflows/ci.yml with the minimum valid workflow:
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: echo "Hello from GitHub Actions"
Commit and push this file. Every push to the repository now triggers a run: GitHub looks in .github/workflows, finds a workflow whose on value matches the event, and starts the job. You can watch the run under the Actions tab of your repository.
Why
actions/checkout? Each job starts on a fresh virtual machine with an empty working directory. Theactions/checkoutaction clones your repository into the runner so later steps can access your code.
Step 2 — Configure triggers with on
The on key defines which events start the workflow. Common triggers:
on: push # any push to any branch
on:
push:
branches: [ main ] # only the main branch
paths: [ "src/**" ] # only when files under src change
on:
pull_request: # run on pull requests
branches: [ main ]
on: # run on a schedule, cron syntax
schedule:
- cron: "0 8 * * *"
on: workflow_dispatch # manual trigger from the Actions tab
You can combine events. For example, on: [push, pull_request]. Branch and path filters use glob patterns; quoted '*' and '**' match any number of characters.
Step 3 — Build and test your code
The most common CI goal is to verify your code builds and passes tests on every change. Here is a practical workflow for a Node.js project:
name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npm run build --if-present
- run: npm test
Key points:
actions/setup-nodeinstalls a specific Node.js version and adds it toPATH. It also caches dependencies (thecache: "npm"input) to make future runs faster.npm ciinstalls dependencies from the lock file — it is generally faster and more reliable thannpm install.- The
--if-presentflag makes the build step optional, so the same workflow works for projects without a build script. - The matrix keyword lets you test multiple versions or operating systems in parallel without duplicating the job:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ["18", "20"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
Each value in the node-version array creates a separate job running the same steps, using the matrix context to pick its version.
Step 4 — Store secrets and use them in workflows
Never hard-code credentials in a workflow file. Instead, store them as secrets on the repository (Settings → Secrets and variables → Actions) and reference them with the secrets context:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: npm test
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
GitHub redacts secrets in logs. Use the principle of least privilege: give each secret the minimum scope it needs, and rotate them if a workflow or repository is compromised.
Step 5 — Deploy automatically (continuous delivery)
The final step of a pipeline ships your verified code. A common pattern is to run tests first, then deploy only when tests pass. One workflow can do both by making the deploy job depend on the test job with needs:
name: CI/CD
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npm test
deploy:
needs: test # only runs if 'test' succeeds
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Deploy
run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
The needs: test line makes the deploy job wait for the test job and run only if it succeeds — so broken code never deploys. In place of ./deploy.sh you would call the deploy action for your platform (for example, a Cloudflare Pages or static hosting deployment action) or run the CLI with your secret token.
Step 6 — Check, debug, and extend your workflow
- Open the Actions tab to see run status, logs, and failures.
- Each step's output is shown in the run log; failed steps are highlighted.
- Use
continue-on-error: trueon a step when a known failure should not block the job. - Limit how long a job may run with
timeout-minutesso a hanging job does not consume minutes forever. - Store build/test artifacts (test reports, screenshots, logs) with the
actions/upload-artifactaction so you can inspect them after a run.
Common mistakes to avoid
- Missing
.github/workflowspath — workflows must live in that exact directory at the repository root. - Hard-coded credentials — always use secrets, never commit tokens or keys in YAML.
- Forgetting
actions/checkout— later steps cannot see your code without it. - Unsafe shell interpolation — treat any value coming from a PR title or issue body as untrusted input when passing it to
run:. - Wrong action versions — pin the major version (e.g.
actions/checkout@v6) and review new majors before upgrading. - Ignoring the event context — a
pull_requestevent runs in the context of the PR, so make sure your test commands are valid there too.
FAQ
Is GitHub Actions free?
Yes for public repositories, where workflows run at no cost. Private repositories include a monthly allowance of free action minutes on GitHub's hosted runners (the exact amount depends on your plan), after which you pay for additional minutes. Self-hosted runners use your own machines and have no per-minute fee.
What is the difference between CI and CD?
Continuous integration (CI) automatically builds and tests every change you merge, catching integration problems early. Continuous delivery (CD) automates deploying the verified build so reliable software can be released at any time with a repeatable process. CI/CD refers to the two working together in one pipeline.
How do I run a workflow only on the main branch?
Use the on.push.branches filter:
on:
push:
branches: [ main ]
The workflow then runs only when the push targets main. You can also add paths filters or use pull_request events with branch filters.
What is a GitHub Actions runner?
A runner is a machine that executes the steps of a job. GitHub provides GitHub-hosted runners (fresh Linux, Windows, or macOS virtual machines) with a set of preinstalled tools, or you can set up self-hosted runners on your own infrastructure.
How do I use secrets without exposing them in logs?
Reference secrets through the secrets context (${{ secrets.NAME }}) and set them as environment variables or action inputs rather than printing them. GitHub automatically redacts secret values in logs. Keep secrets scoped to the minimum permission needed and rotate them regularly.
Summary
GitHub Actions turns a GitHub repository into a CI/CD platform. You define workflows as YAML files in .github/workflows, trigger them with events like push and pull_request, build and test code on hosted runners, secure credentials with secrets, and deploy automatically once tests pass. Start with one small workflow that builds and tests your project, then extend it with a deploy job — a working pipeline for free on public repositories.
Sources
- GitHub Docs — Understanding GitHub Actions (docs.github.com/en/actions/about-github-actions/understanding-github-actions)
- GitHub Docs — Workflows (docs.github.com/en/actions/using-workflows/about-workflows)
- GitHub Docs — Workflow syntax for GitHub Actions (docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)
- GitHub Docs — Building and testing Node.js (docs.github.com/en/actions/tutorials/build-and-test-code/nodejs)
- Wikipedia — CI/CD (en.wikipedia.org/wiki/CI/CD)
Share this guide
Related guides
- What is unit testing? How to write, structure, and run unit tests
Unit testing is the practice of testing the smallest testable parts of a program — functions, methods, or classes — in isolation. Learn what a unit test is, the arrange-act-assert structure, test doubles and mocking, the testing pyramid, and best practices for writing good unit tests.
- How to use Docker Compose: multi-container apps explained
Learn Docker Compose to run multi-container apps from one YAML file: services, networks, volumes, health checks, Compose Watch, env vars, docker compose up and common commands — with a real Node.js example.
- What is a webhook? How webhooks work and how to secure them
A webhook is a user-defined HTTP callback: one service sends an HTTP request to a URL you control when an event happens. Learn how they work, how they differ from polling and regular APIs, and how to verify signatures and handle retries safely.