# Pinned by digest, and that is the whole point of the line. `FROM # bitnami/haproxy` carries no tag, the scheduled build runs daily, and so this # image was rebuilt every morning on whatever the base happened to be — twice # in three days that broke the public door with no commit of ours behind it: # 2026-09-02 the base began preferring the FIPS provider and TLS 1.3 closed; # 2026-09-04 the base changed again and haproxy could no longer load the # certificate at all ("inconsistencies between private key and certificate" on # a leaf and key that openssl confirms are a pair). # # This digest is the base measured working on 2026-09-04: haproxy 3.4.4-7f03ae6, # serving the live testnet2 certificate, curl 200 over TLS 1.3. Bumping it is a # deliberate act with a check behind it, which is what the last two mornings # were missing. FROM bitnami/haproxy@sha256:c3fac4a702bf242262588491e2541f83d870484ddf65e1c91527208dc1156c49 # The FIPS preference is left exactly as the base ships it, and that is a # reversal. The base is Photon OS, whose openssl.cnf reads the variable straight # out of the environment: # # [alg_sect] # default_properties = ?fips=$ENV::OPENSSL_FIPS # # On the 2026-09-02 base "yes" meant every algorithm fetch preferred the FIPS # provider, which cannot produce an X25519MLKEM768 key, so haproxy offered a # group it could not use and refused every modern client. `ENV OPENSSL_FIPS=no` # fixed that and was correct for that base. # # On the base pinned above it is the other way round: with "no" haproxy cannot # load an ECDSA certificate at all and exits at parse time, while the shipped # "yes" loads it and still negotiates TLS 1.3. Measured on testnet2 against the # live certificate, same image, only the variable changed: # # OPENSSL_FIPS=no config check fails at `bind *:443` # OPENSSL_FIPS=yes config check passes, curl 200, TLSv1.3 # # So the override is gone rather than inverted. Pinning the base is what makes # leaving it alone safe: the value that is right depends on the base, and the # base no longer moves on its own. # Copy config COPY ./docker/haproxy.cfg /bitnami/haproxy/conf/haproxy.cfg # Directory for the runtime API socket. HAProxy runs as uid 1001 here and binds # a unix socket by creating `..tmp` and renaming it over the target, # so it needs write permission on the *directory*, not just the file — which is # also why a stale socket left by a previous run is harmless. # # /var/lib is owned by root, hence the explicit USER switch. Docker copies this # ownership onto an empty named volume when it initialises one here, so the # volume shared with certbot comes up writable by HAProxy without a chown at # runtime. USER root RUN mkdir -p /var/lib/haproxy && chown 1001:1001 /var/lib/haproxy USER 1001 # The base image's entrypoint is the haproxy binary itself, with no shell in # between, so this wrapper is the whole chain: it fills in XFF_HMAC_KEY when # nothing else did (see the script for why that is better than requiring it) and # execs the same binary with the same arguments. CMD is restated rather than # left to inheritance — it would be inherited, but a changed ENTRYPOINT is # exactly where that stops being obvious to the next reader. COPY --chmod=0755 ./docker/entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] CMD ["-f", "/bitnami/haproxy/conf/haproxy.cfg"]