Explanation

Extra Manifests Organization Strategy

Learn why and how the KUP6S extra manifests are organized into semantic groups.

The Problem

Infrastructure-as-code manifests can become difficult to manage as projects grow. Common challenges include:

  1. Hidden Dependencies: Numeric ordering (01, 02, 03…) obscures functional relationships

  2. Insertion Conflicts: Adding new manifests requires renumbering to maintain order

  3. Unclear Grouping: Related components scattered across different numbers

  4. Scalability: Running out of numbers in tightly-packed sequences

Example of the old system:

01-namespace.yaml
02-csi-driver-smb.yaml
03-smb-storageclass.yaml
04-cert-manager.yaml
...
18-external-secrets-operator.yaml

What happens when you need to add another storage component between 03 and 04? Renumber everything? Create 03b? Neither scales well.

The Solution: Semantic Grouping

KUP6S reorganized manifests into semantic groups with letter-based sub-ordering:

GROUP-LETTER-description.yaml.tpl

Where:

  • GROUP: Numeric group (10, 20, 30, etc.) representing functional category

  • LETTER: Uppercase letter (A, B, C, D…) for ordering within group

  • description: Descriptive component name

Example of the new system:

10-A-namespaces.yaml.tpl         # Group 10: Foundation
20-A-cert-manager.yaml.tpl       # Group 20: Security
20-B-external-secrets-operator.yaml.tpl
20-C-application-secrets.yaml.tpl
30-A-traefik-basicauth.yaml.tpl  # Group 30: Networking
30-B-traefik-dashboard.yaml.tpl
40-A-csi-driver-smb.yaml.tpl     # Group 40: Storage
40-B-smb-storageclass.yaml.tpl
...

Group Definitions

Group 10 - Foundation

Purpose: Core infrastructure that must exist before anything else

Components:

  • Namespaces for all infrastructure services

Why first: All namespaced Kubernetes resources require their namespace to exist

Group 20 - Security & Secrets

Purpose: Security infrastructure and secret management

Components:

  • cert-manager (TLS certificates)

  • External Secrets Operator (secret synchronization)

  • ClusterSecretStore resources

Why second: Security components should be established before networking and applications

Critical ordering: ESO operator (20-B) must deploy before ClusterSecretStore (20-C)

Group 30 - Networking

Purpose: Ingress controllers and network infrastructure

Components:

  • Traefik authentication middleware

  • Traefik dashboard ingress

Why after security: Ingresses require TLS certificates from cert-manager (Group 20)

Group 40 - Storage

Purpose: Persistent storage infrastructure

Components:

  • SMB CSI driver for Hetzner Storage Box

  • SMB StorageClass

  • Longhorn backup configuration

  • Longhorn recurring backups

  • Longhorn ingress

  • Longhorn storage classes

Why mid-stack: Storage is independent but needed before stateful applications

Group 50 - Provisioning

Purpose: Infrastructure-as-code operators for dynamic resource provisioning

Components:

  • Crossplane operator

  • Crossplane AWS S3 provider (configured for Hetzner)

  • etcd backup bucket (DR region)

  • Loki S3 bucket (production region)

Why before observability: Monitoring stack (Group 70) needs S3 buckets for Loki logs

Critical ordering: Crossplane (50-A) → Provider (50-B) → Buckets (50-C, 50-D)

Group 70 - Observability

Purpose: Monitoring, logging, and alerting infrastructure

Components:

  • kube-prometheus-stack (Prometheus, Grafana, Loki, Alertmanager, Alloy)

Why after provisioning: Loki requires S3 bucket from Group 50

Group 80 - GitOps

Purpose: Declarative application deployment tools

Components:

  • ArgoCD for GitOps-based deployments

Why last: ArgoCD deploys applications after infrastructure is ready

Benefits

1. Clear Functional Grouping

Related components naturally cluster together:

40-A-csi-driver-smb.yaml.tpl
40-B-smb-storageclass.yaml.tpl
40-C-longhorn-backup-secret.yaml.tpl
40-D-longhorn-recurring-backup.yaml.tpl
40-E-longhorn-ingress.yaml.tpl
40-F-longhorn-storage-classes.yaml.tpl

All storage components are in Group 40, making it obvious they’re related.

2. Easy Insertion

Adding a new component doesn’t require renumbering:

Old system (need to add cert-renewer between cert-manager and traefik):

04-cert-manager.yaml          # Exists
04b-cert-renewer.yaml?        # Ugly workaround
05-traefik-basicauth.yaml     # Or renumber everything below?

New system (just pick next letter in Group 20):

20-A-cert-manager.yaml.tpl
20-B-external-secrets-operator.yaml.tpl
20-C-application-secrets.yaml.tpl
20-D-cert-renewer.yaml.tpl    # ← Clean insertion

3. Explicit Dependencies

Group numbers make cross-group dependencies obvious:

