Repository Structure

Organizing your GitOps repository is crucial for maintainability, scalability, and team collaboration. A well-structured repository makes it easy to find resources, manage environments, and implement promotion workflows.

Repository Organization Patterns

Pattern 1: App-of-Apps

The App-of-Apps pattern uses a root application that manages multiple child applications.

graph TB A[Root Application] --> B[App 1] A --> C[App 2] A --> D[App 3] B --> E[Deployment] B --> F[Service] B --> G[ConfigMap] C --> H[StatefulSet] C --> I[PVC] style A fill:#e1f5ff style B fill:#e8f5e9 style C fill:#fff4e1 style D fill:#f3e5f5

Structure:

repo/
├── apps/
│   ├── app1/
│   │   └── application.yaml
│   ├── app2/
│   │   └── application.yaml
│   └── app3/
│       └── application.yaml
└── root/
    └── application.yaml  # Root app-of-apps

Root Application (ArgoCD):

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/repo
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd

Benefits:

  • Single point of management
  • Easy to add/remove applications
  • Centralized configuration

Pattern 2: Monorepo

All applications and infrastructure in a single repository.

monorepo/
├── apps/
│   ├── web-app/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── dev/
│   │       ├── staging/
│   │       └── production/
│   ├── api/
│   │   └── ...
│   └── worker/
│       └── ...
├── infrastructure/
│   ├── monitoring/
│   ├── logging/
│   └── ingress/
└── clusters/
    ├── dev/
    │   └── kustomization.yaml
    ├── staging/
    │   └── kustomization.yaml
    └── production/
        └── kustomization.yaml

Benefits:

  • Single source of truth
  • Easy cross-references
  • Atomic changes across apps

Limitations:

  • Larger repository size
  • Requires careful access control
  • Can become complex

Pattern 3: Multi-Repo

Separate repository per application or team.

org/
├── web-app-manifests/
│   ├── base/
│   └── overlays/
├── api-manifests/
│   └── ...
├── infrastructure/
│   └── ...
└── cluster-config/
    └── ...

Benefits:

  • Clear ownership
  • Independent versioning
  • Better access control
  • Smaller repositories

Limitations:

  • More repositories to manage
  • Cross-app changes require multiple PRs
  • Potential for inconsistency

Pattern 4: Environment-Based

Organize by environment first.

repo/
├── environments/
│   ├── dev/
│   │   ├── apps/
│   │   │   ├── web-app/
│   │   │   └── api/
│   │   └── infrastructure/
│   ├── staging/
│   │   └── ...
│   └── production/
│       └── ...
└── base/
    ├── web-app/
    └── api/

Benefits:

  • Clear environment separation
  • Easy environment promotion
  • Environment-specific configs

Standard Monorepo Structure

kubernetes-manifests/
├── README.md
├── .github/
│   └── workflows/
├── apps/
│   ├── web-app/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   ├── configmap.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── dev/
│   │       │   ├── kustomization.yaml
│   │       │   └── configmap-patch.yaml
│   │       ├── staging/
│   │       │   └── kustomization.yaml
│   │       └── production/
│   │           └── kustomization.yaml
│   └── api/
│       └── ...
├── infrastructure/
│   ├── monitoring/
│   │   ├── prometheus/
│   │   └── grafana/
│   ├── logging/
│   │   └── loki/
│   └── ingress/
│       └── nginx/
├── clusters/
│   ├── dev/
│   │   ├── apps.yaml
│   │   └── kustomization.yaml
│   ├── staging/
│   │   └── ...
│   └── production/
│       └── ...
└── base/
    └── common/
        └── namespace.yaml

Kustomize Base and Overlays

Base Structure

apps/web-app/base/
├── deployment.yaml
├── service.yaml
├── configmap.yaml
└── kustomization.yaml

base/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deployment.yaml
- service.yaml
- configmap.yaml

commonLabels:
  app: web-app
  managed-by: kustomize

Overlay Structure

apps/web-app/overlays/dev/
├── kustomization.yaml
└── configmap-patch.yaml

overlays/dev/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: dev

resources:
- ../../base

replicas:
- name: web-app
  count: 1

images:
- name: web-app
  newTag: dev-latest

patches:
- path: configmap-patch.yaml
  target:
    kind: ConfigMap
    name: web-app-config

overlays/dev/configmap-patch.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-app-config
data:
  environment: dev
  log_level: debug

Directory Layout Examples

Example 1: Simple Structure

manifests/
├── apps/
│   ├── app1.yaml
│   └── app2.yaml
└── infrastructure/
    └── monitoring.yaml

Example 2: Kustomize Structure

manifests/
├── apps/
│   └── web-app/
│       ├── base/
│       └── overlays/
│           ├── dev/
│           └── prod/
└── infrastructure/
    └── ...

