Load balancing
One proxy fronts the whole fleet — auto-activated for multi-host roles, shareable between apps.
The idea#
Upstream kamal runs one proxy per host and leaves multi-host traffic distribution to you — external DNS round-robin, a cloud load balancer, or nothing. dash runs a load balancer: a dash-proxy instance that fronts the fleet and distributes requests across all web hosts, while each host keeps its own per-app proxy for gapless deploys.
The load balancer is the layer that sees the whole fleet, so fleet-level concerns live there: TLS termination and certificates, rate limiting and IP lists, the response cache, session affinity, and read routing.
Activation#
Three forms of proxy: loadbalancer::
proxy:
# A dedicated machine (outside `servers:`) or one of the web hosts —
# on a web host, the loadbalancer takes over that host's proxy container.
loadbalancer: lb.example.com
# Or: always run it on the first host of the primary role, even with
# a single host.
loadbalancer: true
# Or: opt out of the automatic activation for multi-host primary roles.
loadbalancer: falseAuto-activation: when the primary role has more than one host and loadbalancer: is unset, dash uses the first web host as the load balancer. Set loadbalancer: false if you front the fleet with your own load balancing — this is the main behavior difference from kamal when migrating a multi-host app.
Where each option applies#
With a load balancer in front, every proxy deploy option has exactly one home — dash enforces the layering so you cannot configure a rate limiter that counts the fleet as one client, or an allow list that blocks the load balancer itself:
- edge — applied only by the load balancer, stripped from the per-host proxies:
host/hosts,ssl(certificates, on-demand, mTLS),ssl_redirect,ssl_staging,ssl_domains,basic_auth,allow_ips,client_ip,rate_limit,session_affinity,canonical_host,redirects,cache,read_routing. - per-app — applied only by the per-host proxies, next to the app:
headers,rewrites,intercept_errors,sleep,compress. - both — each layer runs its own copy, deliberately: healthchecks, response/request timeouts, path timeouts, target pool tuning, buffering,
path_prefix,forward_headers, logging.
Without a load balancer, the single proxy is every layer at once and the whole surface applies to it. See the generated Proxy reference for each option.
Rolling out behind it#
Each web host keeps serving its old container until the new one passes dash-proxy's health check, so booting hosts in parallel never reduces the fleet's capacity. What a serial boot: limit: 1 buys is blast radius — proving the image on one host before the rest — and it pays for it with a full boot per host. A canary keeps the proof and drops the serialisation:
boot:
canary: 1 # first web host boots alone; the other hosts, all roles, boot togetherA failed canary stops the deploy before any other host is touched. limit and wait still apply to the hosts after the canary. Every deploy ends with a timing table under Finished all in — one row per phase and per host, with how long each host waited to become healthy — so the effect of canary, wait, and proxy/healthcheck/interval is visible rather than guessed. See the Booting reference.
Operating it#
dash proxy loadbalancer info # status of the loadbalancer container
dash proxy loadbalancer logs # its request logs
dash proxy loadbalancer deploy # re-register this app's targets
dash proxy loadbalancer start # start it (e.g. after a manual stop)
dash proxy loadbalancer stop # stop itA normal dash deploy keeps the load balancer's targets current — the explicit deploy subcommand exists for repair, e.g. after restoring a host.