← Back to all blogs
GitHub Actions CI/CD Pipeline Setup – Production Ready Configuration
Sat Feb 28 20268 minIntermediate

GitHub Actions CI/CD Pipeline Setup – Production Ready Configuration

A comprehensive, production‑ready guide for creating a CI/CD pipeline with GitHub Actions, covering workflow design, code examples, architecture, FAQs, and best practices.

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

Introduction to Production‑Ready GitHub Actions

Why GitHub Actions?

GitHub Actions has evolved from a simple automation tool into a fully‑featured CI/CD engine that can power enterprise‑grade deployments. Its native integration with the GitHub ecosystem eliminates the need for external webhook management, reduces latency, and offers granular permission controls.

Core Benefits for Production Environments

  • Scalability: Parallel jobs and self‑hosted runners enable you to scale horizontally.
  • Security: Fine‑grained OIDC tokens, secret masking, and environment protection rules keep production secrets safe.
  • Observability: Built‑in job annotations, step summaries, and integrations with monitoring platforms provide end‑to‑end visibility.

In the sections that follow we will construct an end‑to‑end pipeline that builds Docker images, runs static analysis, pushes artifacts to a container registry, and finally deploys to a Kubernetes cluster using a blue‑green strategy.


Architecture Overview

High‑Level Diagram

+----------------+ +----------------------+ +-------------------+ | GitHub Repo | ---> | GitHub Actions CI | ---> | Container Registry | +----------------+ +----------------------+ +-------------------+ | | | v v v +----------------+ +----------------------+ +-------------------+ | Feature Branch| | Build & Test Jobs | | Production Cluster | +----------------+ +----------------------+ +-------------------+

Key Components

  1. Source Repository - Stores application code, Dockerfiles, Helm charts, and workflow definitions.
  2. GitHub Actions Runner - Executes jobs. We will use a combination of GitHub‑hosted Linux runners for quick feedback and self‑hosted runners for resource‑intensive stages (e.g., integration tests).
  3. Artifact Store - Docker images are pushed to GitHub Container Registry (GHCR); release binaries are archived as workflow artifacts.
  4. Target Environment - An Amazon EKS or GKE cluster, protected by environment rules that require manual approval before deployment to production.

Data Flow

  • A push to main or a pull‑request triggers the CI workflow.
  • The workflow builds a Docker image, runs linting, unit tests, and generates a SBOM (Software Bill of Materials).
  • On a successful CI run, a CD workflow is triggered via workflow_run. It pulls the image, validates it with Trivy, and then deploys using Helm to the staging namespace.
  • After automated health checks pass, a manual approval gate promotes the release to the production namespace using a blue‑green rollout.

Step‑by‑Step Pipeline Implementation

1. Repository Layout

my‑app/ ├─ .github/ │ └─ workflows/ │ ├─ ci.yml │ └─ cd.yml ├─ Dockerfile ├─ helm/ │ └─ my‑app/ │ ├─ Chart.yaml │ └─ values.yaml ├─ src/ │ └─ ... └─ tests/ └─ ...

2. CI Workflow (ci.yml)

yaml name: CI on: push: branches: [main] pull_request: branches: [main]

permissions: contents: read packages: write security-events: write

jobs: lint_and_test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4

  - name: Set up Node.
uses: actions/setup-node@v4
        with:
          node-version: '20'
  - name: Install dependencies
    run: npm ci

  - name: Run ESLint
    run: npm run lint

  - name: Run Unit Tests
    run: npm test -- --coverage

build_image: needs: lint_and_test runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout code uses: actions/checkout@v4

  - name: Log in to GHCR
    uses: docker/login-action@v3
    with:
      registry: ghcr.io
      username: ${{ github.actor }}
      password: ${{ secrets.GITHUB_TOKEN }}

  - name: Build Docker image
    run: |
      IMAGE_TAG=ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}
      docker build -t $IMAGE_TAG .
      docker push $IMAGE_TAG
    env:
      DOCKER_BUILDKIT: 1

  - name: Generate SBOM (Syft)
    uses: anchore/sbom-action@v0
    with:
      image: ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}
      format: spdx-
scan_image:
    needs: build_image
    runs-on: ubuntu-latest
    steps:
      - name: Pull image
        run: docker pull ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}
  - name: Scan with Trivy
    uses: aquasecurity/trivy-action@master
    with:
      image-ref: ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}
      format: sarif
      output: trivy-results.sarif

  - name: Upload Trivy results
    uses: github/codeql-action/upload-sarif@v2
    with:
      sarif_file: trivy-results.sarif

Explanation:

  • The workflow starts with linting and unit testing, ensuring code quality before any heavy operation.
  • The build_image job builds a Docker image using BuildKit for efficient caching, tags it with the commit SHA, and pushes to GHCR.
  • SBOM generation provides a bill of materials for compliance audits.
  • The scan_image job runs a Trivy vulnerability scan and uploads results as SARIF, making findings visible in the GitHub Security tab.

3. CD Workflow (cd.yml)

yaml name: CD on: workflow_run: workflows: [CI] types: - completed

permissions: contents: read packages: read id-token: write deployments: write

concurrency: group: production-deploy cancel-in-progress: true

jobs: deploy_staging: if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: self-hosted environment: name: staging url: https://staging.example.com steps: - name: Checkout repository uses: actions/checkout@v4

  - name: Authenticate to GKE/EKS (OIDC)
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsOIDCRole
      aws-region: us-east-1

  - name: Set up kubectl & helm
    uses: azure/setup-helm@v3
    with:
      version: v3.12.0

  - name: Deploy to Staging (Helm upgrade)
    env:
      IMAGE_TAG: ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}
    run: |
      helm upgrade --install my-app ./helm/my-app \
        --namespace staging \
        --set image.repository=$IMAGE_TAG \
        --wait --timeout 5m

  - name: Run Smoke Tests
    run: |
      ./scripts/smoke-test.sh https://staging.example.com

manual_approval: needs: deploy_staging runs-on: ubuntu-latest environment: name: production url: https://prod.example.com steps: - name: Wait for manual approval uses: peter-evans/manual-approval@v2 with: timeout-minutes: 1440 required-approvers: alice,bob

deploy_production: if: ${{ needs.manual_approval.result == 'success' }} needs: [manual_approval] runs-on: self-hosted environment: name: production url: https://prod.example.com steps: - name: Checkout repository uses: actions/checkout@v4

  - name: Authenticate to GKE/EKS (OIDC)
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsOIDCRole
      aws-region: us-east-1

  - name: Deploy Blue‑Green Release
    env:
      IMAGE_TAG: ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:${{ github.sha }}
    run: |
      # Deploy to new namespace "green"
      helm upgrade --install my-app-green ./helm/my-app \
        --namespace green \
        --set image.repository=$IMAGE_TAG \
        --wait

      # Run health checks against green
      ./scripts/health-check.sh https://green.example.com

      # Switch traffic (Ingress) to green
      kubectl patch ingress my-app -n production -p '{"spec":{"rules":[{"host":"prod.example.com","http":{"paths":[{"path":"/","backend":{"serviceName":"my-app-green","servicePort":80}}]}}]}}'

      # Optional: keep blue for rollback window
      echo "Deployment complete. Blue version retained for 30 minutes."

Explanation:

  • The CD workflow is triggered only when the CI workflow finishes successfully.
  • Staging deployment runs on a self‑hosted runner that has direct network access to the cluster.
  • A manual‑approval step protects production; only authorized users can approve.
  • The blue‑green strategy deploys to a fresh namespace (green), validates health, then switches the ingress to point to the new service, allowing an instant rollback by reverting the ingress rule.

4. Security Hardening Tips

  • Store all secrets (registry credentials, OIDC role ARN) in GitHub Encrypted Secrets.
  • Enable GitHub Advanced Security and enforce branch protection rules.
  • Use immutable tags (commit SHA) instead of latest to avoid accidental overwrites.
  • Rotate OIDC tokens every run; never expose long‑lived AWS keys.

FAQs

Frequently Asked Questions

Q1: How do I run the pipeline on a self‑hosted runner that lives inside a private VPC?

  • Install the runner following the official GitHub documentation, register it with the self-hosted label, and ensure the machine has the required Docker, kubectl, and Helm binaries. Configure the runner's IAM instance profile (or attach an OIDC provider) so it can assume the deployment role without storing static credentials.

Q2: Can I use this setup with Azure Kubernetes Service (AKS) instead of EKS/GKE?

  • Absolutely. Replace the aws-actions/configure-aws-credentials step with azure/login and adjust the service‑principal or managed identity accordingly. The Helm and kubectl commands remain identical because they interact with the Kubernetes API, which is cloud‑agnostic.

Q3: What is the recommended way to handle database migrations in this pipeline?

  • Include a dedicated job (or step) after the staging deployment that runs migration scripts via a temporary pod. Use a kubectl exec command or a Helm hook (pre-upgrade) to execute the migration. Always run migrations against the green namespace first and verify success before switching traffic.

Q4: How can I enforce that only images with no critical vulnerabilities are promoted to production?

  • The scan_image job outputs a SARIF file that GitHub marks as a security alert. Add a policy gate using github/codeql-action/upload-sarif and configure a branch protection rule that requires the CI check scan_image to pass with a status of success (i.e., no findings above the defined severity threshold).

Conclusion

Bringing It All Together

Implementing a production‑ready CI/CD pipeline with GitHub Actions provides a seamless, secure, and observable pathway from code commit to live service. By layering static analysis, container scanning, and a blue‑green deployment model, you achieve high confidence releases while minimizing downtime.

Key takeaways:

  • Leverage OIDC for temporary cloud credentials, eliminating long‑lived secrets.
  • Use self‑hosted runners for resource‑intensive stages and to keep traffic within private networks.
  • Adopt environment protection rules and manual approvals to enforce governance.
  • Generate a Software Bill of Materials (SBOM) and upload vulnerability reports to GitHub Security for compliance.

When you adopt the blueprint above, you gain a reproducible, auditable, and scalable pipeline that can evolve alongside your applications. Continue to iterate on the workflow-add canary releases, integrate feature flags, or plug in observability tools like Prometheus and Grafana-to stay ahead of the ever‑changing DevOps landscape.