Cloud Native 12 min read

Create a Production‑Grade GitOps CI/CD Pipeline Using GitHub Actions and Argo

This guide walks through a production‑level GitOps CI/CD pipeline that integrates GitHub Actions for building and pushing Docker images, a separate GitOps repository for declarative Kubernetes manifests managed with Helm and Kustomize, and Argo CD plus Argo Rollouts to deliver automated, safe, progressive releases across staging and production environments.

DevOps Coach
DevOps Coach
DevOps Coach
Create a Production‑Grade GitOps CI/CD Pipeline Using GitHub Actions and Argo

Overview

This article demonstrates a production‑ready GitOps CI/CD pipeline built with GitHub Actions, Argo CD, and Argo Rollouts. It automates Docker image builds, stores declarative Kubernetes manifests in a separate GitOps repository, and enables safe progressive releases.

Pipeline diagram
Pipeline diagram

End‑to‑End Flow

Code change : Developer opens a pull request and merges to main.

CI (Continuous Integration) : GitHub Actions builds a Docker image and pushes it to GitHub Container Registry (GHCR).

GitOps update : The CI workflow writes the new image tag into the GitOps repository.

CD (Continuous Delivery) : Argo CD detects the change and synchronises the Kubernetes cluster.

Progressive release : Argo Rollouts performs a Blue‑Green or Canary rollout of the new version.

Architecture Principle

The application code repository and the GitOps configuration repository are kept strictly separate.

Application repo ( service-foundry-web): contains source code (React, Go, Dockerfile, etc.).

GitOps repo ( service-foundry-argocd): stores Helm/Kustomize manifests that define how the app runs.

Example Application

The tutorial uses a simple containerised React app available at https://github.com/nsalexamy/service-foundry-web. It serves as a testbed for the full CI/CD chain.

Part 1 – CI with GitHub Actions

Create .github/workflows/build-and-push.yaml in the application repository:

name: Build and Push Image
on:
  push:
    branches:
      - main

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: nsalexamy/service-foundry-web

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout source
        uses: actions/checkout@v4
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Extract image tag
        id: meta
        run: |
          TAG=${GITHUB_SHA::7}
          echo "tag=$TAG" >> $GITHUB_OUTPUT
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: |
            ghcr.io/nsalexamy/service-foundry-web:${{ steps.meta.outputs.tag }}
            ghcr.io/nsalexamy/service-foundry-web:latest

Part 2 – Build GitOps Config with Helm & Kustomize

Initialize directory

mkdir service-foundry-web-gitops
cd service-foundry-web-gitops

Generate Helm chart (including Argo Rollout resources)

# Ensure you are in service-foundry-web-gitops
yo open-nsa2:helm-chart-rollouts chart-home/service-foundry-web \
  --image-repository ghcr.io/nsalexamy/service-foundry-web \
  --image-tag latest \
  --service-port 80

The command creates chart-home/service-foundry-web with key files: rollout.yaml: Argo Rollout definition. service.yaml: Stable traffic routing. service-preview.yaml: Preview (canary) traffic routing.

Staging environment configuration (Kustomize)

staging/kustomization.yaml :

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: staging
resources:
  - ingressroute.yaml
helmGlobals:
  chartHome: ../chart-home
helmCharts:
  - name: service-foundry-web
    releaseName: service-foundry-web
    namespace: staging
    valuesFile: values-staging.yaml

staging/values-staging.yaml (the tag field will be overwritten by CI):

replicaCount: 4
image:
  tag: "latest"
rollouts:
  enabled: true
  strategy:
    blueGreen:
      activeService: "service-foundry-web"
      previewService: "service-foundry-web-preview"
      autoPromotion: false  # manual promotion in the demo

staging/ingressroute.yaml defines public routes:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: service-foundry-web-staging-ingressroute
spec:
  entryPoints:
    - web
    - websecure
  routes:
    - match: Host(`web-staging.servicefoundry.org`)
      kind: Rule
      services:
        - name: service-foundry-web
          port: 80
    - match: Host(`web-staging-preview.servicefoundry.org`)
      kind: Rule
      services:
        - name: service-foundry-web-preview
          port: 80

Part 3 – CD with Argo CD

Create an Argo CD Application that watches the GitOps directory:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: service-foundry-web-staging
  namespace: argocd
spec:
  project: service-foundry
  source:
    repoURL: [email protected]:nsalexamy/service-foundry-argocd.git
    targetRevision: HEAD
    path: demo-apps/service-foundry-web-gitops/staging
  destination:
    server: https://kubernetes.default.svc
    namespace: staging
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Apply the manifest to the cluster:

kubectl apply -f service-foundry-web-staging-app.yaml

Part 4 – Auto‑update GitOps Repository

Add a second workflow in the application repository that, after a successful image build, writes the new tag back to the GitOps repo:

name: Update GitOps Repo
on:
  workflow_run:
    workflows: ["Build and Push Image"]
    types:
      - completed

jobs:
  update:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - name: Checkout GitOps repo
        uses: actions/checkout@v4
        with:
          repository: nsalexamy/service-foundry-argocd
          token: ${{ secrets.GITOPS_TOKEN }}
      - name: Update image tag
        run: |
          TAG=${GITHUB_SHA::7}
          sed -i "s|tag:.*|tag: \"${TAG}\"|" demo-apps/service-foundry-web-gitops/staging/values-staging.yaml
      - name: Commit and push
        run: |
          git config user.name "github-actions"
          git config user.email "[email protected]"
          git commit -am "deploy: service-foundry-web ${GITHUB_SHA::7}"
          git push

Verification – Run the Full Pipeline

Visit the current version at https://web-staging.servicefoundry.org (e.g., orange theme).

Modify public/config.json in the application repo, changing primaryColor to #673AB7 (purple).

Commit and push to main:

git add .
git commit -m "Update theme color to Deep Purple"
git push origin main

Observe the sequence:

GitHub runs the Build and Push workflow.

GitHub runs the Update GitOps Repo workflow.

Argo CD detects the new tag in values-staging.yaml and synchronises the cluster.

Argo Rollouts launches the preview (green) stack showing the purple version while the main (blue) stack remains orange.

Check the preview URL https://web-staging-preview.servicefoundry.org – it should display the purple theme. If manual promotion is enabled, clicking Promote in the Argo Rollouts UI switches all traffic to the new version.

Summary

Developers focus on business code in the application repository.

GitHub Actions builds Docker images and writes the new tag back to the GitOps repository.

Argo CD continuously reconciles cluster state with the declarative GitOps configuration.

Argo Rollouts provides safe, progressive releases (Blue‑Green or Canary).

The architecture scales from small teams to large‑enterprise environments.

CI/CDKubernetesGitOpsHelmArgo CDGitHub ActionsKustomize
DevOps Coach
Written by

DevOps Coach

Master DevOps precisely and progressively.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.