How to Set Up Load Balancing with Apache and Docker Containers

How to set up load balancing with Apache and Docker containers

Once I started containerizing my applications, I had to rethink how load balancing worked. Backend servers were no longer fixed IPs sitting on physical or virtual machines — they were ephemeral containers that could be recreated, rescheduled, or scaled at any moment. In this post, I’ll walk through how I set up Apache as a load balancer in front of Docker containers, including the networking details that tripped me up the first time around.

Why Combine Apache with Docker for Load Balancing

Docker makes it trivial to spin up multiple identical instances of an application. Apache, running either on the host or in its own container, can sit in front of those instances and distribute traffic across them — giving you the benefits of load balancing without needing a separate hardware appliance or cloud load balancer for smaller deployments.

Prerequisites

  • Docker and Docker Compose installed
  • Basic familiarity with Dockerfiles and docker-compose.yml
  • Apache 2.4+ (I’ll run this in its own container)
  • Your application containerized and ready to run multiple instances

Step 1: Set Up Your Application Containers

Here’s a simple example using a Node.js app, but the same principles apply to PHP, Python, or any other backend.

# Dockerfile for app
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]

Step 2: Create a Docker Compose File with Multiple App Instances

version: "3.8"

services:
  app1:
    build: ./app
    container_name: app1
    networks:
      - appnet

  app2:
    build: ./app
    container_name: app2
    networks:
      - appnet

  app3:
    build: ./app
    container_name: app3
    networks:
      - appnet

  apache-lb:
    image: httpd:2.4
    container_name: apache-lb
    ports:
      - "80:80"
    volumes:
      - ./apache/httpd.conf:/usr/local/apache2/conf/httpd.conf
      - ./apache/balancer.conf:/usr/local/apache2/conf/extra/balancer.conf
    depends_on:
      - app1
      - app2
      - app3
    networks:
      - appnet

networks:
  appnet:
    driver: bridge

Notice I’m not exposing ports on app1, app2, and app3 to the host — only Apache needs to reach them, and it does so over the internal appnet Docker network using container names as hostnames.

Step 3: Configure Apache to Reference Container Names

This is the part that’s genuinely different from a traditional setup: instead of IP addresses, you reference Docker container names, which Docker’s internal DNS resolves automatically.

# apache/balancer.conf
<Proxy "balancer://appcluster">
    BalancerMember "http://app1:3000"
    BalancerMember "http://app2:3000"
    BalancerMember "http://app3:3000"
    ProxySet lbmethod=byrequests
</Proxy>

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass "/" "balancer://appcluster/"
    ProxyPassReverse "/" "balancer://appcluster/"
</VirtualHost>

Make sure your main httpd.conf loads the necessary modules and includes this file:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so

Include conf/extra/balancer.conf

Step 4: Start the Stack

docker-compose up -d --build

Verify all containers are running:

docker-compose ps

Test the load balancer:

for i in {1..6}; do curl -s http://localhost/ ; echo; done

If your app returns something identifying which instance responded (like a hostname or container ID), you should see requests rotating across app1, app2, and app3.

Step 5: Scaling Containers Dynamically

One of the advantages of this setup is how easily you can scale. With Docker Compose:

docker-compose up -d --scale app1=3

However, I’ll be honest — Apache’s static BalancerMember configuration doesn’t automatically pick up new containers created this way. For truly dynamic scaling, you have two practical options:

  1. Manually update balancer.conf and reload Apache whenever you scale (fine for smaller, less dynamic setups).
  2. Use a service discovery / templating tool like consul-template or a custom script that regenerates balancer.conf based on currently running containers, then triggers apachectl graceful.

For most small-to-medium deployments I’ve worked on, option one combined with a simple deploy script has been sufficient.

Step 6: Reload Apache After Configuration Changes

docker exec apache-lb httpd -k graceful

This applies new balancer members without dropping active connections — important if you’re scaling under live traffic.

Real-World Use Cases

  • Local development environments that mimic production load balancing behavior using Docker Compose.
  • Small-to-medium production deployments where a full orchestration platform like Kubernetes is overkill, but you still want horizontal scaling and load distribution.
  • CI/CD pipelines that spin up ephemeral multi-container environments to test load-balanced behavior before deploying.

Troubleshooting Common Issues

Apache Can’t Resolve Container Names — confirm all services are on the same Docker network (appnet in this example); containers on different networks can’t resolve each other by name.

502 Bad Gateway Immediately After Startup — Apache may have started before the app containers were ready. Add a healthcheck and depends_on with condition: service_healthy:

app1:
  build: ./app
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
    interval: 5s
    retries: 5

Balancer Config Not Reflecting After Rebuild — if you mounted balancer.conf as a volume, confirm the host file was actually updated; Docker won’t rebuild a bind-mounted config file automatically.

Security Best Practices

  • Never expose backend container ports directly to the host unless needed for debugging.
  • Use a dedicated Docker network for backend-to-load-balancer traffic, isolated from other services.
  • Keep your httpd:2.4 base image updated to pick up security patches.
  • If running Apache itself in Docker, avoid running the container as root where possible.

Performance Optimization Tips

  • Use Docker’s --restart unless-stopped policy on app containers so they recover automatically from crashes, working alongside Apache’s failover.
  • Keep container images lean to reduce startup time, which matters when scaling out quickly.
  • Monitor container resource usage (docker stats) alongside Apache’s balancer manager for a complete picture of your cluster’s health.

FAQs

Should I run Apache in a container or on the host when load balancing Docker containers? Either works. Running Apache in its own container keeps your setup fully portable and consistent across environments, which is what I generally prefer for reproducibility.

Does this approach work with Kubernetes instead of Docker Compose? The load balancing concepts are similar, but Kubernetes typically handles this natively through Services and Ingress controllers rather than a manually configured Apache balancer. Apache can still be used as an Ingress controller in some setups, but the configuration approach differs from what’s shown here.

How do I handle SSL/TLS in this setup? Terminate SSL at the Apache container using mod_ssl, then proxy plain HTTP to backend containers over the internal Docker network, which is generally considered safe since that traffic never leaves the host.

Summary and Key Takeaways

Load balancing with Apache and Docker containers follows the same core mod_proxy_balancer principles as a traditional setup, but with container names replacing static IPs and Docker networking handling internal DNS resolution. The main gap to be aware of is that Apache’s balancer configuration is static by default — scaling dynamically requires either manual config updates or a templating/service-discovery layer on top.

References

Total
1
Shares

Leave a Reply

Previous Post
How to configure health checks for load-balanced servers

How to Configure Health Checks for Load-Balanced Servers

Next Post
How to use mod_proxy_balancer for load distribution

How to Use mod_proxy_balancer for Load Distribution

Related Posts