Skip to content

Instantly share code, notes, and snippets.

@briceburg
Last active April 22, 2025 20:00
Show Gist options
  • Save briceburg/babe00e0a924a46f54af95b5aa8b6fa3 to your computer and use it in GitHub Desktop.
Save briceburg/babe00e0a924a46f54af95b5aa8b6fa3 to your computer and use it in GitHub Desktop.
TCP Proxy to a Postgres Database - HAProxy Configuration Example

HAProxy Configuration Example

TCP Proxy to a Postgres Database

Usage

docker compose up -d
docker compose run test

🚀

---
services:
pghost:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: foo
proxy:
build: .
environment:
PROXY_HOST: pghost
PROXY_PORT: 5432
ports:
- 5432:5432
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
test:
image: postgres:15-alpine
entrypoint: ""
command: |
sh -c '
pg_isready --host=proxy --username=postgres
'
profiles:
- test
FROM haproxy:2.9-alpine
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY healthcheck.sh /usr/local/bin/healthcheck.sh
HEALTHCHECK CMD ["/usr/local/bin/healthcheck.sh"]
ARG HEALTHCHECK_PORT="8080"
ARG METRICS_PORT="8405"
# align client and server timeouts with underlying server (e.g. match databaseidle_session_timeout value)
# WARNING: Ports Must be > 1024 in AWS Fargate -- https://github.com/aws/containers-roadmap/issues/1721
ENV \
CLIENT_TIMEOUT="180m" \
HEALTHCHECK_PORT="${HEALTHCHECK_PORT}" \
MAX_CONNECTIONS="1024" \
METRICS_PORT="${METRICS_PORT}" \
PROXY_HOST="replace-me" \
PROXY_PORT="replace-me" \
SERVER_TIMEOUT="180m"
global
maxconn 24000
log stderr format raw daemon info
defaults
# log-format "{\"client_ip\":\"%ci\",\"duration\":%Tt,\"bytes\":%B,\"terminaton_state\":\"%ts\",\"actconn\":%ac,\"beconn\":%bc,\"feconn\":%fc,\"retries\":%rc,\"srv_queue\":%sq,\"backend_queue\":%bq}"
option tcplog
option dontlognull
retries 3
timeout check 2s
timeout connect 1s
# align client and server timeouts with underlying server (e.g. match databaseidle_session_timeout value)
timeout client "$CLIENT_TIMEOUT"
timeout server "$SERVER_TIMEOUT"
maxconn "$MAX_CONNECTIONS"
resolvers dns
parse-resolv-conf
frontend healthcheck
bind *:"$HEALTHCHECK_PORT"
mode http
monitor fail if { nbsrv(host) eq 0 }
monitor-uri /status
maxconn 16
frontend metrics
bind *:"$METRICS_PORT"
mode http
http-request use-service prometheus-exporter if { path /metrics }
no log
maxconn 16
frontend main
bind *:"$PROXY_PORT"
default_backend host
log global
mode tcp
backend host
server h1 "$PROXY_HOST":"$PROXY_PORT" resolvers dns check maxconn "$MAX_CONNECTIONS"
#!/usr/bin/env sh
wget -SO - "http://127.0.0.1:${HEALTHCHECK_PORT}/status" > /dev/null 2>&1 || exit 1
@briceburg
Copy link
Author

In the above, the server FQDN is looked up once one startup and cached. In order to respond to PROXY_HOST IP changes, a resolvers section must be used;

haproxy.cfg with runtime DNS lookups

global
  log stderr format raw daemon info

defaults
  mode  tcp
  log  global
  option dontlognull
  option redispatch
  retries 3
  maxconn 32

resolvers foo
  parse-resolv-conf

frontend larry
  bind *:${PROXY_PORT}
  default_backend curly

backend curly
  server moe ${PROXY_HOST}:${PROXY_PORT} resolvers foo check

HAProxy will now log IP changes;

curly/moe changed its IP from 172.16.238.36 to 172.16.238.33 by foo/127.0.0.11.

to simulate this, an extended docker compose file can be used;

  • use the below docker compose file, docker compose up
  • change the IP address of pghost, e.g. from "172.16.238.33" to "172.16.238.34" and run docker compose up pghost -d

docker-compose.yml with set networks and IPs

---
services:
  pghost:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: foo
    networks:
      foo:
        ipv4_address: "172.16.238.33"
  proxy:
    build: .
    environment:
      PROXY_HOST: pghost
      PROXY_PORT: 5432
    networks:
      - foo
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
  test:
    image: postgres:15-alpine
    entrypoint: ""
    networks:
      - foo
    command: |
      sh -c '
        pg_isready --host=proxy --username=postgres
      '
    profiles:
      - test

networks:
  foo:
    ipam:
      driver: default
      config:
        - subnet: "172.16.238.0/24"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment