In the previous article we deployed an internal certificate authority using Step-CA running inside Kubernetes. That CA is now capable of issuing certificates for internal infrastructure.

The next step is enabling automatic certificate issuance for Kubernetes services.

In modern Kubernetes environments this task is usually handled by cert-manager, which integrates with certificate authorities through protocols such as ACME.

In this setup we configure cert-manager to request certificates from our internal Step-CA instance.


Current Infrastructure Overview

Before configuring certificate automation, it is useful to understand the current our lab infrastructure layout.

VM - gitlab          172.21.5.153
docker - registry    172.21.5.75
VM - jenkins         172.21.5.154
docker - ldapman     172.21.5.76
VM - devpi           172.21.5.160
VM - vault           172.21.5.157
VM - postgresql      172.21.5.158
K8s - nexus          172.21.5.76

Some services run on virtual machines while others run inside Kubernetes. Kubernetes services will obtain their certificates automatically using cert-manager.


Installing cert-manager

cert-manager is installed using the official Helm chart.

helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true

This installation creates the controllers responsible for managing certificate resources inside the cluster.


Checking Available Ingress Classes

Before configuring certificate issuance we must identify the ingress class used by the cluster.

kubectl get ingressclass

Example output:

NAME     CONTROLLER             PARAMETERS   AGE
nginx    k8s.io/ingress-nginx   <none>       173d
public   k8s.io/ingress-nginx   <none>       173d

In this cluster the following ingress classes exist:

IngressClass
 ├─ nginx
 └─ public (default)

To determine which ingress class is the default, inspect the annotations.

kubectl get ingressclass public -o yaml
kubectl get ingressclass nginx -o yaml

Look for the annotation:

ingressclass.kubernetes.io/is-default-class: "true"

Updating the Step-CA Deployment

Next, the Step-CA deployment is updated with the configuration that enables ACME support. https://github.com/faustobranco/devops-db/blob/master/SmallStep-CA/values-step3.yaml

helm upgrade --install step-ca smallstep/step-certificates --values values-step3.yaml -n step-ca --create-namespace

At this stage ACME is configured but cannot yet issue certificates because the CA endpoint itself still requires a valid TLS certificate.


Creating a Temporary Certificate for the CA Endpoint

Since ACME is not yet functional, a temporary certificate must be created manually for the Step-CA ingress endpoint.

kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: step-ca-tls
  namespace: step-ca
spec:
  secretName: step-ca-tls
  issuerRef:
    name: step-ca-issuer
    kind: ClusterIssuer
  dnsNames:
    - ca.devops-db.internal
EOF

This creates a Certificate resource that will eventually store the TLS certificate used by the CA service.


Checking the Step-CA Ingress

Verify that the ingress resource exists and exposes the CA service.

kubectl get ingress -n step-ca

Example output:

NAME                        CLASS    HOSTS                   ADDRESS     PORTS     AGE
step-ca-step-certificates   public   ca.devops-db.internal   127.0.0.1   80, 443   20m

Generating a TLS Certificate for the CA Service

Because ACME is not yet active, we generate a certificate manually for the CA service.

Create the OpenSSL configuration file:

vi ca-cert.cnf

Configuration:

[req]
distinguished_name = dn
req_extensions = v3_req
prompt = no

[dn]
CN = ca.devops-db.internal

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = ca.devops-db.internal
DNS.2 = step-ca-step-certificates.step-ca.svc.cluster.local

Generate the private key:

openssl genrsa -out ca.devops-db.internal.key 2048

Create the certificate request:

openssl req -new \
-key ca.devops-db.internal.key \
-out ca.devops-db.internal.csr \
-config ca-cert.cnf

Sign the certificate using the intermediate CA.

openssl x509 -req \
-in ca.devops-db.internal.csr \
-CA intermediate-ca.crt \
-CAkey intermediate-ca.key \
-CAcreateserial \
-out ca.devops-db.internal.crt \
-days 365 \
-sha256 \
-extfile ca-cert.cnf \
-extensions v3_req

Creating the Full Certificate Chain

The TLS secret used by Kubernetes must contain the full certificate chain.

