Best Practices
This guide covers recommended patterns and best practices for using Nyl with ArgoCD in production environments.
Directory Structure
Section titled “Directory Structure”Recommended Layout
Section titled “Recommended Layout”Organize your GitOps repository with clear separation of concerns:
gitops-repo/├── bootstrap/│ └── bootstrap.yaml # Initial bootstrap manifest├── argocd/│ └── argocd.yaml # ArgoCD installation├── apps.yaml # ApplicationGenerator for apps├── clusters/│ ├── production/│ │ ├── app1.yaml│ │ ├── app2.yaml│ │ └── app3.yaml│ ├── staging/│ │ ├── app1.yaml│ │ └── app2.yaml│ └── development/│ └── app1.yaml├── base/ # Shared base configurations│ ├── postgres/│ └── redis/├── nyl.toml└── nyl-secrets.yamlDirectory Organization Strategies
Section titled “Directory Organization Strategies”By Environment:
clusters/├── production/├── staging/└── development/- Clear separation of environments
- Easy to apply different policies per environment
- Suitable for ApplicationGenerator scanning
By Team:
teams/├── platform/├── data/└── ml/- Delegation to team-specific directories
- Team-level RBAC with ArgoCD Projects
- Enables team autonomy
By Application Type:
├── core-services/├── data-services/├── monitoring/└── security/- Logical grouping by function
- Useful for dependency management
- Clear categorization
Hybrid Approach (Recommended):
├── core/ # Critical platform services│ ├── argocd/│ ├── cert-manager/│ └── ingress-nginx/├── clusters/│ ├── production/│ │ ├── team-a/│ │ └── team-b/│ └── staging/│ ├── team-a/│ └── team-b/└── shared/ # Shared configurations ├── postgres/ └── redis/ApplicationGenerator Patterns
Section titled “ApplicationGenerator Patterns”One Generator Per Environment
Section titled “One Generator Per Environment”Create separate ApplicationGenerators for each environment:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1kind: ApplicationGeneratormetadata: name: production-appsspec: destination: server: https://kubernetes.default.svc namespace: argocd source: repoURL: https://github.com/myorg/gitops.git targetRevision: main path: clusters/production project: production syncPolicy: automated: prune: true selfHeal: true labels: environment: production---# apps-staging.yamlapiVersion: argocd.nyl.niklasrosenstein.github.com/v1kind: ApplicationGeneratormetadata: name: staging-appsspec: destination: server: https://kubernetes.default.svc namespace: argocd source: repoURL: https://github.com/myorg/gitops.git targetRevision: main path: clusters/staging project: staging syncPolicy: automated: selfHeal: true # Note: No prune in staging labels: environment: stagingOne App Per File
Section titled “One App Per File”Each application should have its own YAML file:
clusters/production/├── nginx.yaml # One app├── postgres.yaml # One app└── redis.yaml # One appBenefits:
- Clear ownership and Git history per app
- Easy to move apps between environments
- Better for code review
- Simpler conflict resolution
Avoid putting multiple apps in one file (unless they’re tightly coupled).
Exclude Patterns
Section titled “Exclude Patterns”Use exclude patterns to prevent accidental Application generation:
spec: source: exclude: - ".*" # Hidden files - "_*" # Underscore prefix (templates, WIP) - "*.backup" # Backup files - "test-*" # Test files - "apps.yaml" # The generator itself - "README.md" # DocumentationSync Policies
Section titled “Sync Policies”Production Environments
Section titled “Production Environments”For production, be conservative:
syncPolicy: automated: prune: true # Delete removed resources selfHeal: true # Force Git state syncOptions: - CreateNamespace=true - PruneLast=true # Prune after other operationsWhy?
prune: true: Ensures removed manifests are deletedselfHeal: true: Prevents manual changes (drift detection)PruneLast: Safer deletion order
Staging/Development Environments
Section titled “Staging/Development Environments”For non-production, allow more flexibility:
syncPolicy: automated: prune: false # Don't auto-delete (allow manual testing) selfHeal: true # Still enforce Git stateWhy?
prune: false: Allows temporary manual resources for testingselfHeal: true: Still prevents accidental drift
Manual Sync for Critical Services
Section titled “Manual Sync for Critical Services”For very critical services (databases, auth), consider manual sync:
# No automated syncPolicy# Sync manually via ArgoCD UI or CLISecret Management
Section titled “Secret Management”Option 1: Sealed Secrets
Section titled “Option 1: Sealed Secrets”Use Bitnami Sealed Secrets for encrypted secrets in Git:
apiVersion: nyl.niklasrosenstein.github.com/v1kind: NylReleasemetadata: name: app namespace: default---apiVersion: v1kind: SealedSecretmetadata: name: app-secrets namespace: defaultspec: encryptedData: password: AgBjW8X... # Encrypted data safe for GitOption 2: External Secrets Operator
Section titled “Option 2: External Secrets Operator”Sync secrets from external providers (AWS Secrets Manager, Vault, etc.):
apiVersion: external-secrets.io/v1beta1kind: ExternalSecretmetadata: name: app-secrets namespace: defaultspec: secretStoreRef: name: aws-secrets-manager target: name: app-secrets data: - secretKey: password remoteRef: key: /app/passwordOption 3: Nyl Secrets (SOPS)
Section titled “Option 3: Nyl Secrets (SOPS)”Use Nyl’s built-in SOPS integration:
type: sopspath: ./.secrets.yaml
# .secrets.yaml (encrypted with SOPS)database: password: ENC[AES256_GCM,data:xxx,iv:yyy,tag:zzz]Never commit plaintext secrets to Git!
Multi-Cluster Setup
Section titled “Multi-Cluster Setup”Hub-and-Spoke Model
Section titled “Hub-and-Spoke Model”Manage multiple clusters from a central ArgoCD instance:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1kind: ApplicationGeneratormetadata: name: prod-cluster1-appsspec: destination: server: https://cluster1.example.com:6443 namespace: argocd source: repoURL: https://github.com/myorg/gitops.git path: clusters/cluster1 labels: cluster: cluster1---# apps-prod-cluster2.yamlapiVersion: argocd.nyl.niklasrosenstein.github.com/v1kind: ApplicationGeneratormetadata: name: prod-cluster2-appsspec: destination: server: https://cluster2.example.com:6443 namespace: argocd source: repoURL: https://github.com/myorg/gitops.git path: clusters/cluster2 labels: cluster: cluster2Register clusters in ArgoCD:
argocd cluster add cluster1-contextargocd cluster add cluster2-contextPer-Cluster ArgoCD
Section titled “Per-Cluster ArgoCD”Each cluster has its own ArgoCD instance:
gitops-repo/├── clusters/│ ├── prod-us-east/│ │ ├── argocd/│ │ ├── apps.yaml # ApplicationGenerator for this cluster│ │ └── apps/│ └── prod-eu-west/│ ├── argocd/│ ├── apps.yaml│ └── apps/Each cluster bootstraps independently.
Monitoring and Observability
Section titled “Monitoring and Observability”Prometheus Metrics
Section titled “Prometheus Metrics”ArgoCD exports metrics; monitor these key indicators:
argocd_app_info: Application statusargocd_app_sync_total: Sync operationsargocd_app_health_status: Health status
Alerts
Section titled “Alerts”Set up alerts for:
# OutOfSync alert- alert: ArgoCDAppOutOfSync expr: argocd_app_sync_status{sync_status="OutOfSync"} == 1 for: 15m labels: severity: warning
# Degraded health- alert: ArgoCDAppUnhealthy expr: argocd_app_health_status{health_status!="Healthy"} == 1 for: 10m labels: severity: criticalNotifications
Section titled “Notifications”Configure ArgoCD notifications for Slack, email, or PagerDuty:
apiVersion: v1kind: ConfigMapmetadata: name: argocd-notifications-cm namespace: argocddata: service.slack: | token: $slack-token template.app-sync-status: | message: Application {{.app.metadata.name}} sync is {{.app.status.sync.status}} trigger.on-sync-failed: | - when: app.status.sync.status == 'Failed' send: [app-sync-status]CI/CD Integration
Section titled “CI/CD Integration”Validation Pipeline
Section titled “Validation Pipeline”Add CI checks to validate Nyl manifests before merge:
name: Validate Manifestson: [pull_request]jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Nyl run: | curl -L https://github.com/NiklasRosenstein/nyl/releases/latest/download/nyl-linux-amd64 \ -o /usr/local/bin/nyl chmod +x /usr/local/bin/nyl - name: Validate run: nyl validate . - name: Render run: nyl render apps.yaml > /dev/nullDry-Run Rendering
Section titled “Dry-Run Rendering”Test ApplicationGenerator output in CI:
# Render and check outputnyl render apps.yaml > output.yaml
# Verify Applications were generatedcat output.yaml | grep "kind: Application" | wc -l
# Check specific apps existgrep "name: nginx" output.yamlgrep "name: postgres" output.yamlPreview Environments
Section titled “Preview Environments”Create preview environments for PRs:
name: Preview Environmenton: [pull_request]jobs: preview: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy Preview run: | # Create preview namespace kubectl create namespace pr-${{ github.event.pull_request.number }}
# Render manifests with preview profile nyl render -e preview apps.yaml | kubectl apply -n pr-${{ github.event.pull_request.number }} -f -Performance Considerations
Section titled “Performance Considerations”Repository Size
Section titled “Repository Size”Keep repositories focused:
- Separate infrastructure repo from application repos
- Use Git submodules for shared configurations if needed
- Archive old applications to separate branches
Sync Frequency
Section titled “Sync Frequency”Adjust sync frequency based on needs:
# Faster sync for developmentmetadata: annotations: argocd.argoproj.io/sync-options: "Timeout=120"
# Slower sync for large appsmetadata: annotations: argocd.argoproj.io/sync-options: "Timeout=600"Resource Limits
Section titled “Resource Limits”Set appropriate resource limits for ArgoCD components:
spec: values: repoServer: resources: limits: cpu: "1000m" memory: "1Gi" requests: cpu: "500m" memory: "512Mi"Disaster Recovery
Section titled “Disaster Recovery”Backup Strategy
Section titled “Backup Strategy”- Git is Source of Truth: All manifests in Git
- Backup ArgoCD Configuration:
Terminal window kubectl get applications -n argocd -o yaml > argocd-apps-backup.yamlkubectl get appprojects -n argocd -o yaml > argocd-projects-backup.yaml - Cluster Backups: Use Velero or similar for cluster state
Recovery Procedure
Section titled “Recovery Procedure”- Restore cluster from backup (if needed)
- Re-apply bootstrap manifest:
Terminal window kubectl apply -f bootstrap.yaml - ArgoCD recreates all Applications from Git
- Applications sync and restore workloads
Why this works: Git is the source of truth; ArgoCD recreates everything from Git automatically.
Security
Section titled “Security”RBAC with Projects
Section titled “RBAC with Projects”Use ArgoCD Projects for RBAC:
apiVersion: argoproj.io/v1alpha1kind: AppProjectmetadata: name: team-a namespace: argocdspec: description: Team A applications sourceRepos: - https://github.com/myorg/gitops.git destinations: - namespace: team-a-* server: https://kubernetes.default.svc clusterResourceWhitelist: - group: '' kind: Namespace namespaceResourceWhitelist: - group: '*' kind: '*'Signed Commits
Section titled “Signed Commits”Require GPG-signed commits for production:
spec: source: repoURL: https://github.com/myorg/gitops.git targetRevision: main # In ArgoCD settings metadata: annotations: argocd.argoproj.io/verify-signature: "true"Least Privilege
Section titled “Least Privilege”- ArgoCD service account should have minimal permissions
- Use separate service accounts per ApplicationGenerator/Project
- Audit ArgoCD RBAC regularly
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”Issue: Applications not syncing
- Check: ArgoCD repo-server logs
- Fix: Ensure Nyl plugin is installed and accessible
Issue: Wrong Applications generated
- Check: Render manifests locally:
nyl render apps.yaml - Fix: Adjust include/exclude patterns
Issue: Sync timeouts
- Check: Large Helm charts or slow cluster
- Fix: Increase timeout annotation
Debug Commands
Section titled “Debug Commands”# View ApplicationGenerator outputnyl render apps.yaml
# Check ArgoCD Applicationargocd app get <app-name>
# View rendered manifestsargocd app manifests <app-name>
# Force refreshargocd app get <app-name> --refresh
# View repo-server logskubectl logs deployment/argocd-repo-server -n argocd -fSummary
Section titled “Summary”Key takeaways:
- ✅ Use clear directory structure
- ✅ One ApplicationGenerator per environment
- ✅ One app per file
- ✅ Configure appropriate sync policies
- ✅ Never commit plaintext secrets
- ✅ Monitor and alert on sync status
- ✅ Validate in CI before merge
- ✅ Git is your source of truth
Following these practices ensures reliable, secure, and scalable GitOps with Nyl and ArgoCD.