Proxy

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::

config/deploy.yml
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: false

Auto-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 together

A 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.

Sharing one load balancer between apps#

More than one dash app may point loadbalancer: at the same host. Each app registers its own service on the shared proxy and keeps its own deploy.yml; the load balancer multiplexes them by hostname. The rules dash enforces for that topology:

  • Service state survives a reboot. Services persist in the kamal-loadbalancer-config volume; dash proxy reboot only replaces the container, and rebooting from app A does not drop app B's routes.
  • Service names must be unique across apps. The first app to deploy claims the name on the load balancer host; a second app deploying the same name fails at deploy time instead of silently winning.
  • Apps must agree on proxy/run. All of them boot the same container; a conflicting run configuration fails the deploy.
  • dash proxy remove refuses while other apps are installed on the load balancer host (--force removes it for everyone).

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 it

A normal dash deploy keeps the load balancer's targets current — the explicit deploy subcommand exists for repair, e.g. after restoring a host.