Millau
Ingress proxy and load balancer designed for microservice architectures built on Docker Swarm
Install / Use
/learn @codelev/MillauREADME
Millau
Millau (pronounced mijo) is a free ingress proxy and load balancer designed for microservice architectures built on Docker Swarm.
Traditional proxies require configuration for each path and host, and must be restarted to apply changes. Modern proxies listen to Docker events and configure routes automatically. While traditional proxies store configuration files on mounted volumes, modern proxies store routes in memory. However, modern proxies do not support load balancing across services - Millau does.
Millau
- Listens to Docker events for zero restarts
- Failover retries for guaranteed delivery
- Written in Golang and available as a slim Docker image
Comparison
| Product | Configuration | Multi-service LB | Service discovery | Prometheus metrics | Automatic HTTPS | mTLS | Image size, MB | |------------|---------------|------------------|-------------------|--------------------|-----------------|---------|----------------| | NGINX | File | Yes | No | No | No | Yes | 192 | | HAProxy | File | Yes | No | Yes | No | Yes | 105 | | Envoy | File | Yes | No | Yes | No | Yes | 191 | | Caddy | File | Yes | No | Yes | Yes | Yes | 49 | | Traefik | Labels | No | Yes | Yes | Yes | Yes | 224 | | Millau | Labels | Yes | Yes | Yes | Yes | Yes | 33 |
Performance
| Product | Test type | Avg response (ms) | Shortest response (ms) | |------------|-----------------------------------|-------------------|------------------------| | NGINX | GET application/json | 2.45 | 1.73 | | HAProxy | GET application/json | 2.99 | 1.93 | | Caddy | GET application/json | 2.80 | 1.66 | | Traefik | GET application/json | 2.66 | 1.53 | | Millau | GET application/json | 2.76 | 1.74 | | NGINX | POST application/octet-stream 5Mb | 32.24 | 29.27 | | HAProxy | POST application/octet-stream 5Mb | 23.12 | 20.69 | | Caddy | POST application/octet-stream 5Mb | 23.81 | 20.98 | | Traefik | POST application/octet-stream 5Mb | 23.35 | 21.04 | | Millau | POST application/octet-stream 5Mb | 22.86 | 20.53 |
Quickstart
- Create
docker-compose.proxy.yml:services: proxy: image: codelev/millau:latest volumes: - /var/run/docker.sock:/var/run/docker.sock:ro ports: - "8080:80" networks: - millau networks: millau: external: true - Create
docker-compose.service.yml:services: whoami: image: traefik/whoami deploy: mode: replicated replicas: 3 labels: - "millau.enabled=true" - "millau.port=80" ports: - "80" networks: - millau networks: millau: external: true - Create network:
docker network create --driver=overlay millau - Deploy proxy:
docker stack deploy -c docker-compose.proxy.yml proxy - Deploy service:
docker stack deploy -c docker-compose.service.yml service
Use Cases
Blue-Green
docker stack deploy -c docker-compose.bluegreen.yml bluegreen
curl -i -H 'Host: company.com' localhost:8080/api/
# HTTP 200 blue or green
curl -i -H 'Host: www.company.com' localhost:8080/api/
# HTTP 200 blue or green
curl -i -H 'Host: green.local' localhost:8080/api/
# HTTP 200 green
curl -i -H 'Host: blue.local' localhost:8080/api/
# HTTP 200 blue
curl -i -H 'Host: company.locall' localhost:8080
# HTTP 502 No matching services found
curl -i -H 'Host: company.local' localhost:8080/apii/
# HTTP 502 No matching services found
curl -k --resolve company.local:8443:127.0.0.1 --connect-to company.local:8443 -H 'Host: company.local' https://company.local:8443/api/
# HTTP 200 blue or green
curl -k --http2 --resolve company.local:8443:127.0.0.1 --connect-to company.local:8443 -H 'Host: company.local' https://company.local:8443/api/
# HTTP 200 blue or green
docker stack rm bluegreen
Ingress
docker stack deploy -c docker-compose.ingress.yml ingress
curl -i localhost:8080
# HTTP 200 backend
docker stack rm ingress
Failover
docker stack deploy -c docker-compose.failover.yml failover
curl localhost:8080/rest/echo
# HTTP 200
docker stack rm failover
Maintenance Mode
docker stack deploy -c docker-compose.spa.yml spa
curl -H 'Host: company.com' localhost:8080
# homepage
docker service scale spa_homepage=0
curl -H 'Host: company.com' localhost:8080
# maintenance page
docker service scale spa_homepage=1
curl -H 'Host: company.com' localhost:8080
# homepage
docker stack rm spa
Docker Compose
docker compose up -d
curl -i localhost:8080/rest/echo
# HTTP 200
curl -k --resolve company.local:8443:127.0.0.1 --connect-to company.local:8443 https://company.local:8443/rest/echo
curl -k --http2 --resolve company.local:8443:127.0.0.1 --connect-to company.local:8443 https://company.local:8443/rest/echo
# HTTP 200
docker compose down
Telemetry
Millau exposes the following endpoints on port 9100:
- Healthcheck
/:- responds
upand200 OKwhen healthy, - responds
downand503 Service Unavailablewhen unhealthy.
- responds
- Prometheus metrics
/metrics:
| Metric | Description |
|----------------------------------------|----------------------------------------------------------|
| Ingress | |
| millau_ingress_open_connections | The current count of open connections by port. |
| millau_ingress_requests_total | The total count of requests received by port. |
| millau_ingress_requests_bytes_total | The total size of requests in bytes handled by port. |
| millau_ingress_responses_bytes_total | The total size of responses in bytes handled by port. |
| Load Balancer | |
| millau_lb_successful_requests_total | The total count of requests handled by service. |
| millau_lb_failed_requests_total | The total count of requests not handled by service. |
| millau_lb_requests_bytes_total | The total size of requests in bytes handled by service. |
| millau_lb_responses_bytes_total | The total size of responses in bytes handled by service. |
| millau_lb_retries_total | The count of retries made for service. |
| millau_lb_status | Current service status, 0 for down or 1 for up. |
| millau_lb_request_duration_seconds | Request handling histogram by service. |
| millau_topology | Requests routed to services. |
Millau has an official Grafana dashboard.
You can try it out locally using the docker-compose.yml, which sets up Millau, Echo service, Prometheus and Grafana together.

