M28: ACME Renewal Information (RFC 9702) — CA-directed renewal timing with cert ID computation, directory endpoint discovery, graceful degradation for non-ARI CAs. 19 tests. M29: Email notifier wiring + scheduled certificate digest — SMTP connector bridged to service layer via NotifierAdapter, DigestService with HTML email template, 7th scheduler loop (24h), digest preview/send API endpoints and GUI card. 21 tests. M30: Production-ready Helm chart — server Deployment, PostgreSQL StatefulSet, agent DaemonSet, ConfigMaps, Secrets, Ingress, security contexts, health probes, example values for dev/prod/ACME scenarios. Also: OpenAPI spec updates, MCP tool additions, CI helm-lint job, documentation updates across 5 doc files and README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
Certctl Helm Chart - Complete Summary
Overview
A production-ready Helm chart for deploying certctl (self-hosted certificate lifecycle management platform) on Kubernetes. The chart provides:
- High availability support with multi-replica deployments
- Persistent PostgreSQL database with automatic schema migration
- DaemonSet or Deployment-based agent deployment
- Comprehensive security contexts and RBAC
- Multiple deployment scenarios (dev, prod, HA, external DB)
- Full documentation and examples
Chart Metadata
- Name: certctl
- Chart Version: 0.1.0
- App Version: 2.1.0
- Type: application
- License: BSL-1.1 (converts to Apache 2.0 in 2033)
File Structure
deploy/helm/
├── README.md # Main Helm chart documentation
├── DEPLOYMENT_GUIDE.md # Step-by-step deployment guide
├── CHART_SUMMARY.md # This file
│
├── certctl/
│ ├── Chart.yaml # Chart metadata
│ ├── values.yaml # Default configuration values
│ ├── .helmignore # Files to ignore when building chart
│ │
│ └── templates/
│ ├── _helpers.tpl # Helm template helper functions
│ ├── NOTES.txt # Post-deployment notes
│ │
│ ├── server-deployment.yaml # Certctl API server deployment
│ ├── server-service.yaml # Server Kubernetes service
│ ├── server-configmap.yaml # Server configuration
│ ├── server-secret.yaml # Server secrets (API key, DB password, etc)
│ │
│ ├── postgres-statefulset.yaml # PostgreSQL database statefulset
│ ├── postgres-service.yaml # PostgreSQL headless service
│ ├── postgres-secret.yaml # Database credentials secret
│ │
│ ├── agent-daemonset.yaml # Certctl agent daemonset/deployment
│ ├── agent-configmap.yaml # Agent configuration
│ │
│ ├── ingress.yaml # Optional ingress resource
│ └── serviceaccount.yaml # ServiceAccount and RBAC
│
└── examples/
├── values-dev.yaml # Development/testing configuration
├── values-prod-ha.yaml # Production HA configuration
├── values-external-db.yaml # External PostgreSQL (RDS, Cloud SQL)
└── values-acme-dns01.yaml # ACME with DNS-01 (Let's Encrypt)
Key Components
1. Server Deployment
File: templates/server-deployment.yaml
- Manages certctl API server instances
- Configurable replicas (default: 1)
- Health checks (liveness & readiness probes)
- Security context: non-root user, read-only filesystem
- Resource limits (default: 500m CPU, 512Mi memory)
- Automatic restart on failure
Values:
server:
replicas: 1
port: 8443
auth:
type: api-key
apiKey: "REQUIRED"
resources:
requests: {cpu: 100m, memory: 128Mi}
limits: {cpu: 500m, memory: 512Mi}
2. PostgreSQL StatefulSet
File: templates/postgres-statefulset.yaml
- Persistent database storage
- Automatic schema migrations on startup
- Single replica (can be extended with external HA tools)
- Health checks via pg_isready
- Configurable storage size and class
- Security context: non-root user (UID 999)
Values:
postgresql:
enabled: true
storage:
size: 10Gi
storageClass: "" # Use default
auth:
database: certctl
username: certctl
password: "REQUIRED"
3. Agent DaemonSet/Deployment
File: templates/agent-daemonset.yaml
- DaemonSet mode: one agent per Kubernetes node
- Deployment mode: custom number of agent replicas
- Local key storage with secure permissions (0600)
- Health checks and automatic restart
- Optional certificate discovery from filesystem
Values:
agent:
enabled: true
kind: DaemonSet # or Deployment
replicas: 1 # for Deployment only
keyDir: /var/lib/certctl/keys
discoveryDirs: "/etc/ssl/certs" # optional
4. Ingress (Optional)
File: templates/ingress.yaml
- Optional HTTPS ingress
- cert-manager integration for automatic TLS
- Multiple host support
- Path-based routing
Values:
ingress:
enabled: false
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: certctl.example.com
paths:
- path: /
pathType: Prefix
5. ConfigMaps and Secrets
Files:
server-configmap.yaml- Non-secret server configurationserver-secret.yaml- API key, database URL, SMTP passwordpostgres-secret.yaml- Database credentialsagent-configmap.yaml- Agent configuration
All secrets are base64-encoded and stored in Kubernetes Secrets.
6. ServiceAccount and RBAC
File: templates/serviceaccount.yaml
- Optional ServiceAccount creation
- Optional RBAC (ClusterRole, ClusterRoleBinding)
- Namespace-scoped by default
Deployment Scenarios
Development Setup
Use examples/values-dev.yaml:
helm install certctl certctl/ \
--values examples/values-dev.yaml \
--set server.auth.apiKey="dev-key" \
--set postgresql.auth.password="dev-password"
Features:
- Single server replica
- Demo auth (no API key required)
- Small database (5Gi)
- LoadBalancer service for easy access
- Debug logging level
Production HA Setup
Use examples/values-prod-ha.yaml:
helm install certctl certctl/ \
--values examples/values-prod-ha.yaml \
--set server.auth.apiKey="$(openssl rand -base64 32)" \
--set postgresql.auth.password="$(openssl rand -base64 32)"
Features:
- 3 server replicas with pod anti-affinity
- Large database storage (100Gi)
- Pod disruption budgets
- Prometheus monitoring enabled
- Production resource limits
External PostgreSQL
Use examples/values-external-db.yaml:
helm install certctl certctl/ \
--values examples/values-external-db.yaml \
--set postgresql.enabled=false \
--set 'server.env.CERTCTL_DATABASE_URL=postgres://...'
Use cases:
- AWS RDS
- Google Cloud SQL
- Azure Database for PostgreSQL
- External self-managed PostgreSQL
ACME with DNS-01
Use examples/values-acme-dns01.yaml:
helm install certctl certctl/ \
--values examples/values-acme-dns01.yaml
Enables:
- Automatic certificate issuance from Let's Encrypt
- DNS-01 challenge (wildcard support)
- Custom DNS provider scripts
Configuration Options
Server Configuration
| Option | Default | Description |
|---|---|---|
server.replicas |
1 | Number of server replicas |
server.port |
8443 | Server port |
server.auth.type |
api-key | Authentication type |
server.auth.apiKey |
"" | API key (REQUIRED) |
server.logging.level |
info | Log level |
server.logging.format |
json | Log format |
PostgreSQL Configuration
| Option | Default | Description |
|---|---|---|
postgresql.enabled |
true | Enable internal PostgreSQL |
postgresql.storage.size |
10Gi | Database storage size |
postgresql.storage.storageClass |
"" | Storage class name |
postgresql.auth.password |
"" | Database password (REQUIRED) |
Agent Configuration
| Option | Default | Description |
|---|---|---|
agent.enabled |
true | Deploy agents |
agent.kind |
DaemonSet | DaemonSet or Deployment |
agent.replicas |
1 | Replicas (Deployment only) |
agent.keyDir |
/var/lib/certctl/keys | Key storage directory |
Issuer Configuration
| Option | Default | Description |
|---|---|---|
server.issuer.local.enabled |
true | Enable Local CA |
server.issuer.acme.enabled |
false | Enable ACME |
server.issuer.acme.directoryURL |
"" | ACME directory URL |
server.issuer.acme.email |
"" | ACME email |
server.issuer.acme.challengeType |
http-01 | Challenge type |
See values.yaml for complete configuration options.
Helm Template Functions
Defined in templates/_helpers.tpl:
| Function | Purpose |
|---|---|
certctl.name |
Chart name |
certctl.fullname |
Full release name |
certctl.chart |
Chart name and version |
certctl.labels |
Common labels |
certctl.selectorLabels |
Selector labels |
certctl.serverSelectorLabels |
Server selector labels |
certctl.agentSelectorLabels |
Agent selector labels |
certctl.postgresSelectorLabels |
PostgreSQL selector labels |
certctl.serviceAccountName |
ServiceAccount name |
certctl.serverImage |
Server image URI |
certctl.agentImage |
Agent image URI |
certctl.postgresImage |
PostgreSQL image URI |
certctl.databaseURL |
Database connection string |
certctl.serverURL |
Server URL for agents |
Security Features
Pod Security
- Non-root users (UID 1000 for app, UID 999 for PostgreSQL)
- Read-only root filesystems
- No privilege escalation
- Dropped capabilities (ALL)
- Resource limits to prevent DoS
Secrets Management
- All sensitive data in Kubernetes Secrets
- Base64 encoded at rest
- Can be integrated with:
- sealed-secrets
- external-secrets
- Vault
- AWS Secrets Manager
RBAC
- ServiceAccount per release
- Optional ClusterRole/ClusterRoleBinding
- Extensible for custom permissions
Network Security
- Support for Kubernetes NetworkPolicies
- Service-to-service communication via internal DNS
- Optional Ingress with TLS
Monitoring and Observability
Health Checks
- Liveness probes (detect dead containers)
- Readiness probes (detect not-ready services)
- HTTP endpoints:
/health,/readyz
Logging
- Structured JSON logging
- Request ID propagation
- Configurable log levels (debug, info, warn, error)
Metrics
- Prometheus metrics endpoint:
/api/v1/metrics/prometheus - Optional ServiceMonitor for Prometheus Operator
- Built-in metrics:
- Certificate counts by status
- Agent counts and status
- Job completion/failure rates
- Server uptime
Installation Quick Reference
# Development
helm install certctl certctl/ \
--set server.auth.apiKey=dev \
--set postgresql.auth.password=dev
# Production HA
helm install certctl certctl/ \
--values examples/values-prod-ha.yaml \
--set server.auth.apiKey="$(openssl rand -base64 32)" \
--set postgresql.auth.password="$(openssl rand -base64 32)"
# External database
helm install certctl certctl/ \
--values examples/values-external-db.yaml \
--set postgresql.enabled=false \
--set 'server.env.CERTCTL_DATABASE_URL=postgres://...'
# ACME with Let's Encrypt
helm install certctl certctl/ \
--set server.issuer.acme.enabled=true \
--set server.issuer.acme.directoryURL=https://acme-v02.api.letsencrypt.org/directory
# Check status
kubectl get pods -l app.kubernetes.io/instance=certctl
kubectl logs -l app.kubernetes.io/component=server -f
# Upgrade
helm upgrade certctl certctl/ -f new-values.yaml
# Uninstall
helm uninstall certctl
Best Practices
1. Use Secrets Management
# Use sealed-secrets
kubectl create secret generic certctl-secrets \
--from-literal=api-key="$(openssl rand -base64 32)" \
--dry-run=client -o yaml | kubeseal -f - | kubectl apply -f -
2. Configure Resource Limits
Match limits to your cluster capacity:
server:
resources:
requests: {cpu: 250m, memory: 256Mi}
limits: {cpu: 1000m, memory: 512Mi}
3. Enable HA for Production
server:
replicas: 3
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: [...]
4. Use Persistent Storage
postgresql:
storage:
size: 100Gi
storageClass: fast-ssd
5. Enable Monitoring
monitoring:
enabled: true
serviceMonitor:
enabled: true
Documentation
- README.md - Complete Helm chart documentation
- DEPLOYMENT_GUIDE.md - Step-by-step deployment instructions
- values.yaml - Commented configuration reference
Support
For issues, questions, or contributions:
- GitHub: https://github.com/shankar0123/certctl
- Documentation: https://github.com/shankar0123/certctl/tree/main/docs
License
BSL-1.1 (Business Source License) Converts to Apache 2.0 on March 28, 2033