Cert-Manager Factories
Factory functions for Cert-Manager Custom Resource Definitions with built-in readiness evaluation.
Import
// Import specific functions (recommended)
import { certificate, clusterIssuer, issuer } from 'typekro/cert-manager';
// Or namespace import
import * as certManager from 'typekro/cert-manager';Quick Example
import { clusterIssuer, certificate } from 'typekro/cert-manager';
// Create a Let's Encrypt issuer
const letsEncrypt = clusterIssuer({
name: 'letsencrypt-prod',
spec: {
acme: {
server: 'https://acme-v02.api.letsencrypt.org/directory',
email: 'admin@example.com',
privateKeySecretRef: { name: 'letsencrypt-key' },
solvers: [{ http01: { ingress: { class: 'nginx' } } }]
}
}
});
// Request a certificate
const cert = certificate({
name: 'my-app-tls',
namespace: 'default',
spec: {
secretName: 'my-app-tls-secret',
dnsNames: ['app.example.com'],
issuerRef: { name: 'letsencrypt-prod', kind: 'ClusterIssuer' }
},
id: 'appCert'
});Available Factories
| Factory | Scope | Description |
|---|---|---|
certificate | Namespace | TLS certificate request |
issuer | Namespace | Namespace-scoped certificate issuer |
clusterIssuer | Cluster | Cluster-scoped certificate issuer |
certificate()
Creates a Certificate resource that requests a TLS certificate from an issuer.
import { certificate } from 'typekro/cert-manager';
const cert = certificate({
name: 'my-tls-cert',
namespace: 'default',
spec: {
secretName: 'my-tls-secret',
dnsNames: ['example.com', 'www.example.com'],
issuerRef: {
name: 'letsencrypt-prod',
kind: 'ClusterIssuer'
}
},
id: 'tlsCert'
});
// Status reference
return { certReady: cert.status.conditions?.[0]?.status === 'True' };Certificate Configuration
interface CertificateSpec {
secretName: string; // Secret to store the certificate
issuerRef: {
name: string;
kind: 'Issuer' | 'ClusterIssuer';
group?: string; // Default: 'cert-manager.io'
};
dnsNames?: string[]; // DNS SANs
ipAddresses?: string[]; // IP SANs
duration?: string; // Certificate duration (default: '2160h' / 90 days)
renewBefore?: string; // Renew before expiry (default: '720h' / 30 days)
privateKey?: {
algorithm?: 'RSA' | 'ECDSA' | 'Ed25519';
size?: number; // RSA: 2048, 4096; ECDSA: 256, 384, 521
};
usages?: string[]; // Key usages
}clusterIssuer()
Creates a cluster-wide certificate issuer.
Self-Signed Issuer
import { clusterIssuer } from 'typekro/cert-manager';
const selfSigned = clusterIssuer({
name: 'selfsigned-issuer',
spec: { selfSigned: {} }
});Let's Encrypt with HTTP01
const letsEncrypt = clusterIssuer({
name: 'letsencrypt-prod',
spec: {
acme: {
server: 'https://acme-v02.api.letsencrypt.org/directory',
email: 'admin@example.com',
privateKeySecretRef: { name: 'letsencrypt-prod-key' },
solvers: [{
http01: { ingress: { class: 'nginx' } }
}]
}
}
});Let's Encrypt with DNS01 (Cloudflare)
const letsEncryptDns = clusterIssuer({
name: 'letsencrypt-dns',
spec: {
acme: {
server: 'https://acme-v02.api.letsencrypt.org/directory',
email: 'admin@example.com',
privateKeySecretRef: { name: 'letsencrypt-dns-key' },
solvers: [{
dns01: {
cloudflare: {
email: 'admin@example.com',
apiTokenSecretRef: { name: 'cloudflare-api-token', key: 'api-token' }
}
}
}]
}
}
});CA Issuer
const caIssuer = clusterIssuer({
name: 'ca-issuer',
spec: {
ca: { secretName: 'ca-key-pair' }
}
});issuer()
Creates a namespace-scoped certificate issuer.
import { issuer } from 'typekro/cert-manager';
const nsIssuer = issuer({
name: 'namespace-issuer',
namespace: 'my-namespace',
spec: {
ca: { secretName: 'namespace-ca-key' }
}
});Usage in Compositions
import { type } from 'arktype';
import { kubernetesComposition } from 'typekro';
import { Deployment, Service, Ingress } from 'typekro/simple';
import { certificate, clusterIssuer } from 'typekro/cert-manager';
const SecureAppSpec = type({
name: 'string',
image: 'string',
hostname: 'string'
});
const secureApp = kubernetesComposition({
name: 'secure-app',
apiVersion: 'example.com/v1',
kind: 'SecureApp',
spec: SecureAppSpec,
status: type({ ready: 'boolean', certReady: 'boolean', url: 'string' })
}, (spec) => {
const deploy = Deployment({
id: 'app',
name: spec.name,
image: spec.image,
ports: [{ containerPort: 8080 }]
});
Service({
id: 'svc',
name: `${spec.name}-svc`,
selector: { app: spec.name },
ports: [{ port: 80, targetPort: 8080 }]
});
const cert = certificate({
name: `${spec.name}-tls`,
spec: {
secretName: `${spec.name}-tls-secret`,
issuerRef: { name: 'letsencrypt-prod', kind: 'ClusterIssuer' },
dnsNames: [spec.hostname]
},
id: 'cert'
});
Ingress({
id: 'ingress',
name: spec.name,
host: spec.hostname,
serviceName: `${spec.name}-svc`,
servicePort: 80,
tls: true,
tlsSecretName: `${spec.name}-tls-secret`
});
return {
ready: deploy.status.readyReplicas > 0,
certReady: cert.status.conditions?.[0]?.status === 'True',
url: `https://${spec.hostname}`
};
});Readiness Evaluation
Cert-manager factories include built-in readiness evaluators:
- Certificate: Ready when
Readycondition isTrue - Issuer/ClusterIssuer: Ready when
Readycondition isTrue
// Certificates report detailed status
cert.status.conditions // Array of conditions
cert.status.notAfter // Certificate expiry time
cert.status.notBefore // Certificate valid from
cert.status.renewalTime // Next renewal timeInstalling cert-manager with certManagerBootstrap
certManagerBootstrap installs cert-manager through a Flux HelmRepository and HelmRelease. It renders the same chart values in direct and KRO mode.
import { certManagerBootstrap } from 'typekro/cert-manager';
const factory = certManagerBootstrap.factory('kro', { namespace: 'platform' });
await factory.deploy({
name: 'cert-manager',
namespace: 'cert-manager',
replicaCount: 2,
crds: { enabled: true, keep: true },
controller: {
podDisruptionBudget: { enabled: true, minAvailable: '1' },
topologySpreadConstraints: [
{
maxSkew: 1,
topologyKey: 'topology.kubernetes.io/zone',
whenUnsatisfiable: 'ScheduleAnyway',
labelSelector: { matchLabels: { 'app.kubernetes.io/component': 'controller' } },
},
],
},
webhook: { replicaCount: 2, podDisruptionBudget: { enabled: true } },
cainjector: { replicaCount: 2, podDisruptionBudget: { enabled: true } },
});| Field | Default | Notes |
|---|---|---|
crds.enabled | true | Installs the CRDs with the chart |
crds.keep | true | Keeps the CRDs, and with them every Certificate and Issuer, when the release is uninstalled |
global.leaderElection.namespace | the install namespace | The chart's own default is kube-system. KRO instances created before this default moved their lease; set kube-system to keep it |
strategy.rollingUpdate.maxSurge / maxUnavailable | chart | Strings. A digit-only string ('1') is rendered as an integer, a percentage ('25%') as a string |
replicaCount, webhook.replicaCount, cainjector.replicaCount | chart default (1) | With more than 1 replica, also enable that component's podDisruptionBudget |
controller / webhook / cainjector .podDisruptionBudget | off | enabled, plus minAvailable or maxUnavailable, not both. Each is a string holding an integer ('1') or a percentage ('50%'); the chart renders it unquoted. The chart defaults to minAvailable: 1 |
controller / webhook / cainjector .topologySpreadConstraints | none | Passed to the chart unchanged. Give each constraint a labelSelector that matches that component's pods (app.kubernetes.io/component: controller, webhook or cainjector) |
controller / webhook / cainjector / startupapicheck .resources | requests 10m / 32Mi, limits 100m / 128Mi | Each field is defaulted on its own |
startupapicheck.timeout | 5m | The chart's default is 1m |
prometheus.enabled | false | The chart's default is true |
The cert-manager CRDs are chart templates, not files in the chart's crds/ directory. So crds.enabled decides whether they are installed, and Flux's install.crds / upgrade.crds policy does not apply to them.
Fields of the TypeScript config that the bootstrap schema does not declare (controller.tolerations, controller.affinity, controller.env, customValues, ...) apply in direct mode only. A KRO instance cannot carry them.
Migrating from installCRDs
installCRDs is deprecated. cert-manager chart v1.17 and later refuse installCRDs together with crds.enabled, so TypeKro now renders only crds:
- The bootstrap maps
installCRDstocrds.enabledwhencrds.enabledis unset, soinstallCRDs: falsestill disables the CRDs. Replace it withcrds: { enabled: false }. certManagerHelmReleasenow defaults tocrds: { enabled: true, keep: true }instead ofinstallCRDs: true. If yourvaluesstill setinstallCRDs, the factory adds nocrdsdefault, so the chart sees only your setting.- Direct-mode
customValuesthat still setinstallCRDsown the CRD setting: the bootstrap then renders nocrds. Avaluesobject passed tocertManagerHelmReleaseas one schema reference cannot be inspected at build time, so it must not carryinstallCRDs: true; the chart would refuse it next to thecrds.enableddefault. - An existing release keeps its CRDs.
installCRDs: truealready meantcrds.enabled: truewithcrds.keep: true, so the rendered CRDs are the same.
Prerequisites
To install cert-manager without TypeKro:
# Using Helm
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=trueNext Steps
- Kubernetes Factories - Core Kubernetes resources
- Flux Factories - GitOps integration
- Pebble - ACME test server for development