Skip to content

GitHub Actions CI/CD Pipeline: Step by Step

Cloud & DevOps6 min read

Build a CI/CD pipeline with GitHub Actions: a tested YAML workflow, secure OIDC deployment, approvals, caching and common mistakes.

In this article
  1. Core concepts in GitHub Actions
  2. Step 1: decide what the pipeline must prove
  3. Step 2: create the CI workflow
  4. Step 3: make the checks required
  5. Step 4: add deployment
  6. Step 5: harden the pipeline
  7. Step 6: keep it fast and reliable
  8. Common mistakes
  9. Pipeline checklist
  10. Summary

To build a CI/CD pipeline with GitHub Actions, you add a YAML file under .github/workflows/ in your repository that tells GitHub when to run (for example on every pull request), what to run (install, test, build) and, for the deployment half, where to send the result. Continuous integration (CI) checks every change automatically. Continuous delivery or deployment (CD) ships the verified build to an environment. A basic working pipeline takes about an hour to set up and pays for itself within weeks.

This guide builds one step by step for a Node.js web application, then shows how to add a secure deployment job. The same structure applies to other languages; only the setup and test commands change.

Core concepts in GitHub Actions

  • Workflow: a YAML file describing an automated process.
  • Event: what triggers it, such as push, pull_request or a schedule.
  • Job: a set of steps that runs on one runner. Jobs run in parallel unless you say otherwise.
  • Step: a single command or a reusable action.
  • Runner: the machine that executes the job, either GitHub-hosted or self-hosted.
  • Action: a reusable unit of automation, such as checking out code or setting up a language runtime.
  • Secrets and variables: configuration stored in GitHub rather than in the repository.

CloudWiss lists GitHub Actions alongside Jenkins and GitLab CI among its pipeline tools. The ideas below transfer to all three.

Step 1: decide what the pipeline must prove

Before writing YAML, agree what "good" means for your project. A sensible baseline is: dependencies install cleanly from the lockfile, linting passes, tests pass, and the application builds. Add security checks and deployment afterwards. A pipeline that checks too little gives false comfort; one that takes thirty minutes gets bypassed. Aim for feedback in a few minutes.

Step 2: create the CI workflow

Create .github/workflows/ci.yml:

name: CI

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Check out code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Test
        run: npm test

      - name: Build
        run: npm run build

      - name: Upload build output
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7

What each part is doing:

  • on runs the workflow for every pull request and for every push to main.
  • permissions: contents: read gives the job's token the minimum rights it needs. Start restrictive and widen only when required.
  • concurrency cancels an older run for the same branch when you push again, saving time and runner minutes.
  • timeout-minutes stops a hung job from running for hours.
  • cache: npm caches downloaded packages between runs, and npm ci installs exactly what the lockfile specifies.
  • The artefact upload keeps the build output so the deployment job can reuse it instead of rebuilding.

Commit this file, open a pull request and watch the Actions tab. If a step fails, the log shows exactly which command and why.

Step 3: make the checks required

A pipeline nobody has to respect is decoration. In the repository settings, add a branch protection rule or ruleset for main that requires the CI job to pass and at least one review before merging. This is where continuous integration becomes a real quality gate.

Step 4: add deployment

For deployment, avoid long-lived cloud keys stored as secrets. GitHub Actions can authenticate to AWS and Azure through OpenID Connect (OIDC), receiving short-lived credentials for each run. Here is an AWS example, added as a second job in the same file (or in a separate deploy.yml):

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Check out code
        uses: actions/checkout@v4

      - name: Download build output
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-deploy
          aws-region: eu-west-1

      - name: Deploy
        run: ./scripts/deploy.sh

Key points:

  • needs: test means deployment only runs after the tests succeed.
  • The if condition limits deployment to pushes to main, never to pull requests from forks.
  • environment: production lets you add required reviewers and environment-specific secrets in GitHub, giving a manual approval step before production changes.
  • id-token: write allows the OIDC token request. The role in AWS must have a trust policy that only accepts your repository (and ideally your branch or environment). The account number above is a placeholder.
  • deploy.sh is your own script, whether it syncs static files, updates a container service or applies Terraform. Keep it in the repository so it is reviewed too.

