Skip to content

Handle loadbalancing for multiple web hosts#1490

Open
mhenrixon wants to merge 17 commits intobasecamp:mainfrom
mhenrixon:feat/loadbalancing
Open

Handle loadbalancing for multiple web hosts#1490
mhenrixon wants to merge 17 commits intobasecamp:mainfrom
mhenrixon:feat/loadbalancing

Conversation

@mhenrixon
Copy link
Copy Markdown

@mhenrixon mhenrixon commented Apr 11, 2025

Adding Load Balancer to Kamal

This PR adds load balancing to Kamal with minimal changes. It spreads traffic across multiple app servers while keeping things simple.

Key Design Choices

Simple Approach

  • Uses an unreleased kamal-proxy image as the load balancer
  • Add a loadbalancer key to your proxy settings
  • Works automatically when you have multiple web hosts

Easy Integration

  • Managed through existing proxy commands
  • Updates automatically during deployments
  • Works without configuration when multiple web hosts are detected

Configuration Options

  • Set a specific host: loadbalancer: lb.example.com
  • Or use default: first host becomes the load balancer
  • Handles port mapping (80/443) and SSL automatically

Why We Did It This Way

We built the load balancer into the proxy system because:

  1. It's familiar to existing users
  2. Reuses code we already have
  3. Makes sense conceptually
  4. Requires minimal setup

How It Works

  • Added load balancer capability to the proxy system
  • Auto-detects when load balancing should be used
  • Created simple commands for managing it
  • Made it work with the normal deployment process

Configuration Example

proxy:
  hosts:
    - web-1.example.com
    - web-2.example.com
  loadbalancer: lb.example.com  # Optional - uses first host if not specified
  ssl: true

To turn off load balancing when using multiple roles:

proxy:
  hosts:
    - web-1.example.com
    - web-2.example.com
  loadbalancer: false
  ssl: true

Usage

Load balancing turns on when:

  1. You add a loadbalancer setting, OR
  2. You have multiple hosts

Basic commands:

kamal proxy loadbalancer start    # Start it
kamal proxy loadbalancer stop     # Stop it
kamal proxy loadbalancer deploy   # Update config
kamal proxy loadbalancer info     # Check status
kamal proxy loadbalancer logs     # View logs

Future Plans

We can add more features later:

  • Better load balancing methods
  • Health checks
  • Custom routing

This simple approach makes load balancing available to everyone without complicated setup.

@mhenrixon mhenrixon force-pushed the feat/loadbalancing branch from 3da7b51 to a1d1046 Compare May 19, 2025 16:52
Resolved conflicts:
- lib/kamal/cli/proxy.rb: Combined upstream's host-specific proxy
  pattern (KAMAL.proxy(host)) with local info logging statements
- test/integration/docker/deployer/app/config/deploy.yml: Kept both
  loadbalancer: false and run: registry: registry:4443 configurations
Update reference from Proxy::Boot::MINIMUM_VERSION to
Proxy::Run::MINIMUM_VERSION after upstream refactoring moved
the constant location.
- Create test/commands/loadbalancer_test.rb with 20 tests covering:
  - Container lifecycle (run, start, stop, start_or_run)
  - Deploy command with targets, SSL, and multiple hosts
  - Info, version, logs commands
  - Container/image removal and directory management

- Add 11 loadbalancer CLI tests to test/cli/proxy_test.rb:
  - boot, reboot, details with loadbalancer
  - loadbalancer subcommands (info, start, stop, logs, deploy)
  - remove_container/remove_image with loadbalancer

- Create test/fixtures/deploy_with_loadbalancer.yml fixture
When the loadbalancer is configured on a server that also runs a proxy
(i.e., a web server), the loadbalancer now takes over the proxy role:

- Skip booting separate proxy container on loadbalancer host
- Loadbalancer container uses "kamal-proxy" name (not "load-balancer")
- Loadbalancer uses proxy-compatible volume mounts for app deployments
- Container label matches proxy for proper cleanup

This eliminates the port conflict (both use 80/443) and reduces the
number of containers needed when loadbalancer is on a web server.

Changes:
- Add loadbalancer_on_proxy_host? method to detect shared host
- Update boot/reboot to skip proxy on loadbalancer host
- Loadbalancer container_name is dynamic based on host type
- Add proxy-compatible volume mounts when on proxy host

Tests added for shared host scenario.
@dmoac
Copy link
Copy Markdown

dmoac commented Jan 31, 2026

@mhenrixon - Hey!

Do you need any help on this PR to be merged?

Also - question: this assumes that we have at least two different targets/hosts: 1) one running Kamal proxy and 2) the second one running like a replica. Nevertheless, if we are would want a "real" loadbalancer we would need at least 3 different hosts right? 1 for the loadbalancer, and 2 others to run 2 replicas

