More Docker Services
Use CertOne with Caddy, HAProxy, or Mosquitto to download certificates before the service starts and activate subsequent certificate updates automatically. These examples set credentials directly in the Dockerfile, just like the nginx, httpd, and OpenResty examples.
Choose your service
| Service | Use case | How updates take effect | Published port |
|---|---|---|---|
| Caddy | HTTPS websites and reverse proxies | Validate configuration, then force a graceful reload | 443 |
| HAProxy | HTTPS load balancing and TLS termination | Validate configuration, then reload the master process | 443 |
| Mosquitto | MQTT over TLS | Signal the broker to reload certificates | 8883 |
Before you start
- Create a deployment node, associate an issued certificate, and copy the node ID and Token.
- Choose one example below and save its files in a separate directory.
- Replace
node-REPLACE,REPLACE_WITH_NODE_TOKEN,cert-REPLACE, andexample.com. Use a domain covered by the certificate. The certificate ID in the service configuration selects files to read; CertOne still synchronizes all certificates associated with the node. - Keep
{id}unchanged in CertOne output path templates. It expands to each certificate's ID.
The examples use CERTONE_DEPLOY_TYPE=files with service-specific reload commands. There are no caddy, haproxy, or mosquitto deployment-type values. CertOne validates certificate material before writing it; the additional commands activate it in the service.
CERTONE_VERIFY_ADDRESS and CERTONE_VERIFY_SERVER_NAME make CertOne check the certificate actually served by the local application after a reload. Use a publicly trusted certificate covering the specified hostname. The check does not need public DNS to resolve to the container because it connects to 127.0.0.1 inside it.
The Dockerfile and built image contain the node Token. Keep both private. Persist the complete .certone directory and use a different volume for each running instance. After changing Dockerfile settings, rebuild the image and recreate the container with the same volume.
Caddy
Use this example when CertOne manages the certificate for a Caddy website. The Caddyfile explicitly loads the supplied files and disables Caddy's automatic HTTPS management for this configuration.
Prepare the files
Save as Dockerfile:
FROM alpine:3.23 AS certone-download
RUN apk add --no-cache curl ca-certificates
RUN curl -fsSL https://certone.io/downloads/certone/install.sh -o /tmp/install.sh \
&& CERTONE_INSTALL_DIR=/out sh /tmp/install.sh
FROM caddy:2-alpine
COPY --from=certone-download /out/certone /usr/local/bin/certone
COPY Caddyfile /etc/caddy/Caddyfile
STOPSIGNAL SIGTERM
ENV HOME=/root
ENV CERTONE_NODE_ID="node-REPLACE"
ENV CERTONE_TOKEN="REPLACE_WITH_NODE_TOKEN"
ENV CERTONE_DEPLOY_TYPE=files
ENV CERTONE_CHECK_COMMAND="caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile"
ENV CERTONE_RELOAD_COMMAND="caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile --force"
ENV CERTONE_VERIFY_ADDRESS="127.0.0.1:443"
ENV CERTONE_VERIFY_SERVER_NAME="example.com"
ENTRYPOINT ["/usr/local/bin/certone","exec","--env","--"]
CMD ["caddy","run","--config","/etc/caddy/Caddyfile","--adapter","caddyfile"]
Save as Caddyfile:
{
auto_https off
admin 127.0.0.1:2019
}
https://example.com {
tls /root/.certone/certificates/cert-REPLACE/fullchain.pem /root/.certone/certificates/cert-REPLACE/key.pem
respond "HTTPS is ready" 200
}
Replace respond with your existing reverse_proxy or website configuration when deploying an application. Keep the local admin endpoint enabled: caddy reload uses it. Do not publish port 2019.
Build and start
docker build -t my-caddy-certone .
docker run -d --name my-caddy --init --restart unless-stopped \
-p 443:443 \
--log-opt max-size=10m --log-opt max-file=5 \
-v caddy-certone:/root/.certone \
-v caddy-data:/data -v caddy-config:/config \
my-caddy-certone
Verify and troubleshoot
docker logs --tail 100 my-caddy
docker exec my-caddy certone status --env
docker exec my-caddy caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
Visit https://example.com and inspect the served certificate. If reload fails with a connection error on port 2019, check the admin setting and startup logs. Keep --force: the certificate files can change while the Caddyfile stays identical. See the Caddy reload documentation.
HAProxy
This example terminates HTTPS in HAProxy. It uses a separate key file named fullchain.pem.key, which HAProxy loads alongside fullchain.pem; no certificate-concatenation script is needed.
Prepare the files
Save as Dockerfile:
FROM alpine:3.23 AS certone-download
RUN apk add --no-cache curl ca-certificates
RUN curl -fsSL https://certone.io/downloads/certone/install.sh -o /tmp/install.sh \
&& CERTONE_INSTALL_DIR=/out sh /tmp/install.sh
FROM haproxy:3.2-alpine
USER root
COPY --from=certone-download /out/certone /usr/local/bin/certone
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY --chmod=0755 reload-haproxy.sh /usr/local/bin/reload-haproxy
STOPSIGNAL SIGTERM
ENV HOME=/root
ENV CERTONE_NODE_ID="node-REPLACE"
ENV CERTONE_TOKEN="REPLACE_WITH_NODE_TOKEN"
ENV CERTONE_DEPLOY_TYPE=files
ENV CERTONE_KEY_PATH="/root/.certone/certificates/{id}/fullchain.pem.key"
ENV CERTONE_CHECK_COMMAND="haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg"
ENV CERTONE_RELOAD_COMMAND="/usr/local/bin/reload-haproxy"
ENV CERTONE_VERIFY_ADDRESS="127.0.0.1:443"
ENV CERTONE_VERIFY_SERVER_NAME="example.com"
ENTRYPOINT ["/usr/local/bin/certone","exec","--env","--","/usr/local/bin/docker-entrypoint.sh"]
CMD ["haproxy","-W","-db","-f","/usr/local/etc/haproxy/haproxy.cfg","-p","/run/haproxy.pid"]
Save as haproxy.cfg:
global
log stdout format raw local0
user haproxy
group haproxy
defaults
mode http
log global
timeout connect 5s
timeout client 30s
timeout server 30s
frontend https
bind :443 ssl crt /root/.certone/certificates/cert-REPLACE/fullchain.pem
http-request return status 200 content-type text/plain string "HTTPS is ready"
For a real application, replace http-request return with your backend routing. CertOne and HAProxy's master run as root inside the container so they can read the private certificate directory; the configuration drops worker privileges to the haproxy account. No host sudo or privileged container mode is required by this setup.
Save as reload-haproxy.sh with Unix line endings:
#!/bin/sh
set -eu
pid="$(cat /run/haproxy.pid)"
case "$pid" in
''|*[!0-9]*) echo "Invalid HAProxy master PID in /run/haproxy.pid" >&2; exit 1 ;;
esac
kill -USR2 "$pid"
Build and start
docker build -t my-haproxy-certone .
docker run -d --name my-haproxy --init --restart unless-stopped \
-p 443:443 \
--log-opt max-size=10m --log-opt max-file=5 \
-v haproxy-certone:/root/.certone my-haproxy-certone
Verify and troubleshoot
docker logs --tail 100 my-haproxy
docker exec my-haproxy certone status --env
docker exec my-haproxy haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg
Visit https://example.com and inspect the certificate. If HAProxy cannot load a private key, confirm the matching fullchain.pem.key path and the CERTONE_KEY_PATH template. If reload fails, check /run/haproxy.pid and retain master-worker mode (-W). Do not send the reload signal to PID 1, which belongs to the container init process. See HAProxy's certificate loading and master-worker reload documentation.
Mosquitto
This example serves MQTT over TLS on port 8883, with username/password authentication. It uses the mosquitto account for both CertOne and the broker, so restrictive private-key permissions remain usable after every certificate replacement.
Create an MQTT user
Run this in the example directory. Enter an MQTT password when prompted; this is separate from the CertOne node Token:
docker run --rm -it -v "$PWD:/work" \
--entrypoint mosquitto_passwd eclipse-mosquitto:2 \
-c /work/passwords mqtt-user
This creates a passwords file containing the password hash. Keep it private. The command uses a POSIX shell; in PowerShell, replace "$PWD:/work" with "${PWD}:/work".
Prepare the files
Save as Dockerfile beside passwords:
FROM alpine:3.23 AS certone-download
RUN apk add --no-cache curl ca-certificates
RUN curl -fsSL https://certone.io/downloads/certone/install.sh -o /tmp/install.sh \
&& CERTONE_INSTALL_DIR=/out sh /tmp/install.sh
FROM eclipse-mosquitto:2
USER root
COPY --from=certone-download /out/certone /usr/local/bin/certone
COPY mosquitto.conf /mosquitto/config/mosquitto.conf
COPY --chown=mosquitto:mosquitto --chmod=0600 passwords /mosquitto/config/passwords
COPY --chmod=0755 reload-mosquitto.sh /usr/local/bin/reload-mosquitto
RUN mkdir -p /mosquitto/.certone \
&& chown mosquitto:mosquitto /mosquitto/.certone
ENV HOME=/mosquitto
ENV CERTONE_NODE_ID="node-REPLACE"
ENV CERTONE_TOKEN="REPLACE_WITH_NODE_TOKEN"
ENV CERTONE_DEPLOY_TYPE=files
ENV CERTONE_RELOAD_COMMAND="/usr/local/bin/reload-mosquitto"
ENV CERTONE_VERIFY_ADDRESS="127.0.0.1:8883"
ENV CERTONE_VERIFY_SERVER_NAME="example.com"
USER mosquitto
STOPSIGNAL SIGTERM
ENTRYPOINT ["/usr/local/bin/certone","exec","--env","--","/docker-entrypoint.sh"]
CMD ["/usr/sbin/mosquitto","-c","/mosquitto/config/mosquitto.conf"]
Save as mosquitto.conf:
listener 8883
protocol mqtt
certfile /mosquitto/.certone/certificates/cert-REPLACE/fullchain.pem
keyfile /mosquitto/.certone/certificates/cert-REPLACE/key.pem
require_certificate false
allow_anonymous false
password_file /mosquitto/config/passwords
pid_file /mosquitto/.certone/mosquitto.pid
persistence true
persistence_location /mosquitto/data/
log_dest stdout
require_certificate false means clients authenticate with the MQTT username/password instead of a client certificate. TLS still encrypts the connection and clients must verify the server certificate. Configure topic ACLs for your application's users before using this as a multi-user broker.
Save as reload-mosquitto.sh with Unix line endings:
#!/bin/sh
set -eu
pid="$(cat /mosquitto/.certone/mosquitto.pid)"
case "$pid" in
''|*[!0-9]*) echo "Invalid Mosquitto PID in /mosquitto/.certone/mosquitto.pid" >&2; exit 1 ;;
esac
kill -HUP "$pid"
Build and start
docker build -t my-mosquitto-certone .
docker run -d --name my-mosquitto --init --restart unless-stopped \
-p 8883:8883 \
--log-opt max-size=10m --log-opt max-file=5 \
-v mqtt-certone:/mosquitto/.certone \
-v mqtt-data:/mosquitto/data my-mosquitto-certone
The two named volumes retain agent state/certificates and MQTT persistence separately. The paths follow the runtime user's home directory, so this example uses /mosquitto/.certone, not /root/.certone. If using host bind mounts instead, grant the image's mosquitto user read/write access to both directories.
Verify and troubleshoot
docker logs --tail 100 my-mosquitto
docker exec my-mosquitto certone status --env
Connect your MQTT client to example.com:8883, enable TLS with normal server-certificate verification, and enter mqtt-user plus the password you created. Test publishing and subscribing to an authorized topic. CertOne verifies the served TLS certificate; it does not test MQTT credentials or topic permissions.
A certificate-read error usually indicates an incorrect certificate ID/path or directory ownership. An MQTT authentication failure refers to the broker's user/password, not the node Token. If reload fails, inspect broker logs and the PID file. Mosquitto 2 does not provide the same preflight command as nginx -t; check startup logs after editing its configuration. Certificate updates use Mosquitto's SIGHUP reload behavior.
Common checks
- Replace every placeholder before building. Only
{id}is intentionally left as a template. - Use
certone status --envand container logs while the agent is running. Do not start a second synchronization process against the same state directory. - If a container exits before you can inspect it, run
docker logs --tail 100 CONTAINER_NAME. To check agent configuration without starting the service, rundocker run --rm --entrypoint certone IMAGE_NAME check --env. - If initial activation briefly fails while the application is starting, inspect subsequent retries and confirm the final status. A running container alone does not establish deployment success.
- Certificate rotation affects new TLS connections. An existing long-lived connection can continue using its original TLS session; reconnect when checking the new certificate.
- Rebuild and recreate after changing Dockerfile settings or application configuration copied into the image. Retain named volumes; deleting state is not a routine fix.
- The sample image tags select supported application families. After validation, pin the application version or digest appropriate for your deployment.
For connection failures, authentication errors, or certificate verification problems, see Troubleshooting. For other startup commands and advanced configuration, see Custom Images.