In this learn guide we will obtain a free TLS certificate from LetsEncrypt for an application we have running on our Kubernetes cluster. The sample application will be written in Node.js using the popular microservices framework Express.js.


  • A Kubernetes cluster you control. This can be an existing, pre-configured cluster, or if you don't have one yet here are a couple of options for using k3s, a light-weight Kubernetes distribution from Rancher:
    • If you are part of the #KUBE100 beta program on Civo, you can use the dashboard or command line to create a cluster in minutes. You can sign up here.
    • If you are not in the beta, or use another cloud provider, you can use k3sup.
      • Once you have a virtual machine instance running, download k3sup and run k3sup install --ip $IP --user $USER which will use ssh to log into the remote computer and install Kubernetes. You can use a Civo Instance if you like, just make sure you add your SSH key when setting up.
  • A working KUBECONFIG and kubectl setup. Once you have a new or existing cluster, get your kubectl command-line to point at it and verify that with kubectl config get-contexts. See also: Setup and install kubectl.
  • A domain name. You'll need a domain and you can register one for as little as $2 from For the examples below I used Google Domains and a .dev address. These cost between £10-15 at time of writing.


Pictured: Conceptual diagram First let's install cert-manager, and then a sample application written in Node.js. Once that's in place, we can explore the steps necessary to configure the TLS certificate. cert-manager will then obtain, manage, and renew the certificate for us on an on-going basis.

Install cert-manager

If you're using Civo's k3s service, then when you create your cluster you can add an "app" from the marketplace. If you're doing this after the fact you can run the following using the civo CLI: ``` $civo k8s ls graph-block $ civo applications add cert-manager --cluster graph-block

Added cert-manager v0.11.0 to Kubernetes cluster graph-block `` This finds your cluster by name and installs thecert-managerapp. You can see that in my case, the cluster was calledgraph-block`.

If you're not using Civo's k3s service then run the following instead: k3sup app install cert-manager

Deploy the Sample App

The sample application we will install is an Express.js microservice built to show how to get started. It includes the following: - a Dockerfile which can be used to rebuilt the code at any time - a Deployment YAML file - a Service YAML file - the index.js and package.json used by Node.js

The sample comes with three endpoints which you can edit at a later time: - / - serves a HTML page - /links - serves a JSON response of links - /health - serves a health endpoint giving a HTTP 200 OK status.

Let's clone the repository onto your local machine: $ git clone $ cd expressjs-k8s Apply the Deployment which will create a Pod in your cluster. $ kubectl apply -f ./yaml/dep.yaml Now apply the Service, which is of type ClusterIP. This type of service is not accessible outside of your cluster. $ kubectl apply -f ./yaml/svc.yaml Forward the port for the app to your local computer: kubectl port-forward svc/expressjs 8080:8080

In a browser open

If you're new to writing Kubernetes YAML files then take a few moments to read over the two YAML files, dep.yaml and svc.yaml.

Configure cert-manager for the Sample App

We now need to create two additional objects in Kubernetes to get our certificate: - Ingress - a routing definition for Traefik (the built-in k3s IngressController) - Issuer - a definition for cert-manager to use for obtaining certificates In addition to that, we must also create a record in our DNS system for the domain name.

Set Up DNS

If you're using k3s then Traefik is installed by default and exposes port 80 and 443 on each node. Simply find the public IP of one of your nodes and create a DNS entry for it in your DNS provider.

If you are on the Civo managed Kubernetes service, you will find the DNS entry in your dashboard:

Use this DNS entry to create a CNAME record with the DNS entry in your domain name registrar, such as

If you are using antoher service with a public IP address, create an A record with the public IP, i.e.

Set Up an Issuer

Now create an Issuer. Issuers are used by cert-manager to identify or authenticate your user. We'll use a HTTP01 Issuer which is only used to identify you and share your email address. yaml apiVersion: kind: Issuer metadata: name: letsencrypt-prod spec: acme: email: server: privateKeySecretRef: name: expressjs-secret-issuer-account-key solvers: - http01: ingress: class: traefik Save the above file as issuer.yaml, changing the email address to yours.

Apply the file: $ kubectl apply -f issuer.yaml

Set Up the Ingress Record

Now let's create the Ingress record. Save the following as ingress.yaml, and edit, replacing that with your own domain. yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: expressjs annotations: letsencrypt-prod "traefik" spec: tls: - hosts: - secretName: expressjs-secret rules: - host: http: paths: - backend: serviceName: expressjs servicePort: 8080 path: / Now, apply this file: $ kubectl apply -f ingress.yaml

Try Out Your TLS Certificate

Access your site JSON API via curl:

If you don't see the certificate at once, check that you can ping your domain and that it gives the correct result. DNS configuration can take a few minutes to propagate throughout the Internet.

You can also debug and diagnose potential issues using the cert-manager docs or by joining the Kubernetes Slack workspace.

Wrapping Up

You now have a Node.js website deployed with a free TLS certificate from LetsEncrypt. It's over to you to extend the functionality and to customise it as you see fit. Build your own API and/or webpage and share it with me on Twitter @alexellisuk or the @CivoCloud community. We would love to hear what you end up making!

Customise the Example

You can customise the example using this workflow: - Edit the Node.js code in index.js or routes/index.js - Build a new container image under your username (docker build -t yourname/image:version.) - Push the image to the Docker Hub or a similar registry (docker push yourname/image:version) - Update the image: in dep.yaml - Run kubectl apply -f dep.yaml The TLS certificate, Ingress and Issuer all stay the same between iterations.

Keep Learning

Find out more about the projects we used today: - Sign up to Civo - cert-manager - k3sup