New engagements · 24h
Skip to main content
Home / Docs / GitOps Standards

GitOps

GitOps Standards

1. Repository Structure

We use a monorepo structure separating application code from infrastructure manifests. The GitOps repository contains only Helm charts and ArgoCD Application manifests — no application source code.

gitops-repo/
├── apps/
│   ├── app-of-apps.yaml      # Root ArgoCD Application
│   ├── webapp/
│   │   ├── dev.yaml           # ArgoCD Application for dev
│   │   └── prod.yaml          # ArgoCD Application for prod
│   └── monitoring/
│       └── prod.yaml
├── charts/
│   └── webapp/
│       ├── Chart.yaml
│       ├── templates/         # K8s manifests
│       └── values/
│           ├── dev.yaml       # Dev-specific values
│           └── prod.yaml      # Prod-specific values
└── platform/
    └── argocd/
        └── argocd-cm.yaml    # ArgoCD configuration

2. ArgoCD Application Setup

Every service has an ArgoCD Application manifest that points to the Helm chart and the environment-specific values file. The Application definition lives in the GitOps repository, not in the cluster directly.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: webapp-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/gitops-repo
    targetRevision: main
    path: charts/webapp
    helm:
      valueFiles:
        - values/prod.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: webapp-prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 5
      backoff:
        duration: 5s
        maxDuration: 3m

3. Helm Values per Environment

Environment differences are explicit in values files. Never use conditionals based on environment name inside templates — the values file should contain all environment-specific configuration.

# values/prod.yaml
replicaCount: 3
image:
  tag: "1.4.2"          # pinned in prod
resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "500m"
autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

# values/dev.yaml
replicaCount: 1
image:
  tag: "latest"         # floating tag acceptable in dev
resources:
  requests:
    memory: "128Mi"
    cpu: "100m"
autoscaling:
  enabled: false

4. Sync Policies

We use automated sync with pruning and self-healing enabled on all environments. This eliminates configuration drift — any change not reflected in the repository is automatically reverted.

automated

ArgoCD syncs automatically when the Git repository changes. No manual sync required.

prune: true

Resources removed from Git are deleted from the cluster. No orphaned resources.

selfHeal: true

Manual changes to the cluster are reverted. Git is the single source of truth.

⚠️

Never use kubectl apply directly against ArgoCD-managed resources. ArgoCD will detect the drift and revert the change. All changes must go through a pull request to the GitOps repository.

5. Secret Management with OIDC

Secrets never live in the GitOps repository. We use the External Secrets Operator (ESO) to synchronize secrets from AWS Secrets Manager into Kubernetes Secret objects. The ESO uses OIDC federation — no static credentials.

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: webapp-db-credentials
  namespace: webapp-prod
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: prod/webapp/db
        property: password

Correct secret management flow

AWS Secrets Manager → External Secrets Operator (OIDC auth) → Kubernetes Secret → Pod environment variable. No secret value ever lives in the GitOps repository.