A Docker image that combines haproxy and acme.sh.
The combination of haproxy
and acme.sh
provides a lightweight alternative to Traefik
to implement SSL (TLS) termination for public facing Docker services. A main advantage is the decentralized organization of certificates and the implementation of the Zero Trust principle within a container group.
The haproxy-acme-http01
image is a ready-to-run image for local SSL termination and has the following core features:
- HAProxy listening on port
80
and443
- Port
80
is used for theHTTP-01
ACME certificate challenge and otherwise redirects tohttps
by default - Port
443
redirects traffic to a configurablehost:port
and provides SSL termination
- Port
- Issues a SSL certificate on startup
- Configurable ACME provider (
Let's Encrypt
,ZeroSSL
, ...) - Configurable key length (
2048
,4096
,ec-256
, ...) - Supports SAN certificates
- Configurable ACME provider (
- Automatic certificate renewal
- HAProxy Hitless Reload (zero downtime)
- Configurable certificate renewal notifications (WiP)
docker run -d --name haproxy-acme-http01 \
-e "ACME_MAIL=mail@domain.com" \
-e "ACME_DOMAIN=domain.com" \
-e "SERVER_ADDRESS=whoami" \
-e "SERVER_PORT=80" \
-v /docker_data/acme:/var/lib/acme:rw \
-p 80:80 \
-p 443:443 \
--sysctl net.ipv4.ip_unprivileged_port_start=0 \
ghcr.io/flobernd/haproxy-acme-http01
services:
haproxy-acme:
image: ghcr.io/flobernd/haproxy-acme-http01:latest
container_name: haproxy-acme-http01
restart: unless-stopped
environment:
- ACME_MAIL=mail@domain.com
- ACME_DOMAIN=domain.com
- SERVER_ADDRESS=whoami
- SERVER_PORT=80
volumes:
- /docker_data/acme:/var/lib/acme:rw
ports:
- 80:80
- 443:443
sysctls:
- net.ipv4.ip_unprivileged_port_start = 0
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
It is strongly recommended to specify an external volume for the /var/lib/acme
directory. Most ACME servers enforce a rate limit for issuing and renewing certificates. If you recreate the container without preserving the internal state of acme.sh
, a new certificate will also be created each time.
The container creates a default configuration file haproxy.cfg
in the /usr/local/etc/haproxy
directory.
By mapping the aforementioned path, the primary haproxy.cfg
can be freely customized. Alternatively, additional configurations can be placed in the include
directory, which are then loaded after the primary configuration in alphabetical order.
As long as the default config has not been modified or overwritten, the SERVER_ADDRESS
(required), SERVER_PORT
(default 80
), HAPROXY_HTTP_PORT
(default 80
) and HAPROXY_HTTPS_PORT
(default 443
) environment variables must be set. Otherwise the container will fail to start.
Important
When overwriting the default configuration, make sure that the stats socket
directive is retained. Otherwise, the deployment of certificates will fail.
global
# Allow 'acme.sh' to deploy new certificates without reloading
stats socket /var/lib/haproxy/admin.sock level admin mode 660
Important
When overwriting the default configuration, make sure that the http
frontend correctly responds to the http-01
challenge. Otherwise, the issuing of certificates will fail.
frontend http
mode http
[...]
# Respond to ACME HTTP-01 challenge
http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACME_ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/' }
Set to 1
in order to enable verbose acme.sh
debug output.
Set to 1
in order to automatically update acme.sh
to the latest version on container startup. This requires an active internet connection (default: 0
).
Set to 1
in order to enable the certificate renewal cronjob (default: 1
).
The ACME server to use (default: letsencrypt
).
Supported values: letsencrypt
, letsencrypt_test
, buypass
, buypass_test
, zerossl
, sslcom
, google
, googletest
or an explicit ACME server directory URL like e.g. https://acme-v02.api.letsencrypt.org/directory
See also: https://github.com/acmesh-official/acme.sh/wiki/Server
The mail address for the ACME account registration (required).
The domain to issue the certificate for (required). To issue a multi-domain certificate (SAN), enter additional domains separated by a space character after the primary domain.
For example: sub.domain.com
(single), domain.com *.domain.com
(SAN)
The desired domain key length (default: ec-256
).
Supported values (depending on the ACME server capabilities): 2048
, 3072
, 4096
, 8192
, ec-256
, ec-384
, ec-521
.
The internal haproxy
HTTP listening port. Allows changing the internal port to a non-privileged one (default: 80
). Do not forget to adjust the Docker port mapping accordingly (e.g. -p 80:8080
).
The internal haproxy
HTTPS listening port. Allows changing the internal port to a non-privileged one (default: 443
). Do not forget to adjust the Docker port mapping accordingly (e.g. -p 443:8443
).
The hostname or IP-address of the internal service for which SSL terminiation should be provided (required).
The port of the internal service for which SSL terminiation should be provided (default: 80
).
Optional directives for communicating with the internal service. For example ssl
must be specified here, if the internal service uses HTTPS. Check the haproxy
documentation for more directives.
The haproxy-acme-dns01
image is a ready-to-run image for local SSL termination and has the following core features:
- Issues a SSL certificate on startup
- Configurable ACME provider (
Let's Encrypt
,ZeroSSL
, ...) - Configurable key length (
2048
,4096
,ec-256
, ...) - Supports SAN certificates
- Supports wildcard certificates
- Configurable ACME provider (
- Automatic certificate renewal
- HAProxy Hitless Reload (zero downtime)
- Configurable certificate renewal notifications (WiP)
docker run -d --name haproxy-acme-dns01 \
-e "ACME_MAIL=mail@domain.com" \
-e "ACME_DOMAIN=domain.com *.domain.com" \
-e "ACME_DNS_API=dns_cf" \
-e "CF_Token=<redacted>" \
-e "CF_Zone_ID=<redacted>" \
-v /docker_data/acme:/var/lib/acme:rw \
-p 80:80 \
-p 443:443 \
ghcr.io/flobernd/haproxy-acme-dns01
services:
haproxy-acme:
image: ghcr.io/flobernd/haproxy-acme-dns01:latest
container_name: haproxy-acme-dns01
restart: unless-stopped
environment:
- "ACME_MAIL=mail@domain.com"
- "ACME_DOMAIN=domain.com *.domain.com"
- "ACME_DNS_API=dns_cf"
- "CF_Token=<redacted>"
- "CF_Zone_ID=<redacted>"
volumes:
- /docker_data/acme:/var/lib/acme:rw
ports:
- 80:80
- 43:443
It is strongly recommended to specify an external volume for the /var/lib/acme
directory. Most ACME servers enforce a rate limit for issuing and renewing certificates. If you recreate the container without preserving the internal state of acme.sh
, a new certificate will also be created each time.
The container creates a default configuration file haproxy.cfg
in the /usr/local/etc/haproxy
directory.
By mapping the aforementioned path, the primary haproxy.cfg
can be freely customized. Alternatively, additional configurations can be placed in the include
directory, which are then loaded after the primary configuration in alphabetical order.
As long as the default config has not been modified or overwritten, the SERVER_ADDRESS
(required), SERVER_PORT
(default 80
), HAPROXY_HTTP_PORT
(default 80
) and HAPROXY_HTTPS_PORT
(default 443
) environment variables must be set. Otherwise the container will fail to start.
Important
When overwriting the default configuration, make sure that the stats socket
directive is retained. Otherwise, the deployment of certificates will fail.
global
# Allow 'acme.sh' to deploy new certificates without reloading
stats socket /var/lib/haproxy/admin.sock level admin mode 660
Set to 1
in order to enable verbose acme.sh
debug output.
Set to 1
in order to automatically update acme.sh
to the latest version on container startup. This requires an active internet connection (default: 0
).
Set to 1
in order to enable the certificate renewal cronjob (default: 1
).
The ACME server to use (default: letsencrypt
).
Supported values: letsencrypt
, letsencrypt_test
, buypass
, buypass_test
, zerossl
, sslcom
, google
, googletest
or an explicit ACME server directory URL like e.g. https://acme-v02.api.letsencrypt.org/directory
See also: https://github.com/acmesh-official/acme.sh/wiki/Server
The mail address for the ACME account registration (required).
The domain to issue the certificate for (required). To issue a multi-domain certificate (SAN), enter additional domains separated by a space character after the primary domain.
For example: sub.domain.com
(single), domain.com *.domain.com
(SAN)
The desired domain key length (default: ec-256
).
Supported values (depending on the ACME server capabilities): 2048
, 3072
, 4096
, 8192
, ec-256
, ec-384
, ec-521
.
The DNS API to use (required).
Supported values (incomplete): dns_cf
(Cloudflare), dns_azure
(Azure), dns_gcloud
(Google Cloud), ...
Depending on the DNS API, additional environment variables must be passed to the container. For example, dns_cf
requires the CF_Token
and CF_Zone_ID
/CF_Account_ID
environment variables to be set.
See also: https://github.com/acmesh-official/acme.sh/wiki/dnsapi
The time in seconds to wait for all the TXT records to propagate in DNS API mode. It's not necessary to use this by default, acme.sh
polls DNS status by DOH automatically.
The internal haproxy
HTTP listening port. Allows changing the internal port to a non-privileged one (default: 80
). Do not forget to adjust the Docker port mapping accordingly (e.g. -p 80:8080
).
The internal haproxy
HTTPS listening port. Allows changing the internal port to a non-privileged one (default: 443
). Do not forget to adjust the Docker port mapping accordingly (e.g. -p 443:8443
).
The hostname or IP-address of the internal service for which SSL terminiation should be provided (required).
The port of the internal service for which SSL terminiation should be provided (default: 80
).
Optional directives for communicating with the internal service. For example ssl
must be specified here, if the internal service uses HTTPS. Check the haproxy
documentation for more directives.
The base image haproxy-acme
is based on the Docker "Official Image" for haproxy and the acme.sh Bash script. It serves as a generic template, providing some hook points for customization:
This script is executed before haproxy
starts. Environment variables exported by this script can be used in the haproxy
configuration file.
This script is executed after haproxy
started.
This script is executed as root
before switching to the haproxy
user context.
The default location of the main haproxy
configuration file haproxy.cfg
and the includes
directory.
Docker HAProxy ACME is licensed under the MIT license.