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.