Am I missing something one this?

@mhenrixon
Copy link
Copy Markdown
Author

Also - question: this assumes that we have at least two different targets/hosts: 1) one running Kamal proxy and 2) the second one running like a replica. Nevertheless, if we are would want a "real" loadbalancer we would need at least 3 different hosts right? 1 for the loadbalancer, and 2 others to run 2 replicas

No, i have changed it so that the extra host shouldn't be needed.

If the current host has a rails server (web ip), i combine the load balancer and proxy to be one.

Works as long as we have the same docker image.

@dmoac
Copy link
Copy Markdown

dmoac commented Jan 31, 2026

Also - question: this assumes that we have at least two different targets/hosts: 1) one running Kamal proxy and 2) the second one running like a replica. Nevertheless, if we are would want a "real" loadbalancer we would need at least 3 different hosts right? 1 for the loadbalancer, and 2 others to run 2 replicas

No, i have changed it so that the extra host shouldn't be needed.

If the current host has a rails server (web ip), i combine the load balancer and proxy to be one.

Works as long as we have the same docker image.

Yeah! It makes total sense! If that server that is running the proxy+LB is unstable then we would have a problem of trying to route the requests

@mhenrixon
Copy link
Copy Markdown
Author

If that server that is running the proxy+LB is unstable then we would have a problem of trying to route the requests

Absolutely, I just wanted the to also offer the cheapest way possible to loadbalance.

The thing is, the load balancer and kamal proxy is not the problem and if the rails server is having problems you can always separate the two.

Since it is running docker containers you really shouldn't have any issues.

I have used this on fairly large projects and never had any issues.

@RocKhalil
Copy link
Copy Markdown

isn't it better to send requests via private networks instead of public ips ? isn't it faster for the servers to communicate with each other ?

I think there should be a configuration on the host where you can set both public and private IPs.

Copilot AI review requested due to automatic review settings March 26, 2026 04:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces optional load balancing support to Kamal’s proxy layer by adding a proxy.loadbalancer configuration key, wiring load balancer lifecycle/config updates into existing CLI flows, and adding test coverage/fixtures for the new behavior.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Changes:

  • Add proxy.loadbalancer and load-balancing detection to proxy configuration/validation.
  • Add a new Kamal::Commands::Loadbalancer + Kamal::Configuration::Loadbalancer, and expose management via kamal proxy loadbalancer ....
  • Update deploy/redeploy/boot/reboot flows and add fixtures + unit/CLI/integration tests for load balancing scenarios.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
test/integration/docker/deployer/app_with_roles/config/deploy.yml Disables load balancer in an integration config to avoid interference.
test/integration/docker/deployer/app/config/deploy.yml Disables load balancer in an integration config to avoid interference.
test/fixtures/deploy_with_loadbalancer_on_proxy_host.yml Adds fixture where LB runs on a web/proxy host.
test/fixtures/deploy_with_loadbalancer.yml Adds fixture where LB runs on a dedicated host.
test/configuration/proxy_test.rb Adds configuration tests for loadbalancer/effective host and derived options.
test/commands/loadbalancer_test.rb Adds command builder tests for new load balancer commands.
test/cli/proxy_test.rb Adds CLI tests validating boot/reboot/details and LB subcommands/flows.
test/cli/cli_test_case.rb Stubs load_balancing? by default to keep existing CLI tests stable.
lib/kamal/configuration/validator/proxy.rb Adjusts proxy validation logic in presence of loadbalancer.
lib/kamal/configuration/role.rb Adjusts SSL multi-host validation to account for load balancer usage.
lib/kamal/configuration/proxy.rb Adds load balancer config accessors and load-balancing behavior.
lib/kamal/configuration/loadbalancer.rb Introduces load balancer-specific configuration wrapper.
lib/kamal/configuration/docs/proxy.yml Documents the new loadbalancer option in proxy configuration docs.
lib/kamal/configuration.rb Adds top-level load_balancing? helper.
lib/kamal/commands/proxy.rb Adds a load balancer command accessor on proxy commands.
lib/kamal/commands/loadbalancer.rb Adds new command builder for load balancer lifecycle/config/logs.
lib/kamal/commander.rb Adds load balancer configuration + command construction.
lib/kamal/cli/proxy.rb Adds LB boot/reboot/details hooks and kamal proxy loadbalancer subcommand.
lib/kamal/cli/main.rb Updates deploy/redeploy to push LB configuration updates post app boot.
lib/kamal/cli/build.rb Changes registry login behavior during build push.
CLAUDE.md Adds repository guidance documentation for Claude Code.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants