├── .github └── workflows │ ├── build_image.yml │ └── release_image.yml ├── .gitignore ├── CHANGELOG ├── Dockerfile ├── README.md ├── Tests └── docker-compose.yml └── container-files ├── bootstrap.sh └── etc └── haproxy └── haproxy.cfg /.github/workflows/build_image.yml: -------------------------------------------------------------------------------- 1 | name: Build, Test & Stage HAProxy 2 | 3 | on: 4 | push: 5 | branches: [issue/*, feature/*] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Build Docker image 16 | run: | 17 | docker build -t million12/haproxy . 18 | 19 | - name: Test image 20 | run: | 21 | # 22 | # Test daemon mode (default settings) 23 | # 24 | docker run -d --cap-add NET_ADMIN -p 8080:80 --name haproxy million12/haproxy && sleep 1 25 | curl -u admin:admin http://127.0.0.1:8080/admin?stats 26 | docker rm -f haproxy || true 27 | 28 | # 29 | # Test providing custom HAProxy config 30 | # Test restarting after making changes in that config 31 | # 32 | docker run -d --cap-add NET_ADMIN -p 8080:80 -v ${PWD}/container-files/etc/haproxy/haproxy.cfg:/haproxy/my-haproxy.cfg -e HAPROXY_CONFIG=/haproxy/my-haproxy.cfg --name haproxy million12/haproxy && sleep 5 33 | # Test if HAProxy uses the provided alternative config 34 | while true; do if docker logs haproxy | grep "HAProxy started with /haproxy/my-haproxy.cfg config"; then break; else sleep 1; fi done 35 | 36 | # Make a change and check if HAProxy restarts 37 | docker exec -i haproxy sh -c 'echo "" >> /haproxy/my-haproxy.cfg' 38 | while true; do if docker logs haproxy | grep "HAProxy restarted"; then break; else sleep 1; fi done 39 | 40 | # Check HAProxy stats 41 | curl -sSLi http://127.0.0.1:8080/admin?stats | grep '401 Unauthorized' 42 | curl -sSLi --user admin:admin http://127.0.0.1:8080/admin?stats 43 | curl -sSLi --user admin:admin http://127.0.0.1:8080/admin?stats | grep '200 OK' 44 | curl -sSLi --user admin:admin http://127.0.0.1:8080/admin?stats | grep 'Statistics Report' 45 | 46 | # Make invalid entry 47 | docker exec -i haproxy sh -c 'echo "blabla" >> /haproxy/my-haproxy.cfg' 48 | docker exec -i haproxy sh -c 'cat /haproxy/my-haproxy.cfg | grep "blabla"' 49 | docker rm -f haproxy || true 50 | # 51 | # Test whole stack with Nginx back-end @see Tests/docker-compose.yml 52 | # 53 | docker-compose -f Tests/docker-compose.yml up -d 54 | sleep 5 55 | 56 | # Test direct request to Nginx 57 | curl -sSLi http://127.0.0.1:4080 | grep '200 OK' 58 | curl -sSLi http://127.0.0.1:4080 | grep 'default vhost' 59 | curl -sSLi --insecure https://127.0.0.1:4443 | grep '200' 60 | curl -sSLi --insecure https://127.0.0.1:8443 | grep 'default vhost' 61 | 62 | # Test requests via HAProxy 63 | curl -sSLi http://127.0.0.1:8080 | grep '200 OK' 64 | curl -sSLi http://127.0.0.1:8080 | grep 'default vhost' 65 | curl -sSLi --insecure https://127.0.0.1:8443 | grep '200' 66 | curl -sSLi --insecure https://127.0.0.1:8443 | grep 'default vhost' 67 | - name: Stage Image 68 | run: | 69 | docker build -t million12/haproxy:stage . 70 | docker login -u ${{ secrets.DOCKER_HUB_USER }} -p ${{ secrets.DOCKER_HUB_PASS }} 71 | docker push million12/haproxy:stage 72 | -------------------------------------------------------------------------------- /.github/workflows/release_image.yml: -------------------------------------------------------------------------------- 1 | name: Release HAProxy 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Release image 14 | run: | 15 | export RELEASE=$(grep "HAPROXY_VERSION=" Dockerfile | sed 's|^.*=||g' |awk '{print $1}' | sed 's|"||g') 16 | docker login -u ${{ secrets.DOCKER_HUB_USER }} -p ${{ secrets.DOCKER_HUB_PASS }} 17 | docker pull million12/haproxy:stage 18 | docker tag million12/haproxy:stage million12/haproxy:${RELEASE} 19 | docker tag million12/haproxy:stage million12/haproxy:latest 20 | docker push million12/haproxy:${RELEASE} 21 | docker push million12/haproxy:latest 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.jenkins 2 | /.vscode 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:12 2 | 3 | ENV HAPROXY_MJR_VERSION=2.8 \ 4 | HAPROXY_VERSION=2.8.1 \ 5 | HAPROXY_CONFIG='/etc/haproxy/haproxy.cfg' \ 6 | HAPROXY_ADDITIONAL_CONFIG='' \ 7 | HAPROXY_PRE_RESTART_CMD='' \ 8 | HAPROXY_POST_RESTART_CMD='' \ 9 | OPENSSL_VERSION=3.1.1 10 | 11 | RUN \ 12 | apt update && \ 13 | `# Install build tools. Note: perl needed to compile openssl...` \ 14 | apt install -y \ 15 | inotify-tools \ 16 | wget \ 17 | tar \ 18 | gzip \ 19 | make \ 20 | gcc \ 21 | perl \ 22 | libpcre3-dev \ 23 | zlib1g-dev \ 24 | iptables \ 25 | socat \ 26 | netcat-traditional \ 27 | telnet \ 28 | mtr && \ 29 | `# Install newest openssl...` \ 30 | wget -O /tmp/openssl.tgz https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz && \ 31 | tar -zxf /tmp/openssl.tgz -C /tmp && \ 32 | cd /tmp/openssl-* && \ 33 | ./config \ 34 | --openssldir=/etc/ssl \ 35 | no-shared zlib-dynamic && \ 36 | make -j$(getconf _NPROCESSORS_ONLN) V= && make install_sw && \ 37 | cd && rm -rf /tmp/openssl* && \ 38 | wget -O /tmp/haproxy.tgz http://www.haproxy.org/download/${HAPROXY_MJR_VERSION}/src/haproxy-${HAPROXY_VERSION}.tar.gz && \ 39 | tar -zxvf /tmp/haproxy.tgz -C /tmp && \ 40 | cd /tmp/haproxy-* && \ 41 | make \ 42 | -j$(getconf _NPROCESSORS_ONLN) V= \ 43 | TARGET=linux-glibc \ 44 | USE_LINUX_TPROXY=1 \ 45 | USE_ZLIB=1 \ 46 | USE_REGPARM=1 \ 47 | USE_PCRE=1 \ 48 | USE_PCRE_JIT=1 \ 49 | USE_OPENSSL=1 \ 50 | ADDLIB=-ldl \ 51 | ADDLIB=-lpthread && make install && \ 52 | rm -rf /tmp/haproxy* && \ 53 | mkdir -p /var/lib/haproxy && \ 54 | adduser --no-create-home --disabled-password --gecos "" haproxy && adduser haproxy haproxy && chown -R haproxy:haproxy /var/lib/haproxy && \ 55 | mkdir -p /etc/pki/tls && \ 56 | openssl genrsa -out /etc/ssl/private/dummy.key 2048 && \ 57 | openssl req -new -key /etc/ssl/private/dummy.key -out /etc/ssl/private/dummy.csr -subj "/C=GB/L=London/O=Company Ltd/CN=haproxy" && \ 58 | openssl x509 -req -days 3650 -in /etc/ssl/private/dummy.csr -signkey /etc/ssl/private/dummy.key -out /etc/ssl/private/dummy.crt && \ 59 | cat /etc/ssl/private/dummy.crt /etc/ssl/private/dummy.key > /etc/ssl/private/dummy.pem && \ 60 | apt remove -y make gcc libpcre3-dev && \ 61 | apt clean -y 62 | 63 | COPY container-files / 64 | 65 | ENTRYPOINT ["/bootstrap.sh"] 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HAProxy Load Balancer 2 | === 3 | 4 | [![Build & Test HAProxy](https://github.com/million12/docker-haproxy/workflows/Build%20&%20Test%20HAProxy/badge.svg)](https://github.com/million12/docker-haproxy/actions) 5 | [![GitHub Open Issues](https://img.shields.io/github/issues/million12/docker-haproxy.svg)](https://github.com/million12/docker-haproxy/issues) 6 | [![Stars](https://img.shields.io/github/stars/million12/docker-haproxy.svg?style=social&label=Stars)](https://github.com/million12/docker-haproxy/stargazers) 7 | [![Fork](https://img.shields.io/github/forks/million12/docker-haproxy.svg?style=social&label=Fork)](https://github.com/million12/docker-haproxy/network/members) 8 | [![Release](https://img.shields.io/github/release/million12/docker-haproxy.svg)](http://microbadger.com/images/million12/haproxy.svg) 9 | 10 | [![Docker build](https://dockeri.co/image/million12/haproxy)](https://hub.docker.com/r/million12/haproxy/) 11 | 12 | Felling like supporting me in my projects use donate button. Thank You! 13 | [![PayPal](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.me/POzgo) 14 | 15 | HAProxy docker container [million12/haproxy](https://registry.hub.docker.com/u/million12/haproxy/) with ALPN and HTTP/2 support. 16 | 17 | ### Tags 18 | 19 | Please specify tag when deploying for specific version. 20 | Example: 21 | 22 | `million12/haproxy:latest` 23 | `million12/haproxy:2.1.5` 24 | 25 | ### Features 26 | 27 | * **Support for HTTP/2** with ALPN 28 | * RockyLinux based (migrated at `2.4.16`) 29 | * Ability to **provide any arguments to haproxy** process 30 | Any extra parameters provided to `docker run` will be passed directly to `haproxy` command. 31 | For example, if you run `docker run [run options] million12/haproxy -n 1000` you pass `-n 1000` to haproxy daemon. 32 | * Pretty **lightweight**, only ~100M (with OpenSSL and HAProxy compiled from source). 33 | * **Default [haproxy.cfg](container-files/etc/haproxy/haproxy.cfg) provided** for demonstration purposes. You can easily mount your own or point to different location using `HAPROXY_CONFIG` env. 34 | * **Auto restart when config changes** 35 | This container comes with inotify to monitor changes in HAProxy config and **reload** HAProxy daemon. The reload is done in a way that no connection is lost. 36 | 37 | ### ENV variables 38 | 39 | |Variable|Default Settings|Notes| 40 | |:--|:--|:--| 41 | |`HAPROXY_CONFIG`|`/etc/haproxy/haproxy.cfg`|If you mount your config to different location, simply edit it.| 42 | |`HAPROXY_PORTS`|`80,443`|Comma separated ports| 43 | |`HAPROXY_ADDITIONAL_CONFIG`|Empty|List of file that inotify should monitor for changes divided by space. Example below. Space separated| 44 | |`HAPROXY_PRE_RESTART_CMD`|Empty|Command to execute before restarting haproxy| 45 | |`HAPROXY_POST_RESTART_CMD`|Empty|Command to execute after successfully restarting haproxy| 46 | 47 | ## Usage 48 | 49 | ### Basic 50 | 51 | ```bash 52 | docker run -ti \ 53 | -p 80:80 \ 54 | -p 443:443 \ 55 | million12/haproxy 56 | ``` 57 | 58 | ### Mount custom config , override some options 59 | 60 | ```bash 61 | docker run -d \ 62 | -p 80:80 \ 63 | -v /my-haproxy.cfg:/etc/haproxy/haproxy.cfg \ 64 | million12/haproxy \ 65 | -n 10000 66 | ``` 67 | 68 | Note: in this case config is mounted to its default location, so you don't need to modify 69 | `HAPROXY_CONFIG` variable. 70 | 71 | ### Monitor additional config files 72 | 73 | ```bash 74 | docker run -d \ 75 | -p 80:80 \ 76 | -e HAPROXY_ADDITIONAL_CONFIG='/etc/haproxy/custom1 /etc/haproxy/custom2' \ 77 | million12/haproxy 78 | ``` 79 | 80 | ### Check version and build options 81 | 82 | `docker run -ti million12/haproxy -vv` 83 | 84 | ### Stats 85 | 86 | The default URL for stats is `http://CONTAINER_IP/admin?stats` with username:password ser to `admin:admin`. 87 | 88 | --- 89 | 90 | ## Authors 91 | 92 | Author: Marcin ryzy Ryzycki () 93 | Author: Przemyslaw Ozgo () 94 | -------------------------------------------------------------------------------- /Tests/docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | # Launch Nginx backend server 3 | nginx: 4 | image: million12/nginx 5 | ports: 6 | - '4080:80' 7 | - '4443:443' 8 | environment: 9 | - NGINX_GENERATE_DEFAULT_VHOST=true 10 | 11 | # Launch HAProxy 12 | haproxy: 13 | cap_add: 14 | - NET_ADMIN 15 | image: million12/haproxy 16 | ports: 17 | - '8080:80' 18 | - '8443:443' 19 | links: 20 | - nginx:web.server 21 | -------------------------------------------------------------------------------- /container-files/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -u 4 | 5 | # User params 6 | HAPROXY_CONFIG=${HAPROXY_CONFIG:="/etc/haproxy/haproxy.cfg"} 7 | HAPROXY_ADDITIONAL_CONFIG=${HAPROXY_ADDITIONAL_CONFIG:=""} 8 | HAPROXY_PORTS=${HAPROXY_PORTS:="80,443"} 9 | HAPROXY_PRE_RESTART_CMD=${HAPROXY_PRE_RESTART_CMD:=""} 10 | HAPROXY_POST_RESTART_CMD=${HAPROXY_POST_RESTART_CMD:=""} 11 | HAPROXY_USER_PARAMS=$@ 12 | 13 | # Internal params 14 | HAPROXY_CMD="/usr/local/sbin/haproxy -f ${HAPROXY_CONFIG} -W ${HAPROXY_USER_PARAMS}" 15 | HAPROXY_CHECK_CONFIG_CMD="/usr/local/sbin/haproxy -f ${HAPROXY_CONFIG} -c" 16 | 17 | ####################################### 18 | # Echo/log function 19 | # Arguments: 20 | # String: value to log 21 | ####################################### 22 | log() { 23 | if [[ "$@" ]]; then echo "[`date +'%Y-%m-%d %T'`] $@"; 24 | else echo; fi 25 | } 26 | 27 | ####################################### 28 | # Dump current $HAPROXY_CONFIG 29 | ####################################### 30 | print_config() { 31 | log "Current HAProxy config $HAPROXY_CONFIG:" 32 | printf '=%.0s' {1..100} && echo 33 | cat $HAPROXY_CONFIG 34 | printf '=%.0s' {1..100} && echo 35 | } 36 | 37 | # Launch HAProxy. 38 | # In the default attached haproxy.cfg `web.server` host is used for back-end nodes. 39 | # If that host doesn't exist in /etc/hosts, create it and point to localhost, 40 | # so HAProxy can start with the default haproxy.cfg config without throwing errors. 41 | grep --silent -e "web.server" /etc/hosts || echo "127.0.0.1 web.server" >> /etc/hosts 42 | 43 | log $HAPROXY_CMD && print_config 44 | $HAPROXY_CHECK_CONFIG_CMD 45 | $HAPROXY_CMD & 46 | 47 | HAPROXY_PID=$! 48 | 49 | # Exit immediately in case of any errors or when we have interactive terminal 50 | if [[ $? != 0 ]] || test -t 0; then exit $?; fi 51 | log "HAProxy started with $HAPROXY_CONFIG config, pid $HAPROXY_PID." && log 52 | 53 | # Check if config has changed 54 | # Note: also monitor /etc/hosts where the new back-end hosts might be provided. 55 | while inotifywait -q -e create,delete,modify,attrib /etc/hosts $HAPROXY_CONFIG $HAPROXY_ADDITIONAL_CONFIG; do 56 | log "Restarting HAProxy due to config changes..." && print_config 57 | $HAPROXY_CHECK_CONFIG_CMD 58 | log "Executing pre-restart hook..." 59 | $HAPROXY_PRE_RESTART_CMD 60 | log "Restarting haproxy..." 61 | $HAPROXY_CMD -sf $HAPROXY_PID & 62 | HAPROXY_PID=$! 63 | log "Got new PID: $HAPROXY_PID" 64 | log "Executing post-restart hook..." 65 | $HAPROXY_POST_RESTART_CMD 66 | log "HAProxy restarted, pid $HAPROXY_PID." && log 67 | done 68 | -------------------------------------------------------------------------------- /container-files/etc/haproxy/haproxy.cfg: -------------------------------------------------------------------------------- 1 | global 2 | #debug 3 | chroot /var/lib/haproxy 4 | user haproxy 5 | group haproxy 6 | pidfile /var/run/haproxy.pid 7 | 8 | # Default SSL material locations 9 | ca-base /etc/ssl/certs 10 | crt-base /etc/ssl/private 11 | 12 | # Default ciphers to use on SSL-enabled listening sockets. 13 | ssl-default-bind-options ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.3 no-tls-tickets force-tlsv12 14 | ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS 15 | 16 | spread-checks 4 17 | tune.maxrewrite 1024 18 | tune.ssl.default-dh-param 2048 19 | 20 | defaults 21 | mode http 22 | balance roundrobin 23 | 24 | option dontlognull 25 | option dontlog-normal 26 | option redispatch 27 | 28 | maxconn 5000 29 | timeout connect 5s 30 | timeout client 20s 31 | timeout server 20s 32 | timeout queue 30s 33 | timeout http-request 5s 34 | timeout http-keep-alive 15s 35 | 36 | frontend http-in 37 | bind *:80 38 | http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" 39 | http-request add-header X-Forwarded-Proto https 40 | stats enable 41 | stats refresh 30s 42 | #stats hide-version 43 | stats realm Strictly\ Private 44 | stats auth admin:admin 45 | stats uri /admin?stats 46 | 47 | default_backend nodes-http 48 | 49 | frontend https-in 50 | mode tcp 51 | bind *:443 ssl crt /etc/ssl/private/dummy.pem alpn h2,http/1.1 52 | use_backend nodes-http if { ssl_fc_alpn -i h2 } 53 | default_backend nodes-http 54 | 55 | backend nodes-http 56 | server node1 web.server:80 check 57 | 58 | --------------------------------------------------------------------------------