cat ca.devops-db.internal.crt intermediate-ca.crt > fullchain.crt

Creating the TLS Secret in Kubernetes

Create the Kubernetes TLS secret that will be used by the Step-CA ingress.

kubectl create secret tls step-ca-tls \
  --cert=fullchain.crt \
  --key=ca.devops-db.internal.key \
  -n step-ca \
  --dry-run=client -o yaml | kubectl apply -f -

Restarting the Ingress Controller

Restart the ingress controller to ensure the new certificate is loaded.

kubectl rollout restart daemonset nginx-ingress-microk8s-controller -n ingress

Configuring cert-manager to Use Step-CA

Next we configure cert-manager to use Step-CA as an ACME certificate issuer.

First convert the root CA certificate to base64.

cat root-ca.crt | base64 -w0

Example:

cat root-ca.crt | base64 -w0
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZiVENDQTFXZ0F3SUJBZ0lVZjRPblgrZnIzaVZOT2RrMS9iaGJDNmVzM3Jvd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1JqRUxNQWtHQTFVRUJoTUNVRlF4RWpBUUJnTlZCQW9NQ1VSbGRrOXdjeTFFUWpFak1DRUdBMVVFQXd3YQpSR1YyVDNCekxVUkNJRWx1ZEdWeWJtRnNJRkp2YjNRZ1EwRXdIaGNOTWpZd016RXdNakl5T1RNd1doY05Nell3Ck16QTNNakl5T1RNd1dqQkdNUXN3Q1FZRFZRUUdFd0pRVkRFU01CQUdBMVVFQ2d3SlJHVjJUM0J6TFVSQ01TTXcKSVFZRFZRUUREQnBFWlhaUGNITXRSRUlnU1c1MFpYSnVZV3dnVW05dmRDQkRRVENDQWlJd0RRWUpLb1pJaHZjTgpBUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTGFFNlE1SmQwTlh3NCt5L013MTNWbjBXL2J4SFdha00yMEUxTlMrCndOOXp3QkZoSTA0QVNtZVFVOENaOXdIRE1WVXJTa3lJRmJDRldHU0lIRUxJUGdYR3dmY3poY3hDbXBZNVlIRm4Kb1ZBcVNOU2VWU2tlVTRYSUtDbkVNNzdKY0ZlMys5NnlyOFVlYUdHU3lhdVBkczJDTDlQNkdicTJ4anZ4MmtmMwpFZzNQRzVOQ1kwbWt6cE5FVG0xZVBpU3h5RmVXSDFoanRENXBuakdGNWZYRlRiclpIQm1aOVpqQnprNEhkYnNjClpFSkV6Mk1HTGF6SEh1ZWMyV1BkdGtKdlF4VnhkR1lTTWJLS05YWlMxWDhlNHJOT0lzeGhLQlBPMFFPT29FYmQKcDc2L28vUVNQWFZ6b0NNK0RhL2xtK3A5b09uNW5zZm9xSEpmTU5Yb29KRDdMMjJYWG5adXVScHVhdmpBczhYcQpIUExoYXh1N0VJRXV6K0pyNU5yQkkyY0E3RzhvVE1WWUZSaVdXbGxQb2NiekJCN1NrYXV4RnRrbFRzZ00rdEF1Ckc3Y2haWGg3eFZhV1ArOVQ5Vk5IWHJieUdmY2JuclF4d3NwTFVFMkRYbmJEeXR6L0xZbW4wTlBvdHhYN1k4cTkKTWQvTGFteFkxd1dUR0dRQ1RtRnFBN0ZUb01Fbmp3WFZBOXAwd2lQL1AwajFtaG1SWUo3bmRHS05vNXFzSmcwQgpxZlhQMnlEQkFPRHRCWXQ3T0d1SVI1czQzU2ExQ0hOVzhPR210c0F1ejlCR3I3dWtGbmZ2VzVhdk55bytZQ3cyClJObkZpalQ0ZkppOEhhRTRVVkxOUzZEeWVERXF0Z0hOd1pTR0lUL0cvd2pqRGJrdE1CTTJSNUI3ZXpVOEtPOW0KdEIxdkFnTUJBQUdqVXpCUk1CMEdBMVVkRGdRV0JCUzBBaWdBV2MrTnJRei9nOGlkMU0vRzNEdng1REFmQmdOVgpIU01FR0RBV2dCUzBBaWdBV2MrTnJRei9nOGlkMU0vRzNEdng1REFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHCkNTcUdTSWIzRFFFQkN3VUFBNElDQVFDa1kxNlVwL3ZabXozRmQ1SDI1ZWtDaXhyZW56YWEvT0lNNkgzWGZjejUKbnlDWE92bVN0alBYVXlweVJCYTd4MXp3b3JpYno3bEhDTHJYS0JVbzdMTXNBRnR0eGo5YWdtUlAycGExa1JPNwpXQjl4ZWNXemt6UDY5bFBFVytIVERsT2loZUxmZnBCeFVSQ0Z3VENjcU02T3dZQmp6YW0vRTFkdVI4QUVEVXlGClNZdTNSYXRBbmlnS3gvaHYrMGQwNFpoZHZINmJOUHU5VkNDZlJnOTFaZTVkMmpsZjJDTlhCQjVaVHRIVllXcXoKRGdjWiszZmJjMXZyN1ZZQks1Nm9SZDNIMVl1YkI4cFBEWHc0WDRvVVg1cE4xUC9DSGUxcGRZSFlBYVI2YjdGego3cW1yOHlMblVGcFRGeFZ1QUh6Zjd6SXVKTWlFTkc3REhSMmZBb2hPN1Z3QTdObGl0MVpoM0VuRlZaRFZPbCt6CkFvWWhVT3ExUlBZSmlvSzhkU242dUNtbWhCbE1qZXQ4QkdVVkd0Q1pxVlpwZ3FHMTc5TnJQQ3RlYXRHQXdlQVUKaFl2L0NTdXhxMko4Rnp4VGJVRU9uUjA3Mk1sQlA5ZThGb3Njc3lObW5xMmZyMGRKZTErdDFOVkRCdlZ0Q0NpZwpPNitRNWtpSVVyS29KMUJRZ3dDN05RSGhjSGpoTEgwcHRDTCsyS1FhdkhMcEhMb1NiT25ZNDcwZmp2UU1hMUZlCmRKenl3TUQ4T3pzNjFCdk5qY2orV1N0SEVSc25pTlE4YVgvTGNtR2c5TCtiWWVVeGtoOVM1RGZSckpoemwrcEIKZGlBeTVadUNWNnVQa1lVNTdwM0ZlYWkwbmNnS1AxbUxOT2Ztdmh5OXJzTWVTZ3hZTEN4ZERTTWNlb2crNVRybgplUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K