Once the stack is running, log in Grafana at http://localhost:3000 with username admin and password admin.
The millau.json is already installed and ready to use.
Configuration
Logging Levels
- FATAL: Indicates an unrecoverable error; the process will exit.
- ERROR: Indicates a proxy failure; the process continues running.
- WARN: Indicates client or microservice misbehavior; the process continues running.
- INFO: Indicates normal functional behavior. Default level.
- DEBUG: Indicates step‑by‑step functional behavior.
services:
proxy:
image: codelev/millau:latest
environment:
- LOGGING=DEBUG
...
HTTP and HTTPS Ports
By default, the HTTP and HTTPS ports are 80 and 443. You can change them as follows:
services:
proxy:
image: codelev/millau:latest
environment:
- HTTP=8080
- HTTPS=8443
...
Automatic HTTPS
By default, ACME API is https://api.buypass.com/acme/directory (Buypass AS Certificate Authority, Norway).
You can change is as follows:
services:
proxy:
image: codelev/millau:latest
environment:
- ACME=https://acme-v02.api.letsencrypt.org/directory
...
HTTP Host Matching
The matching logic selects the services whose millau.hosts label matches the domain hierarchy.
It prioritizes:
- exact matches,
- longest suffix.
| Configured | Requested | Selected |
|---------------------|-----------------|-----------|
| one.com two.com | one.com | one.com |
| one.com two.com | www.two.com | two.com |
| one.com two.com | three.two.com | two.com |
| one.com local | blue.local | local |
| * two.com | two.com | two.com |
| * two.com
