Matt Karmazyn

# CI/CD Pipeline Setup

Time to Complete: 3-4 hours
Prerequisites: Infrastructure Deployment completed

# Overview

Set up a complete CI/CD pipeline using GitHub Actions with security scanning, automated testing, and GitOps deployment via ArgoCD.


# 1. GitHub Actions Workflows

# Build and Test Workflow

Create .github/workflows/build.yml in your application repository:

name: Build and Test

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - 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: Run tests
        run: npm test
      
      - name: Run linting
        run: npm run lint

  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
      
      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: 'trivy-results.sarif'

# Docker Build and Push Workflow

Create .github/workflows/docker.yml:

name: Docker Build and Push

on:
  push:
    branches: [main]
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
      
      - name: Run Trivy vulnerability scanner on image
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
          format: 'sarif'
          output: 'trivy-image-results.sarif'
      
      - name: Upload Trivy results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: 'trivy-image-results.sarif'

# 2. Install ArgoCD

# Deploy ArgoCD to EKS

# Create argocd namespace
kubectl create namespace argocd

# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for pods to be ready
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300s

# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

# Port forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

Access ArgoCD at https://localhost:8080

# Install ArgoCD CLI

# macOS
brew install argocd

# Linux
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x /usr/local/bin/argocd

# Login via CLI
argocd login localhost:8080 --username admin

# 3. Configure ArgoCD Application

# Create Application Manifest

Create argocd/application.yaml in your infrastructure repository:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: sample-app
  namespace: argocd
spec:
  project: default
  
  source:
    repoURL: https://github.com/your-org/your-app-repo
    targetRevision: main
    path: k8s/overlays/production
  
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

# Apply the Application

kubectl apply -f argocd/application.yaml

# Verify application is syncing
argocd app list
argocd app get sample-app

# Watch sync status
argocd app sync sample-app --watch

# 4. Set Up GitHub Secrets

# Create AWS Access Keys for GitHub Actions

# Create IAM policy for GitHub Actions
cat > github-actions-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:PutImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "eks:DescribeCluster",
        "eks:ListClusters"
      ],
      "Resource": "*"
    }
  ]
}
EOF

# Create IAM user for GitHub Actions
aws iam create-user --user-name github-actions

# Attach policy
aws iam create-policy --policy-name GitHubActionsECR --policy-document file://github-actions-policy.json
aws iam attach-user-policy --user-name github-actions --policy-arn arn:aws:iam::YOUR_ACCOUNT_ID:policy/GitHubActionsECR

# Create access keys
aws iam create-access-key --user-name github-actions

# Add Secrets to GitHub Repository

Go to your GitHub repository → Settings → Secrets and variables → Actions

Add these secrets:


# 5. Configure Image Pull Secrets

# Create Docker Registry Secret

# For GitHub Container Registry
kubectl create secret docker-registry ghcr-secret \
  --docker-server=ghcr.io \
  --docker-username=YOUR_GITHUB_USERNAME \
  --docker-password=YOUR_GITHUB_PAT \
  --docker-email=YOUR_EMAIL \
  -n production

# Patch default service account to use the secret
kubectl patch serviceaccount default -n production \
  -p '{"imagePullSecrets": [{"name": "ghcr-secret"}]}'

# 6. Deploy Kubernetes Manifests Structure

# Create Kustomize Directory Structure

In your application repository:

mkdir -p k8s/base k8s/overlays/staging k8s/overlays/production

# Create base deployment
cat > k8s/base/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: app
        image: ghcr.io/your-org/your-app:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: production
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
EOF

# Create base service
cat > k8s/base/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: sample-app
spec:
  selector:
    app: sample-app
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP
EOF

# Create base kustomization
cat > k8s/base/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
EOF

# Create production overlay
cat > k8s/overlays/production/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
  - ../../base
namespace: production
replicas:
  - name: sample-app
    count: 3
images:
  - name: ghcr.io/your-org/your-app
    newTag: latest
EOF

# 7. SonarQube Integration (Optional)

# Add SonarQube Scan to Workflow

Add to .github/workflows/build.yml:

  sonarqube:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - name: SonarQube Scan
        uses: sonarsource/sonarqube-scan-action@master
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

# What Gets Created

After completing this step, you'll have:

GitHub Actions Workflows

ArgoCD Installation

ArgoCD Application

GitHub Secrets

Kubernetes Manifests


# Validation Checklist

Run these commands to verify everything is working:

# Verify ArgoCD is running
kubectl get pods -n argocd

# Verify application is synced
argocd app get sample-app

# Check application health
kubectl get pods -n production

# Verify GitHub Actions workflows
# Go to your repository → Actions tab and check recent runs

# Test image pull
kubectl run test-pod --image=ghcr.io/your-org/your-app:latest -n production --rm -it -- /bin/sh

Expected outputs:


# Troubleshooting

# ArgoCD Application Not Syncing

# Check application events
argocd app get sample-app --show-params

# View sync status
kubectl describe application sample-app -n argocd

# Force sync
argocd app sync sample-app --force

# GitHub Actions Failing

# Test AWS credentials locally
aws sts get-caller-identity

# Verify ECR login
aws ecr get-login-password --region us-east-2 | docker login --username AWS --password-stdin YOUR_ACCOUNT_ID.dkr.ecr.us-east-2.amazonaws.com

# Check workflow logs in GitHub UI

# Image Pull Errors

# Verify secret exists
kubectl get secret ghcr-secret -n production -o yaml

# Check service account
kubectl get sa default -n production -o yaml

# Test image pull manually
docker pull ghcr.io/your-org/your-app:latest

# Additional Resources


← Infrastructure Deployment Local Development Environment →