This value will be used as the CA bundle for the ACME configuration.


Creating the ClusterIssuer

The ClusterIssuer resource defines how cert-manager communicates with the internal certificate authority.

kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: step-ca-issuer
spec:
  acme:
    server: https://ca.devops-db.internal/acme/acme/directory
    email: admin@devops-db.internal
    privateKeySecretRef:
      name: step-ca-account-key
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZiVENDQTFXZ0F3SUJBZ0lVZjRPblgrZnIzaVZOT2RrMS9iaGJDNmVzM3Jvd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1JqRUxNQWtHQTFVRUJoTUNVRlF4RWpBUUJnTlZCQW9NQ1VSbGRrOXdjeTFFUWpFak1DRUdBMVVFQXd3YQpSR1YyVDNCekxVUkNJRWx1ZEdWeWJtRnNJRkp2YjNRZ1EwRXdIaGNOTWpZd016RXdNakl5T1RNd1doY05Nell3Ck16QTNNakl5T1RNd1dqQkdNUXN3Q1FZRFZRUUdFd0pRVkRFU01CQUdBMVVFQ2d3SlJHVjJUM0J6TFVSQ01TTXcKSVFZRFZRUUREQnBFWlhaUGNITXRSRUlnU1c1MFpYSnVZV3dnVW05dmRDQkRRVENDQWlJd0RRWUpLb1pJaHZjTgpBUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTGFFNlE1SmQwTlh3NCt5L013MTNWbjBXL2J4SFdha00yMEUxTlMrCndOOXp3QkZoSTA0QVNtZVFVOENaOXdIRE1WVXJTa3lJRmJDRldHU0lIRUxJUGdYR3dmY3poY3hDbXBZNVlIRm4Kb1ZBcVNOU2VWU2tlVTRYSUtDbkVNNzdKY0ZlMys5NnlyOFVlYUdHU3lhdVBkczJDTDlQNkdicTJ4anZ4MmtmMwpFZzNQRzVOQ1kwbWt6cE5FVG0xZVBpU3h5RmVXSDFoanRENXBuakdGNWZYRlRiclpIQm1aOVpqQnprNEhkYnNjClpFSkV6Mk1HTGF6SEh1ZWMyV1BkdGtKdlF4VnhkR1lTTWJLS05YWlMxWDhlNHJOT0lzeGhLQlBPMFFPT29FYmQKcDc2L28vUVNQWFZ6b0NNK0RhL2xtK3A5b09uNW5zZm9xSEpmTU5Yb29KRDdMMjJYWG5adXVScHVhdmpBczhYcQpIUExoYXh1N0VJRXV6K0pyNU5yQkkyY0E3RzhvVE1WWUZSaVdXbGxQb2NiekJCN1NrYXV4RnRrbFRzZ00rdEF1Ckc3Y2haWGg3eFZhV1ArOVQ5Vk5IWHJieUdmY2JuclF4d3NwTFVFMkRYbmJEeXR6L0xZbW4wTlBvdHhYN1k4cTkKTWQvTGFteFkxd1dUR0dRQ1RtRnFBN0ZUb01Fbmp3WFZBOXAwd2lQL1AwajFtaG1SWUo3bmRHS05vNXFzSmcwQgpxZlhQMnlEQkFPRHRCWXQ3T0d1SVI1czQzU2ExQ0hOVzhPR210c0F1ejlCR3I3dWtGbmZ2VzVhdk55bytZQ3cyClJObkZpalQ0ZkppOEhhRTRVVkxOUzZEeWVERXF0Z0hOd1pTR0lUL0cvd2pqRGJrdE1CTTJSNUI3ZXpVOEtPOW0KdEIxdkFnTUJBQUdqVXpCUk1CMEdBMVVkRGdRV0JCUzBBaWdBV2MrTnJRei9nOGlkMU0vRzNEdng1REFmQmdOVgpIU01FR0RBV2dCUzBBaWdBV2MrTnJRei9nOGlkMU0vRzNEdng1REFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHCkNTcUdTSWIzRFFFQkN3VUFBNElDQVFDa1kxNlVwL3ZabXozRmQ1SDI1ZWtDaXhyZW56YWEvT0lNNkgzWGZjejUKbnlDWE92bVN0alBYVXlweVJCYTd4MXp3b3JpYno3bEhDTHJYS0JVbzdMTXNBRnR0eGo5YWdtUlAycGExa1JPNwpXQjl4ZWNXemt6UDY5bFBFVytIVERsT2loZUxmZnBCeFVSQ0Z3VENjcU02T3dZQmp6YW0vRTFkdVI4QUVEVXlGClNZdTNSYXRBbmlnS3gvaHYrMGQwNFpoZHZINmJOUHU5VkNDZlJnOTFaZTVkMmpsZjJDTlhCQjVaVHRIVllXcXoKRGdjWiszZmJjMXZyN1ZZQks1Nm9SZDNIMVl1YkI4cFBEWHc0WDRvVVg1cE4xUC9DSGUxcGRZSFlBYVI2YjdGego3cW1yOHlMblVGcFRGeFZ1QUh6Zjd6SXVKTWlFTkc3REhSMmZBb2hPN1Z3QTdObGl0MVpoM0VuRlZaRFZPbCt6CkFvWWhVT3ExUlBZSmlvSzhkU242dUNtbWhCbE1qZXQ4QkdVVkd0Q1pxVlpwZ3FHMTc5TnJQQ3RlYXRHQXdlQVUKaFl2L0NTdXhxMko4Rnp4VGJVRU9uUjA3Mk1sQlA5ZThGb3Njc3lObW5xMmZyMGRKZTErdDFOVkRCdlZ0Q0NpZwpPNitRNWtpSVVyS29KMUJRZ3dDN05RSGhjSGpoTEgwcHRDTCsyS1FhdkhMcEhMb1NiT25ZNDcwZmp2UU1hMUZlCmRKenl3TUQ4T3pzNjFCdk5qY2orV1N0SEVSc25pTlE4YVgvTGNtR2c5TCtiWWVVeGtoOVM1RGZSckpoemwrcEIKZGlBeTVadUNWNnVQa1lVNTdwM0ZlYWkwbmNnS1AxbUxOT2Ztdmh5OXJzTWVTZ3hZTEN4ZERTTWNlb2crNVRybgplUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    solvers:
      - http01:
          ingress:
            class: public
