Ai/automazione

Certctl and Docker Compose: Simplified Self-Hosted TLS Management

Certctl and Docker Compose: Simplified Self-Hosted TLS Management

Managing TLS certificates is critical for securing service-to-service communication, but it can quickly become a hassle—especially in dev and test environments. While self-signed certificates offer a fast solution, they still require manual creation and renewal. certctl streamlines this by automating the generation and lifecycle management of your certificates. Paired with Docker Compose, you can spin up a fully self-hosted environment for managing TLS certs, making it ideal for developing and testing applications that rely on secure communications. This tutorial walks you through setting up this stack so you can generate and renew TLS certificates efficiently and automatically.

Prerequisites

  • Docker installed
  • Docker Compose installed
  • certctl installed (ensure you have the latest version available)
  • A text editor (e.g., nano, vim)
  • Basic knowledge of Docker and Docker Compose

Docker Compose Setup

Let’s start by defining our services in a docker-compose.yml file. We’ll set up a container for certctl and, optionally, an Nginx container to validate our generated certificates.

version: '3.8'
services:
  certctl:
    image: ghcr.io/cloudflare/certctl:latest
    container_name: certctl
    volumes:
      - ./certs:/certs
    environment:
      - CERTCTL_CA_CN=MySelfSignedCA
    command: sleep infinity # Mantiene il container attivo

  nginx:
    image: nginx:latest
    container_name: nginx-test
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - certctl

This compose file defines two services:

  • certctl: Pulls the official certctl Docker image from the GitHub Container Registry. It maps the local ./certs directory to /certs inside the container and sets the CERTCTL_CA_CN environment variable to define the Certificate Authority’s Common Name (CN). The sleep infinity command keeps the container running, allowing us to execute certctl commands on the fly.
  • nginx: Uses the standard Nginx image, mapping host port 443 to the container. It mounts the local nginx.conf and the ./certs directory as read-only. The depends_on directive ensures the certctl container starts before Nginx.

Creating the Certificates Directory

Create a local directory named certs to store the generated certificates.

mkdir certs

Creating the Nginx Configuration File (Optional)

If you want to test the certificates with Nginx, create an nginx.conf file with the following configuration:

events {}

http {
    server {
        listen 443 ssl;
        server_name localhost;

        ssl_certificate /etc/nginx/certs/example.com.crt;
        ssl_certificate_key /etc/nginx/certs/example.com.key;

        location / {
            root /usr/share/nginx/html;
            index index.html;
        }
    }
}

This configuration tells Nginx to listen on port 443 (HTTPS) and use the TLS certificates generated by certctl. Ensure the ssl_certificate and ssl_certificate_key paths match the filenames that certctl will generate.

Generating the Certificate Authority (CA)

Spin up the containers defined in your docker-compose.yml:

docker-compose up -d

This brings up the certctl and nginx containers in the background. Next, generate the CA by running the certctl ca command inside the certctl container:

docker exec -it certctl certctl ca

This command generates a new CA in the /certs directory inside the container. The ca.crt and ca.key files (CA certificate and private key) will be synced to your local certs directory.

Expected output:

Generating a new CA in /certs
CA certificate: /certs/ca.crt
CA private key: /certs/ca.key

Generating a TLS Certificate for a Domain

With the CA in place, you can generate a TLS certificate for a specific domain. For example, to issue a certificate for example.com, run:

docker exec -it certctl certctl cert example.com

This generates the certificate for example.com inside the container, creating the example.com.crt and example.com.key files in your local certs directory.

Expected output:

Generating a new certificate for example.com in /certs
Certificate: /certs/example.com.crt
Private key: /certs/example.com.key

Verifying the Certificate (Optional)

If you configured Nginx, verify the certificate by navigating to https://localhost in your browser. You will see a security warning because the certificate is self-signed and not trusted by a recognized CA. You can bypass the warning to proceed. You should see the default Nginx landing page, confirming your TLS certificate is working correctly.

Renewing Certificates

TLS certificates have an expiration date, but certctl makes renewals just as straightforward. To renew the certificate for example.com, run the following command:

docker exec -it certctl certctl renew example.com

This renews the certificate for example.com and overwrites the existing example.com.crt and example.com.key files. If you’re running Nginx, you’ll need to reload its configuration to pick up the new certs:

docker exec -it nginx-test nginx -s reload

Automating Certificate Renewal

To automate the renewal process, you can set up a cron job to periodically run the certctl renew command. For instance, to renew your certificates monthly, add the following line to your crontab:

0 0 1 * * docker exec -it certctl certctl renew example.com && docker exec -it nginx-test nginx -s reload

This cron job executes the renewal command and reloads Nginx on the first day of every month.

Common Errors and Troubleshooting

  • Error: certctl: command not found: Ensure certctl is properly installed and accessible in your system’s PATH.
  • Error: x509: certificate signed by unknown authority in the browser: This occurs because the certificate is self-signed and not issued by a trusted CA. You can bypass the browser warning or add the CA certificate to your OS trust store to silence the error.
  • Error: Nginx is not serving the renewed certificate: Make sure you reload the Nginx configuration after renewing the certificate using docker exec -it nginx-test nginx -s reload.

Conclusion

In this tutorial, you learned how to leverage certctl and Docker Compose to manage self-hosted TLS certificates. We covered generating a CA, issuing domain-specific certificates, renewing them, and automating the renewal process. This approach is highly effective for dev and test environments where speed and simplicity are priorities. Keep in mind that self-signed certificates are not suitable for production environments—always use certificates issued by a recognized CA in production. Have questions or feedback? Drop a comment below!

Share this article:

Written by

Rosario Giordano

Rosario Giordano is a system administrator and IT consultant specializing in cybersecurity and cloud, with over 20 years of experience managing enterprise Linux infrastructures. His areas of expertise include SSH hardening, Kubernetes platforms, PostgreSQL databases, VMware/ Proxmox virtualization, and compliance with NIS2 and ISO 27001 security frameworks