Skip to main content

GitOps Directory Structure β€” Design Decisions

This page explains the directory layout chosen for service overlays on this platform, and the architectural reasoning behind it.


The two common patterns​

Pattern A β€” flat overlay per environment​

services/<service>/
β”œβ”€β”€ base/
└── overlays/
β”œβ”€β”€ dev/
β”œβ”€β”€ staging/
└── prod/

The overlay path encodes one dimension: the environment. One cluster is implied β€” the overlay targets a namespace, and namespaces are isolated within the single cluster.

Pattern B β€” environment Γ— cluster nesting​

services/<service>/
β”œβ”€β”€ dev/
β”‚ β”œβ”€β”€ cluster-1/
β”‚ └── cluster-2/
β”œβ”€β”€ staging/
β”‚ └── cluster-1/
└── prod/
β”œβ”€β”€ cluster-1/ ← primary
└── cluster-2/ ← DR / secondary region

The path encodes two dimensions: environment and target cluster. The directory path is the full deployment address β€” it answers both "which environment?" and "which cluster?".


Why this platform uses Pattern A​

This platform runs a single k3s cluster. Pattern B's cluster dimension would add a directory level (cluster-1/) that never branches β€” it would always be one entry and exist purely as scaffolding. A structure that can never vary in practice carries no information and only adds path length.

Pattern A maps cleanly to the existing isolation model:

OverlayNamespaceCluster
overlays/dev/platform-demo-devk3s (only one)
overlays/staging/platform-demo-stagingk3s (only one)
overlays/prod/gitops-demok3s (only one)

The ArgoCD Application source.path field reads the overlay directory directly. The deployment address is fully expressed by the combination of path (environment) + destination.namespace.


When Pattern B becomes the right choice​

Pattern B pays off when at least one of these is true:

Multiple physical clusters exist. If you add an Azure AKS cluster as a cloud burst or DR target alongside the on-prem cluster, two clusters serve the same environment. The overlay path needs to distinguish them:

overlays/prod/on-prem/ ← k3s, fast-heron/fast-skunk
overlays/prod/cloud/ ← AKS, westeurope

A diff overlays/prod/on-prem/ overlays/prod/cloud/ immediately shows any config drift between primary and DR. Without the cluster dimension, this diff is impossible to express in the directory structure.

CI/CD routes to different API servers based on path. In a multi-cluster setup, the deployment pipeline must know which kubeconfig to use. The simplest routing key is the directory path segment β€” the pipeline reads prod/cloud/ and selects the AKS credential, reads prod/on-prem/ and selects the k3s kubeconfig. The path becomes the routing table, not a config file.

Drift between clusters is a compliance concern. Regulated environments (financial services, healthcare) often require proof that production workloads are identical across regions. A directory-per-cluster structure makes cross-region diffs auditable in every pull request.


The design principle​

The directory path should be the minimum unique identifier for a deployment unit β€” no more, no less.

On a single cluster, environment + service name is sufficient. Adding cluster is over-specification. On a multi-cluster platform, environment + cluster + service name is the minimum identifier β€” omitting the cluster dimension makes the path ambiguous.

Apply the same logic to any new dimension before adding it. A region segment (eu/, us/) only belongs in the path if you actually deploy the same service to multiple regions independently and need to track them separately. If every service always deploys to both regions together, a region segment adds noise without enabling any useful diff or routing.


Promotion flow (current, single cluster)​

CI push to main
└── kustomize edit set image β†’ overlays/dev/

PR to bump tag β†’ overlays/staging/
└── manual ArgoCD sync β†’ platform-demo-staging namespace

PR to bump tag β†’ overlays/prod/
└── manual ArgoCD sync β†’ gitops-demo namespace

If a second cluster were added, the staging and prod PRs would target overlays/staging/on-prem/ and overlays/staging/cloud/ independently, and each would trigger a sync against the corresponding cluster's ArgoCD instance or kubeconfig.


Summary​

ConcernSingle clusterMulti-cluster
Structureoverlays/dev/staging/prod/overlays/<env>/<cluster>/
Path encodesenvironmentenvironment + target cluster
Drift detectionnamespace isolationcross-cluster directory diff
CI routingfixed kubeconfigpath-based credential selection
When to migrateWhen second cluster is addedN/A

The current flat structure is the correct choice and requires no changes until a second Kubernetes cluster joins the platform.