EOF


Automatic vs Manual Certificate Issuance

At this point the internal certificate authority is fully operational and integrated with Kubernetes through cert-manager.

There are two different approaches that can be used to issue certificates for services running inside the cluster.

Both approaches are valid and useful depending on the operational model and infrastructure requirements.

The following sections demonstrate both methods.

Creating a TLS Certificate for Nexus

In this example, I’m going to create a certificate for one of our services, which is hosted on Kubernetes, called Nexus.

But the process is the same for any service.

Now that cert-manager is configured, certificates can be issued automatically for Kubernetes services.

The following ingress definition exposes the Nexus service and instructs cert-manager to generate a certificate automatically.

kubectl apply -n default -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nexus-ingress
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: step-ca-issuer
spec:
  ingressClassName: public
  rules:
    - host: nexus.devops-db.internal
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nexus
                port:
                  number: 8081
  tls:
    - hosts:
        - nexus.devops-db.internal
      secretName: nexus-tls
EOF

Verifying the Certificate

cert-manager will generate the certificate automatically.

kubectl get certificate

Certificate issuance may take a short amount of time.
When a Certificate resource is created, cert-manager performs several internal steps including creating an Order and Challenge before requesting the certificate from the CA.

Example output:

NAME        READY   SECRET      AGE
nexus-tls   True    nexus-tls   115s