# 70-A-kube-prometheus-stack.yaml.tpl
# Dependencies:
# - 50-D-loki-s3-bucket.yaml.tpl (S3 storage for Loki)

Group 70 depending on Group 50 is immediately clear: observability needs provisioning.

4. Room to Grow

Each group has 26 available letters (A-Z). That’s plenty of space for future additions without renumbering.

Want to add monitoring tools? Just use 70-B, 70-C, 70-D…

5. Better Git History

When adding new manifests, git diffs show only the new file, not renumbering changes:

Old system:

- 05-traefik.yaml
+ 05-cert-renewer.yaml
+ 06-traefik.yaml  # Renamed - git shows deletion+addition

New system:

+ 20-D-cert-renewer.yaml  # Clean addition, no renames

Migration from Old to New

The complete rename mapping:

Old

New

Group

01-namespace.yaml

10-A-namespaces.yaml.tpl

Foundation

04-cert-manager.yaml

20-A-cert-manager.yaml.tpl

Security

18-external-secrets-operator.yaml

20-B-external-secrets-operator.yaml.tpl

Security

09-application-secrets-eso.yaml

20-C-application-secrets.yaml.tpl

Security

05-traefik-basicauth.yaml

30-A-traefik-basicauth.yaml.tpl

Networking

06-traefik-dashboard.yaml

30-B-traefik-dashboard.yaml.tpl

Networking

02-csi-driver-smb.yaml

40-A-csi-driver-smb.yaml.tpl

Storage

03-smb-storageclass.yaml

40-B-smb-storageclass.yaml.tpl

Storage

11-longhorn-backup.yaml

40-C-longhorn-backup-secret.yaml.tpl

Storage

10-longhorn-recurring-backup.yaml

40-D-longhorn-recurring-backup.yaml.tpl

Storage

12-longhorn-ingress.yaml

40-E-longhorn-ingress.yaml.tpl

Storage

13-longhorn-storage-classes.yaml

40-F-longhorn-storage-classes.yaml.tpl

Storage

07-crossplane.yaml

50-A-crossplane.yaml.tpl

Provisioning

08-crossplane-provider-aws-s3.yaml

50-B-crossplane-provider-s3.yaml.tpl

Provisioning

(new)

50-C-etcd-backup-bucket.yaml.tpl

Provisioning

09-loki-s3-bucket.yaml

50-D-loki-s3-bucket.yaml.tpl

Provisioning

14-kube-prometheus-stack.yaml

70-A-kube-prometheus-stack.yaml.tpl

Observability

16-argocdchart.yaml

80-A-argocd.yaml.tpl

GitOps

Note: All files renamed using git mv to preserve history.

Design Philosophy

Why Groups of 10?

Groups increment by 10 (10, 20, 30…) to leave room for future category insertion:

10 - Foundation
20 - Security
25 - ??? (future category between Security and Networking)
30 - Networking

This is unlikely to be needed, but the flexibility costs nothing.

Why Uppercase Letters?

Uppercase letters (A, B, C) provide better visual distinction in file listings compared to lowercase:

Better readability:
40-A-csi-driver-smb.yaml.tpl
40-B-smb-storageclass.yaml.tpl

vs.

40-a-csi-driver-smb.yaml.tpl  # Lowercase harder to spot
40-b-smb-storageclass.yaml.tpl

Why .tpl Extension?

The .tpl extension explicitly marks files as OpenTofu templates containing variable placeholders like ${variable}.

This prevents accidentally treating them as direct kubectl-applicable manifests.

Best Practices

Adding a New Manifest

  1. Choose the right group: What functional category does this belong to?

  2. Check dependencies: Does it depend on other groups? Should it go after them?

  3. Pick next letter: Find the next available letter in that group

  4. Use descriptive names: 50-B-crossplane-provider-s3.yaml.tpl is better than 50-B-cp.yaml.tpl

  5. Update kustomization.yaml.tpl: Add to appropriate group section

  6. Document: Add entry to extra-manifests.md

Respecting Group Boundaries

Don’t: Mix components from different functional groups

Bad:
40-A-csi-driver-smb.yaml.tpl      # Storage
40-B-smb-storageclass.yaml.tpl    # Storage
40-C-prometheus.yaml.tpl          # ❌ Monitoring - wrong group!

Do: Keep groups functionally cohesive

Good:
40-A-csi-driver-smb.yaml.tpl      # Storage
40-B-smb-storageclass.yaml.tpl    # Storage
...
70-A-kube-prometheus-stack.yaml.tpl  # ✅ Monitoring in correct group

Ordering Within Groups

Letters (A, B, C…) indicate deployment order within a group. Respect dependencies:

50-A-crossplane.yaml.tpl          # Operator first
50-B-crossplane-provider-s3.yaml.tpl  # Provider second (needs operator)
50-C-etcd-backup-bucket.yaml.tpl  # Bucket third (needs provider)
50-D-loki-s3-bucket.yaml.tpl      # Another bucket (needs provider)

See Also