CI/CD Best Practices
DevOpsGenie is pipeline-agnostic. It integrates with GitHub Actions, GitLab CI, Tekton, and Jenkins. This guide covers the patterns and practices that apply regardless of your CI platform.
The Golden Path Pipeline
DevOpsGenie recommends a Golden Path pipeline structure that every team should adopt:
Code Push → CI (Lint, Test, Build) → Image Push → GitOps Update → CD (ArgoCD) → Production
This separation of CI (build, test, package) and CD (deploy via Git) is intentional. It ensures:
- Every deployment is traceable to a Git commit
- Rollbacks are a
git revertaway - The cluster state is always the authoritative source of truth
GitHub Actions Integration
Recommended Workflow Structure
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
AWS_REGION: us-east-1
ECR_REPOSITORY: my-service
IMAGE_TAG: ${{ github.sha }}
jobs:
# ── Step 1: Quality Gates ────────────────────────────────
lint-and-test:
name: Lint & 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: Lint
run: npm run lint
- name: Type check
run: npm run typecheck
- name: Unit tests
run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
# ── Step 2: Build & Push Image ───────────────────────────
build-and-push:
name: Build & Push
runs-on: ubuntu-latest
needs: lint-and-test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
id-token: write
contents: read
outputs:
image: ${{ steps.build.outputs.image }}
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-ecr
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image
id: build
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
run: |
docker build \
--build-arg GIT_COMMIT=${{ github.sha }} \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--cache-from $REGISTRY/$ECR_REPOSITORY:latest \
-t $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
-t $REGISTRY/$ECR_REPOSITORY:latest \
.
docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $REGISTRY/$ECR_REPOSITORY:latest
echo "image=$REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Scan image for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ steps.build.outputs.image }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: '1'
# ── Step 3: Update GitOps Repository ────────────────────
update-gitops:
name: Update GitOps
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Checkout GitOps repository
uses: actions/checkout@v4
with:
repository: your-org/gitops
token: ${{ secrets.GITOPS_TOKEN }}
path: gitops
- name: Update image tag
working-directory: gitops
run: |
# Using yq to update image tag in Kustomize overlay
yq e '.spec.template.spec.containers[0].image = "${{ needs.build-and-push.outputs.image }}"' \
-i kubernetes/apps/my-service/overlays/production/deployment-patch.yaml
git config user.email "github-actions@your-org.com"
git config user.name "GitHub Actions"
git add -A
git commit -m "chore(deploy): bump my-service to ${{ env.IMAGE_TAG }}
Deployed by: ${{ github.actor }}
Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
git push
Secrets Management in CI
Never store secrets as plaintext in CI environment variables. DevOpsGenie integrates with AWS Secrets Manager and OIDC:
# Use OIDC for AWS authentication — no long-lived credentials
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions
aws-region: us-east-1
# Fetch runtime secrets from Secrets Manager
- name: Fetch secrets
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
SONAR_TOKEN, /ci/sonarcloud/token
NPM_TOKEN, /ci/npm/token
Kustomize Overlays
Use Kustomize for environment-specific configuration without duplicating base manifests:
kubernetes/apps/my-service/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── hpa.yaml
│ └── kustomization.yaml
└─ ─ overlays/
├── staging/
│ ├── kustomization.yaml
│ └── deployment-patch.yaml # 2 replicas, smaller resources
└── production/
├── kustomization.yaml
└── deployment-patch.yaml # 6 replicas, larger resources, PDB
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: team-payments
images:
- name: my-service
newTag: "abc1234" # updated by CI pipeline
patches:
- path: deployment-patch.yaml
- path: hpa-patch.yaml
commonLabels:
environment: production
Pipeline Quality Gates
Define mandatory quality gates that must pass before promotion:
| Gate | Tool | Failure Action |
|---|---|---|
| Unit tests | Jest / pytest | Block PR merge |
| Lint & type check | ESLint / mypy | Block PR merge |
| Container scan | Trivy | Block image push (CRITICAL) |
| SAST | CodeQL / Semgrep | Block PR merge |
| Integration tests | Playwright / pytest | Block staging deploy |
| Smoke tests | k6 / custom | Block production promote |
| SLO analysis | Prometheus + Argo | Auto-rollback |
Branching Strategy
DevOpsGenie recommends trunk-based development with short-lived feature branches:
main— the trunk, always deployablefeature/*— short-lived, merged within 2 dayshotfix/*— emergency fixes, merged directly tomain
Avoid long-running release branches. Use feature flags to decouple deployment from feature release.
Next Steps
- Observability — monitor your pipelines and deployments
- Deployment Strategies — canary and blue/green releases
- Security & Access Control — securing your CI/CD supply chain