Inspect the generated certificate:

kubectl get secret nexus-tls \
-o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -issuer -subject

Example result:

issuer=C=PT, O=DevOps-DB, CN=DevOps-DB Intermediate CA
subject=CN=nexus.devops-db.internal

This confirms that the certificate was issued by the internal certificate authority.


What Happens Automatically in the Cluster

Once an ingress resource is created with the cert-manager annotation, the entire certificate issuance workflow becomes automatic.

Ingress created
      ↓
cert-manager detects annotation
      ↓
Certificate resource created
      ↓
ACME request generated
      ↓
Step-CA issues certificate
      ↓
TLS secret created
      ↓
Ingress begins serving HTTPS

This automation allows Kubernetes services to obtain trusted TLS certificates without manual intervention.

Manual certificate issuance can be useful when:

  • certificates must be versioned or stored in repositories
  • infrastructure automation tools such as Terraform, Ansible, or CI pipelines generate certificates
  • certificates need to be distributed outside Kubernetes
  • the same certificate must be used across multiple environments

In this section we generate a certificate manually using the Step CLI, then inject it into Kubernetes as a TLS secret used by the Ingress controller.


Bootstrap the Step CLI

Before requesting certificates from the internal certificate authority, the Step CLI must be bootstrapped so it can trust the CA.

This step downloads the root certificate and stores the CA configuration locally.

step ca bootstrap \
  --ca-url https://ca.devops-db.internal \
  --fingerprint b8d4d77badb5aeca4d23a67269846aa3dbf3271c94c0cf97e82e482aab6e7732

This command initializes the Step CLI configuration inside:

~/.step/

The CLI can now securely communicate with the internal certificate authority.


Generate a Certificate Token

The internal CA uses provisioners to control how certificates are issued. In this environment a JWK provisioner named vm-hosts is used.

A token must first be generated to authorize the certificate request.

TOKEN=$(step ca token nexus.devops-db.internal \
  --provisioner vm-hosts \
  --key jwk-provisioner-pub.json)

