← Back to all blogs
GitHub Actions CI/CD Pipeline Setup - Step-by-Step Tutorial
Sat Feb 28 20267 minIntermediate

GitHub Actions CI/CD Pipeline Setup - Step-by-Step Tutorial

A comprehensive tutorial that walks you through creating a GitHub Actions CI/CD pipeline from scratch, including code, architecture, and FAQs.

#github actions#ci/cd#docker#kubernetes#devops#automation

Introduction

Overview of CI/CD with GitHub Actions

Continuous Integration and Continuous Deployment (CI/CD) have become essential practices for modern software delivery. GitHub Actions provides a native, cloud‑hosted automation engine that eliminates the need for external servers while offering deep integration with the GitHub ecosystem. This tutorial walks you through a complete end‑to‑end pipeline-from code checkout to production rollout-using a reproducible, version‑controlled workflow file.

Who Should Read This Guide?

  • Developers familiar with Git and GitHub.
  • Teams looking to automate testing, building, and deployment.
  • Engineers interested in a scalable, cost‑effective CI/CD solution.

By the end of the tutorial you will have a functional pipeline that triggers on every pull request and push to the main branch, executes unit tests, builds a Docker image, pushes it to a container registry, and finally deploys the image to a Kubernetes cluster.

Prerequisites

Before creating the workflow, ensure the following items are available:

  1. GitHub Repository - A repository with source code (e.g., a Node.js or Python project).
  2. Dockerfile - Located at the repository root; it defines how the application container is built.
  3. Container Registry - Docker Hub, GitHub Packages, or any OCI‑compatible registry where the image will be stored.
  4. Kubernetes Cluster - A cluster reachable via kubectl. A managed service such as GKE, EKS, or AKS works well.
  5. Secrets - Store sensitive values (registry credentials, kubeconfig, etc.) in the repository’s Settings → Secrets → Actions.

The following table summarises the required secrets:

Secret NamePurpose
REGISTRY_URLRegistry host (e.g., docker.io)
REGISTRY_USERUsername for the registry
REGISTRY_TOKENPersonal access token / password
KUBE_CONFIGBase‑64 encoded kubeconfig file
DOCKER_IMAGEImage name without tag (e.g., myorg/app)

Step-by-Step Pipeline Configuration

Step‑by‑Step Pipeline Configuration

Below is a line‑by‑line explanation of a robust GitHub Actions workflow. The file lives at .github/workflows/ci-cd.yml.

yaml name: CI/CD Pipeline

on: push: branches: [ main ] pull_request: branches: [ main ]

env: IMAGE_NAME: ${{ secrets.DOCKER_IMAGE }} REGISTRY: ${{ secrets.REGISTRY_URL }}

jobs: build-test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3

  - name: Set up Docker Buildx
    uses: docker/setup-buildx-action@v2

  - name: Log in to registry
    uses: docker/login-action@v2
    with:
      registry: ${{ env.REGISTRY }}
      username: ${{ secrets.REGISTRY_USER }}
      password: ${{ secrets.REGISTRY_TOKEN }}

  - name: Cache Docker layers
    uses: actions/cache@v3
    with:
      path: /tmp/.buildx-cache
      key: ${{ runner.os }}-buildx-${{ github.sha }}
      restore-keys: |
        ${{ runner.os }}-buildx-

  - name: Build and push image
    uses: docker/build-push-action@v4
    with:
      context: .
      push: true
      tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
      cache-from: type=local,src=/tmp/.buildx-cache
      cache-to: type=local,dest=/tmp/.buildx-cache,mode=max

  - name: Run unit tests
    run: |
      # Example for a Node.js project
      npm ci
      npm test

deploy: needs: build-test runs-on: ubuntu-latest steps: - name: Decode kubeconfig env: KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} run: | echo "$KUBE_CONFIG" | base64 -d > $HOME/.kube/config

  - name: Set image tag
    run: |
      echo "IMAGE_TAG=${{ github.sha }}" >> $GITHUB_ENV

  - name: Deploy to Kubernetes
    uses: azure/k8s-deploy@v4
    with:
      namespace: production
      manifests: |
        k8s/deployment.yaml
        k8s/service.yaml
      images: |
        ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}