Example 3: Helm Structure

manifests/
├── apps/
│   └── web-app/
│       ├── values-dev.yaml
│       ├── values-staging.yaml
│       └── values-prod.yaml
└── charts/
    └── web-app/
        └── ...

Secrets Management

Approach 1: External Secrets Operator

manifests/
├── apps/
│   └── web-app/
│       ├── deployment.yaml
│       └── external-secret.yaml
└── secrets/
    └── templates/
        └── web-app-secret.yaml

Approach 2: Sealed Secrets

manifests/
├── apps/
│   └── web-app/
│       ├── deployment.yaml
│       └── sealed-secret.yaml

Approach 3: Separate Secrets Repo

Keep secrets in a separate, private repository:

secrets-repo/
├── dev/
│   └── web-app-secret.yaml
└── production/
    └── web-app-secret.yaml

Best Practices

1. Use Kustomize for Environment Differences

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deployment.yaml
- service.yaml

# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

replicas:
- name: web-app
  count: 5

images:
- name: web-app
  newTag: v1.2.3

2. Separate Base from Overlays

  • Base - Common configuration
  • Overlays - Environment-specific changes

3. Use Consistent Naming

apps/
├── web-app/          # kebab-case
├── api-service/      # descriptive
└── worker-job/       # clear purpose

4. Document Structure

Create README files:

apps/web-app/
├── README.md
├── base/
└── overlays/

README.md:

# Web App

Application for serving web traffic.

## Structure

- `base/` - Base manifests
- `overlays/` - Environment-specific configs

## Deployment

Deploy to dev:
```bash
kubectl apply -k overlays/dev

### 5. Version Control Best Practices

- Use meaningful commit messages
- Tag releases
- Use branches for features
- Protect main branch

### 6. Keep Manifests DRY

Use Kustomize or Helm to avoid duplication:

```yaml
# Bad: Duplicated manifests
apps/web-app/dev/deployment.yaml
apps/web-app/staging/deployment.yaml
apps/web-app/prod/deployment.yaml

# Good: Base + overlays
apps/web-app/base/deployment.yaml
apps/web-app/overlays/dev/kustomization.yaml
apps/web-app/overlays/prod/kustomization.yaml

Common Anti-Patterns

Anti-Pattern 1: Flat Structure

# Bad
manifests/
├── app1-dev.yaml
├── app1-prod.yaml
├── app2-dev.yaml
└── app2-prod.yaml

Problem: Hard to maintain, lots of duplication

Solution: Use base + overlays

Anti-Pattern 2: No Separation

# Bad
manifests/
├── everything.yaml

Problem: Hard to find resources, large files

Solution: Organize by app and resource type

Anti-Pattern 3: Hardcoded Values

# Bad
apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 3  # Hardcoded
  template:
    spec:
      containers:
      - image: myapp:latest  # Hardcoded tag

Problem: Can’t vary by environment

Solution: Use Kustomize or Helm

Anti-Pattern 4: Secrets in Git

# Bad - Never do this!
apiVersion: v1
kind: Secret
data:
  password: cGFzc3dvcmQ=  # Base64 encoded, but still in Git

Problem: Security risk

Solution: Use Sealed Secrets, External Secrets, or separate secrets repo

Repository Structure for ArgoCD

ArgoCD App-of-Apps

repo/
├── apps/
│   ├── web-app/
│   │   └── application.yaml
│   └── api/
│       └── application.yaml
└── root/
    └── application.yaml

ArgoCD ApplicationSet

repo/
├── apps/
│   ├── web-app/
│   │   ├── base/
│   │   └── overlays/
│   └── api/
│       └── ...
└── applicationset.yaml

Repository Structure for Flux

Flux Structure

repo/
├── clusters/
│   └── production/
│       ├── flux-system/
│       └── apps/
│           └── web-app/
│               └── kustomization.yaml
└── apps/
    └── web-app/
        ├── base/
        └── overlays/

Flux Kustomization

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: web-app
  namespace: flux-system
spec:
  interval: 5m0s
  path: ./apps/web-app/overlays/production
  prune: true
  sourceRef:
    kind: GitRepository
    name: manifests

Summary

A well-organized GitOps repository:

  • Separates concerns - Apps, infrastructure, clusters
  • Uses base + overlays - Avoids duplication
  • Organizes by purpose - Easy to find resources
  • Manages secrets securely - Never in Git
  • Documents structure - README files
  • Follows conventions - Consistent naming

Choose a structure that fits your team:

  • Monorepo - Single repository, easier collaboration
  • Multi-repo - Separate repos, better isolation
  • App-of-Apps - Centralized management
  • Environment-based - Clear environment separation

The key is consistency and maintainability. Start simple and evolve as needed.