This command generates a signed JWT token that authorizes the certificate request for the hostname nexus.devops-db.internal.


Request the Certificate from Step-CA

Using the generated token, the Step CLI can now request a certificate from the internal CA.

step ca certificate \
  nexus.devops-db.internal \
  nexus.devops-db.internal.crt \
  nexus.devops-db.internal.key \
  --token $TOKEN

This command performs several operations automatically:

  • generates a private key
  • creates a certificate signing request
  • sends the request to Step-CA
  • receives the signed certificate

At this point two files are generated:

nexus.devops-db.internal.crt
nexus.devops-db.internal.key

Create the Certificate Chain

The Kubernetes ingress controller expects the certificate chain to include both the service certificate and the intermediate CA certificate.

Create the full chain by concatenating the certificates.

cat nexus.devops-db.internal.crt intermediate-ca.crt > fullchain.crt

This file now contains:

service certificate
intermediate certificate

Create the TLS Secret in Kubernetes

The certificate and private key must now be stored inside Kubernetes as a TLS secret.

kubectl create secret tls nexus-tls \
  --cert=fullchain.crt \
  --key=nexus.devops-db.internal.key \
  -n default

Verify that the secret was created successfully.

kubectl get secret nexus-tls -n default

Example output:

NAME        TYPE                DATA   AGE
nexus-tls   kubernetes.io/tls   2      17s

Configure the Ingress to Use the TLS Secret

The Ingress resource must reference the TLS secret that was just created.

The TLS section should include:

tls:
  - hosts:
      - nexus.devops-db.internal
    secretName: nexus-tls

Apply the Ingress configuration.

kubectl apply -n default -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nexus-ingress
spec:
  ingressClassName: public
  rules:
    - host: nexus.devops-db.internal
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nexus
                port:
                  number: 8081
  tls:
    - hosts:
        - nexus.devops-db.internal
      secretName: nexus-tls
EOF

Once applied, the ingress controller automatically loads the certificate from the TLS secret and begins serving HTTPS traffic.


Verify the Certificate

The final step is verifying that the correct certificate is being served by the ingress endpoint.

echo | openssl s_client -connect nexus.devops-db.internal:443 -servername nexus.devops-db.internal 2>/dev/null | openssl x509 -noout -issuer -subject

Example result:

issuer=C=PT, O=DevOps-DB, CN=DevOps-DB Intermediate CA
subject=CN=nexus.devops-db.internal

This confirms that the certificate presented by the Nexus service was successfully issued by the internal certificate authority.


Manual Certificate Workflow

The complete workflow for manual certificate issuance can be summarized as follows:

Generate token
      ↓
Request certificate from Step-CA
      ↓
Create certificate chain
      ↓
Create Kubernetes TLS secret
      ↓
Ingress serves HTTPS

This method provides full control over certificate creation and distribution, making it particularly useful when certificates must be versioned, stored in artifact repositories, or distributed across multiple infrastructure environments.


Automatic vs Manual Certificate Issuance

Both approaches demonstrated in this guide are valid and useful depending on the infrastructure requirements.

Choosing between automatic and manual certificate issuance depends mainly on how certificates are managed and distributed across the environment.

Automatic Certificate Issuance (cert-manager)

The automated approach uses cert-manager integrated with Step-CA through ACME.

In this model, certificates are created and renewed automatically whenever a Kubernetes Ingress resource requests TLS.

Typical workflow:

This approach is ideal for:

  • Kubernetes-native applications
  • services that require automatic certificate renewal
  • environments where certificates should be managed entirely by the cluster

Because cert-manager automatically handles renewals, this method minimizes operational overhead.


Manual Certificate Issuance

The manual approach uses the Step CLI to generate certificates directly from the internal certificate authority.

The certificate is then injected into Kubernetes as a TLS secret and referenced by the Ingress resource.

Typical workflow:

This approach is useful when:

  • certificates must be versioned
  • certificates are distributed through artifact repositories
  • infrastructure automation tools generate certificates
  • the same certificate is used both inside and outside Kubernetes

It is also commonly used for services running on virtual machines, load balancers, or external infrastructure components.