/etc/haproxy is a named volume in deployed containers, so the baked-in
cloudflare_ips.list and trusted_proxies.list COPYed there in the prior
task never actually reached hosts with a pre-existing volume -- the
start-up.sh guard then found them "missing" and created them empty.
With both lists empty, the from_trusted_proxy ACL in hap_listener.tpl
matched nothing, so CF-Connecting-IP / X-Real-IP / X-Forwarded-For got
stripped from every peer, including Cloudflare's own edge. Confirmed
live: image shipped 34/13 lines, running container had 0/0.
Fix: stage both files under /haproxy/defaults (outside the volume) and
apply their ownership rule in start-up.sh instead of a blind
"create if missing":
- cloudflare_ips.list is shipped data -- always refresh it from the
baked default so Cloudflare range updates reach existing hosts.
- trusted_proxies.list is operator data -- seed it from the baked
default only when missing, and never overwrite what an operator
added on the server.
Both branches fall back to creating an empty file if the baked default
is somehow absent, since a missing "-f" target is a fatal HAProxy
config error.
Verified against a volume pre-populated to shadow the image (mimicking
a real host): cloudflare_ips.list repopulates with all 15 IPv4 + 7
IPv6 ranges even after being truncated and restarted; a distinctive
operator entry appended to trusted_proxies.list survives a restart
untouched; haproxy -c still validates cleanly.
Release-worthy fix for a defect from the just-released 2026.08.2 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
63 lines
3.8 KiB
Docker
63 lines
3.8 KiB
Docker
# Base image mirrored into the in-house registry to remove docker.io
|
|
# (Cloudflare R2) as a single point of failure for CI builds. The 2026-05-12
|
|
# Cloudflare incident took down docker.io blob pulls and broke this image's CI.
|
|
# Refresh procedure (run on a workstation that can reach docker.io, e.g.
|
|
# monthly or when Python patches drop):
|
|
# docker pull docker.io/library/python:3.12-slim
|
|
# docker tag docker.io/library/python:3.12-slim \
|
|
# repo.anhonesthost.net/cloud-hosting-platform/python:3.12-slim
|
|
# docker push repo.anhonesthost.net/cloud-hosting-platform/python:3.12-slim
|
|
# Future improvement: a scheduled Gitea Action that does the above automatically.
|
|
FROM repo.anhonesthost.net/cloud-hosting-platform/python:3.12-slim
|
|
|
|
# image.source is what ghcr.io uses to link the package to a GitHub repo
|
|
# sidebar; pointing at the public GitHub mirror enables that linking. The
|
|
# canonical source-of-truth git remote is still Gitea, but Gitea's registry
|
|
# doesn't consume this label, so there's no contention.
|
|
# Stamped from the VERSION file by CI (build-arg) so `docker inspect` reports
|
|
# what's running on any host. Defaults to "dev" for local/manual builds.
|
|
ARG VERSION=dev
|
|
LABEL org.opencontainers.image.title="haproxy-manager-base" \
|
|
org.opencontainers.image.description="HAProxy management API with Let's Encrypt automation, Coraza WAF integration, and template-driven config" \
|
|
org.opencontainers.image.source="https://github.com/shadowdao/haproxy-manager-base" \
|
|
org.opencontainers.image.version="${VERSION}" \
|
|
org.opencontainers.image.licenses="MIT"
|
|
|
|
RUN apt update -y && apt dist-upgrade -y && apt install socat haproxy cron certbot curl jq net-tools -y && apt clean && rm -rf /var/lib/apt/lists/*
|
|
WORKDIR /haproxy
|
|
COPY ./templates /haproxy/templates
|
|
COPY requirements.txt /haproxy/
|
|
COPY haproxy_manager.py /haproxy/
|
|
COPY scripts /haproxy/scripts
|
|
COPY trusted_ips.list /etc/haproxy/trusted_ips.list
|
|
COPY trusted_ips.map /etc/haproxy/trusted_ips.map
|
|
# /etc/haproxy is a named volume in deployed containers, so baked-in files
|
|
# under that path get shadowed by the volume on existing deployments. The
|
|
# trusted_ips.* pair above predates that discovery and is handled by the
|
|
# older start-up.sh guard (out of scope here). cloudflare_ips.list and
|
|
# trusted_proxies.list are staged under /haproxy/defaults instead, so
|
|
# start-up.sh can always read the image's baked copy regardless of what the
|
|
# volume shadows /etc/haproxy with.
|
|
COPY cloudflare_ips.list /haproxy/defaults/cloudflare_ips.list
|
|
COPY trusted_proxies.list /haproxy/defaults/trusted_proxies.list
|
|
# Place errorfiles outside the volumed path; the HAProxy config references
|
|
# them by absolute path.
|
|
COPY errors /haproxy/errors
|
|
RUN chmod +x /haproxy/scripts/*
|
|
RUN pip install -r requirements.txt
|
|
# Create log directories
|
|
RUN mkdir -p /var/log && touch /var/log/haproxy-manager.log /var/log/haproxy-manager-errors.log
|
|
RUN chmod 755 /var/log/haproxy-manager.log /var/log/haproxy-manager-errors.log
|
|
# Set up cron for certificate renewal with proper permissions and environment
|
|
RUN mkdir -p /var/spool/cron/crontabs && \
|
|
echo 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' > /var/spool/cron/crontabs/root && \
|
|
echo '0 */12 * * * /haproxy/scripts/renew-certificates.sh >> /var/log/haproxy-manager.log 2>&1' >> /var/spool/cron/crontabs/root && \
|
|
chmod 600 /var/spool/cron/crontabs/root && \
|
|
chown root:crontab /var/spool/cron/crontabs/root
|
|
# 443/udp carries HTTP/3 (QUIC). EXPOSE is documentation only — the container
|
|
# must still be run with `-p 443:443/udp` for the UDP listener to be reachable.
|
|
EXPOSE 80 443 443/udp 8000
|
|
# Add health check
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
|
CMD curl -sf --max-time 5 http://localhost:8000/health && curl -s --max-time 5 -o /dev/null http://localhost/ || exit 1
|
|
CMD ["/haproxy/scripts/start-up.sh"] |