Breakdown of Each Block

Trigger (on)

  • The workflow runs on pushes and pull‑request events targeting the main branch, guaranteeing that every change is validated before it reaches production.

Global Environment (env)

  • IMAGE_NAME and REGISTRY are derived from repository secrets, keeping the file generic and reusable across environments.

Build & Test Job

  • Checkout pulls the code into the runner.
  • Setup Buildx enables multi‑platform builds and caching.
  • Docker Login authenticates with the private registry using secrets.
  • Cache step reduces build time by persisting layer caches between runs.
  • Build‑Push creates a Docker image tagged with the commit SHA and pushes it directly to the registry.
  • Unit Tests execute after the image is built, ensuring that test failures stop the pipeline before deployment.

Deploy Job

  • Declared with needs: build-test so it only starts after a successful build and test.
  • The Kubernetes configuration is securely restored from a base‑64 secret.
  • The image tag (commit SHA) is exported to the environment, making it accessible inside the deployment manifest.
  • The azure/k8s-deploy action applies the manifests to the production namespace, performing a rolling update.

These steps together form a fully automated CI/CD pipeline that can be extended with linting, security scans, or canary releases as the team matures.

Architecture Overview

The following diagram visualises the data flow from a developer’s local machine to a live service running inside Kubernetes:

+----------------+ +----------------+ +-------------------+ | Developer Git | ----> | GitHub Repo | ----> | GitHub Actions | | (push/PR) | | (source code) | | Runner (ubuntu) | +----------------+ +----------------+ +-------------------+ | v +----------------------------+ | Docker Build & Test Stage | | - Build image (Buildx) | | - Run unit tests | | - Push to Registry | +----------------------------+ | v +----------------------------+ | Deploy Stage | | - Decode kubeconfig | | - kubectl apply manifests | | - Rolling update | +----------------------------+ | v +----------------------------+ | Kubernetes Cluster | | - Deployments, Services | | - Pods running new image | +----------------------------+

All secrets travel securely; they are never exposed in logs.

Key Design Considerations

AspectDecisionRationale
Runnerubuntu-latest (hosted)No infrastructure to manage; fast spin‑up.
Cachingactions/cache + Buildx local cacheCuts Docker build time by ~40 %.
Image TaggingCommit SHA (${{ github.sha }})Guarantees immutability and traceability.
Deployment Toolazure/k8s-deploy (generic)Works with any Kubernetes cluster, not only Azure.
SecuritySecrets stored in GitHub, base‑64 encoded kubeconfigPrevents credential leakage.
RollbackKubernetes rolling update automatically reverts to previous replica set if health checks failMinimal downtime.

By separating build and deploy jobs, the pipeline adheres to the separation of concerns principle, making each step observable, retryable, and independently scalable.

FAQs

Q1: How can I run integration tests that require external services? A: Spin up required services in Docker Compose as part of the workflow. Use the services keyword in GitHub Actions to launch containers before the test step, then point your test suite to the service’s hostname.

Q2: My pipeline fails on the docker/login-action step with “authentication required”. A: Verify that the REGISTRY_USER and REGISTRY_TOKEN secrets match the credentials of the target registry. For Docker Hub, the token must be a personal access token with read/write permissions, not your account password.

Q3: Can I trigger the workflow only for tags instead of branches? A: Yes. Replace the push trigger with push: tags: ['v*'] to run the pipeline for semantic version tags such as v1.2.3. This is useful for releasing production‑grade images.

Conclusion

Implementing a GitHub Actions CI/CD pipeline delivers immediate feedback, reduces manual effort, and enforces a consistent delivery process. The tutorial covered everything from repository preparation and secret management to a complete workflow file that builds, tests, pushes, and deploys a containerised application. By adopting the architecture described above, teams gain:

  • Speed - Caching and parallel jobs cut feedback loops.
  • Reliability - Automated tests and rolling updates prevent bad code from reaching customers.
  • Security - Secrets stay encrypted and never appear in logs.

As your project evolves, extend the pipeline with static‑code analysis, dependency scanning, or blue‑green deployments to further increase confidence. The modular nature of GitHub Actions ensures that additional steps can be introduced without disrupting existing functionality, making the solution future‑proof for any DevOps journey.