If you deploy containers, add a build-and-push job that creates an image tagged with the commit SHA, then deploys that exact tag. The image-based approach pairs well with blue-green and canary deployment strategies. For the container basics, see Docker explained for business teams.

Step 5: harden the pipeline

  • Pin third-party actions. Actions from unknown publishers run with access to your build. Prefer official or well-known actions and consider pinning to a full commit SHA for anything sensitive.
  • Keep secrets out of logs. GitHub masks registered secrets, but a derived value may not be masked. Do not echo credentials.
  • Treat pull requests from forks as untrusted. Secrets are not passed to them by default; do not weaken that. Be especially careful with the pull_request_target trigger.
  • Enable dependency updates. Dependabot can open pull requests for outdated actions and packages.
  • Scan. Add dependency and image vulnerability scanning, and secret scanning on the repository.

Step 6: keep it fast and reliable

  • Cache dependencies and, for containers, build layers.
  • Run independent jobs, such as lint and tests, in parallel.
  • Use a matrix when you must test several runtime versions, but avoid a matrix by default.
  • Quarantine flaky tests. A pipeline that fails randomly teaches people to click "re-run" and ignore it.
  • Reuse logic with reusable workflows once several repositories share the same steps.

Common mistakes

  • Deploying from a developer's laptop as well. Two routes to production means neither is trustworthy. Make the pipeline the only way.
  • Rebuilding for each environment. Build once, then promote the same artefact through staging to production.
  • Long-lived cloud keys in secrets. Use OIDC and a narrowly scoped role.
  • Overly broad permissions. Set them at workflow or job level.
  • Using npm install in CI. It can change dependency versions. Use npm ci or your ecosystem's lockfile-respecting equivalent.
  • No rollback plan. Know how to redeploy the previous artefact quickly.
  • Infrastructure changes by hand. Put environment changes through the same pipeline using Terraform.
  • No visibility after deployment. A green pipeline does not mean a healthy service. Pair it with monitoring and alerting.

Pipeline checklist

  1. Workflow runs on pull requests and on main.
  2. Lint, test and build complete in a few minutes.
  3. Branch protection requires the checks and a review.
  4. Permissions are minimal and actions are from trusted sources.
  5. Deployment uses OIDC, an environment and, for production, an approval.
  6. The same artefact is promoted between environments.
  7. Rollback has been tried at least once.

Summary

A good pipeline is small, fast and strict: prove the change is safe, build it once, deploy it the same way every time. Start with the CI job above and add deployment when you trust it. If you want help designing pipelines on GitHub Actions, GitLab CI or Jenkins, CloudWiss's cloud and DevOps services cover CI/CD pipeline automation from the first workflow through to production deployment.

Share this article

General educational information, not legal or professional advice for your specific situation.

Frequently asked questions

How do I create a GitHub Actions workflow?

Add a YAML file under .github/workflows/ in your repository. Define the events that trigger it, then jobs made of steps that check out code, install dependencies, test, build and optionally deploy.

How should I authenticate GitHub Actions to AWS or Azure?

Use OpenID Connect federation instead of long-lived keys. The workflow receives short-lived credentials for a narrowly scoped role that trusts only your repository and, ideally, a specific branch or environment.

What is the difference between CI and CD?

Continuous integration automatically builds and tests every change. Continuous delivery or deployment takes the verified build and releases it to an environment, optionally after a manual approval.

How do I stop deployments to production happening by accident?

Use a protected branch, require passing checks and a review, run deploy jobs only on main, and attach the job to a GitHub environment with required reviewers.

Keep reading

Let’s build it

Ready to build with CloudWiss?

Tell us what you want to build, move to the cloud or keep running. We reply with a clear next step.