infra/charts/cert-manager-stack

cert-manager-stack

A wrapper Helm chart that bundles cert-manager with pre-configured Cloudflare DNS-01 solver and Let's Encrypt ClusterIssuer for GitOps deployments.

📋 Overview

This chart provides a production-ready cert-manager deployment by:

  • Using cert-manager as a dependency (pristine upstream chart, easily upgradable)
  • Adding custom resources (Cloudflare API Secret, Let's Encrypt ClusterIssuer)
  • Single configuration file (manifests/cert-manager-stack/values.yaml)
  • GitOps-friendly (works seamlessly with ArgoCD)

🏗️ Architecture

cert-manager-stack (wrapper chart)
├── Dependency: cert-manager (embedded in charts/cert-manager/)
│   ├── CRDs (Certificate, ClusterIssuer, etc.)
│   ├── cert-manager controller
│   ├── cert-manager webhook
│   └── cert-manager cainjector
└── Custom Resources (from wrapper templates)
    ├── Secret: cloudflare-api-token
    └── ClusterIssuer: letsencrypt

📁 Files Structure

charts/cert-manager-stack/
├── Chart.yaml                                   # Wrapper chart definition
├── values.yaml                                  # Default values (DO NOT EDIT)
├── charts/                                      # Embedded dependencies
│   └── cert-manager/                           # cert-manager chart
│       ├── Chart.yaml
│       ├── values.yaml
│       └── templates/
├── templates/
│   ├── _helpers.tpl                            # Template helpers
│   ├── cloudflare-api-token-secret.yaml        # Cloudflare API Secret
│   ├── clusterissuer-letsencrypt.yaml          # Let's Encrypt ClusterIssuer
│   └── NOTES.txt                               # Post-install notes
└── README.md                                    # This file

manifests/cert-manager-stack/
└── values.yaml                                  # ✏️ EDIT THIS FILE

⚙️ Configuration

Single Source of Truth

manifests/cert-manager-stack/values.yaml is the only file you need to edit.

Configuration Structure

# cert-manager upstream chart values
certManager:
  enabled: true
  crds:
    enabled: true
  prometheus:
    enabled: false

# Cloudflare DNS provider
cloudflare:
  enabled: true
  apiToken: "YOUR_CLOUDFLARE_API_TOKEN"
  secretName: cloudflare-api-token
  namespace: cert-manager

# Let's Encrypt ClusterIssuer
clusterIssuer:
  enabled: true
  name: letsencrypt
  email: dvirlabs@gmail.com
  server: https://acme-v02.api.letsencrypt.org/directory

🚀 ArgoCD Integration

Create an ArgoCD Application:

# argocd-apps/cert-manager-stack.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager-stack
  namespace: argocd
spec:
  project: default
  source:
    repoURL: <your-git-repo>
    targetRevision: HEAD
    path: charts/cert-manager-stack
    helm:
      valueFiles:
        - ../../manifests/cert-manager-stack/values.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: cert-manager
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

ArgoCD will:

  1. Read the wrapper chart from charts/cert-manager-stack/
  2. Load cert-manager dependency from embedded charts/cert-manager-stack/charts/cert-manager/
  3. Apply values from manifests/cert-manager-stack/values.yaml
  4. Deploy everything as a unified Helm release

📦 Installation

Manual Installation (for testing)

# Navigate to chart directory
cd charts/cert-manager-stack

# Update dependencies
helm dependency update

# Install
helm install cert-manager-stack . \
  --namespace cert-manager \
  --create-namespace \
  --values ../../manifests/cert-manager-stack/values.yaml
  1. Update your Cloudflare API token in manifests/cert-manager-stack/values.yaml
  2. Commit and push to Git
  3. ArgoCD will automatically sync and deploy

🔄 Upgrading cert-manager

To upgrade to a newer cert-manager version:

  1. Replace the embedded cert-manager chart in charts/cert-manager-stack/charts/cert-manager/
  2. Edit charts/cert-manager-stack/Chart.yaml
  3. Update the dependency version to match:
    dependencies:
      - name: cert-manager
        version: "v1.21.0"  # Update this to match embedded chart version
        alias: certManager
    
  4. Commit and push
  5. ArgoCD will handle the upgrade

Why This Approach?

What We're NOT Doing:

  • Forking/modifying the upstream cert-manager chart
  • Manual kubectl apply for Secret/ClusterIssuer
  • Embedding resources in cert-manager's values
  • Using hacks like extraObjects

What We ARE Doing:

  • Clean dependency management - cert-manager stays pristine
  • Single Helm release - all resources managed together
  • GitOps native - no manual steps
  • Helm best practices - proper dependency and values structure
  • Easy upgrades - just bump the version number
  • Migration-ready - clean path to External Secrets/Vault

Benefits Over Modifying Upstream Chart:

  1. Upgradability: Can upgrade cert-manager without merge conflicts
  2. Clarity: Separation between upstream and custom resources
  3. Maintainability: Upstream chart bugs/fixes don't affect custom logic
  4. Reusability: Can apply same pattern to other charts
  5. Audibility: Clear distinction in Git history

📝 Usage Examples

Create a Certificate

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-app-tls
  namespace: my-app
spec:
  secretName: my-app-tls-secret
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
  dnsNames:
    - myapp.example.com
    - "*.myapp.example.com"

Use with Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  tls:
    - hosts:
        - myapp.example.com
      secretName: my-app-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  number: 80

🔐 Migrating to External Secrets

When ready to move from raw Secrets to External Secrets:

  1. Create a new template templates/cloudflare-external-secret.yaml:

    {{- if .Values.cloudflare.useExternalSecret }}
    apiVersion: external-secrets.io/v1beta1
    kind: ExternalSecret
    metadata:
      name: cloudflare-api-token
      namespace: cert-manager
    spec:
      secretStoreRef:
        name: vault-backend
        kind: ClusterSecretStore
      target:
        name: cloudflare-api-token
      data:
        - secretKey: api-token
          remoteRef:
            key: cloudflare/api-token
    {{- end }}
    
  2. Update manifests/cert-manager-stack/values.yaml:

    cloudflare:
      enabled: false              # Disable raw Secret
      useExternalSecret: true     # Enable ExternalSecret
      secretName: cloudflare-api-token  # Keep same name
    
  3. No changes needed to ClusterIssuer (references same secret name)

🔍 Troubleshooting

Check cert-manager logs

kubectl logs -n cert-manager deploy/cert-manager-stack-certManager

Check ClusterIssuer status

kubectl describe clusterissuer letsencrypt
kubectl get clusterissuer letsencrypt -o yaml

Check Certificate status

kubectl describe certificate my-app-tls -n my-app
kubectl get certificaterequest -n my-app

Verify Cloudflare secret

kubectl get secret cloudflare-api-token -n cert-manager
kubectl describe secret cloudflare-api-token -n cert-manager

Check ACME challenges

kubectl get challenges --all-namespaces
kubectl describe challenge <challenge-name> -n <namespace>

📚 References

🤝 Contributing

To improve this wrapper chart:

  1. Edit files in charts/cert-manager-stack/
  2. Test with helm template or helm install --dry-run
  3. Update manifests/cert-manager-stack/values.yaml if needed
  4. Commit and create a PR

📄 License

This wrapper chart follows the same license as cert-manager (Apache 2.0).