Introduction #
Imagine you are working with Docker Compose and you have deployed your Python or Node.js app with it. You might have noticed that these are usually single-threaded, and you need multiple instances of the same app to make use of the tons of CPU your server has.
When dealing with high traffic or CPU-intensive operations, running multiple replicas of your service is crucial to distribute the load and make full use of your server’s resources.
In that case, you might have discovered that you can use the deploy.replicas
count configuration attribute in Docker Compose and saw this error:
Attaching to docker-replica-test-node-1, docker-replica-test-node-2
docker-replica-test-node-1 | Server is running at http://localhost:8080/
Error response from daemon: driver failed programming external connectivity on endpoint docker-replica-test-node-2:
Bind for 0.0.0.0:8080 failed: port is already allocated
Problem #
The issue occurs because each container is trying to bind to the same port (8080), but Docker Compose cannot allocate the same port to multiple containers. Docker Compose lacks built-in load balancing for service ports, which leads to a conflict when replicas attempt to use the same port.
Here’s an example of a docker-compose.yml
file that causes the issue:
version: '3.7'
services:
node:
image: node:22
working_dir: /usr/src/app
volumes:
- ./index.js:/usr/src/app/index.js
ports:
- "8080:8080"
command: node index.js
deploy:
replicas: 2
Solution: Switching to Docker Swarm #
To solve this issue, we need to switch from Docker Compose to Docker Swarm, which does provide port load balancing and allows us to create multiple instances (replicas) of containers.
Initialize Docker Swarm #
At first, let’s initialize Docker Swarm
docker swarm init
Which will produce the following output:
The token provided is only relevant if you plan to add additional worker or manager nodes to this Docker Swarm cluster.
It can be ignored for now.
docker swarm init
Swarm initialized: current node (izw30cwofxm9brukpg6hi2x8s) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-5wpisj9wudh44z2verbi3fq7omczx7qur0b6jb7zmexeg4tb2t-f0vpjofp761d8p0jfg3yxv6wn 192.168.65.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Deploy the Stack #
Now, deploy the docker-compose.yml
using Docker Swarm by running:
docker stack deploy -c docker-compose.yml test-node
Check Deployed Containers #
After that, you will see that the actual containers do not expose any ports:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0e8463b14b19 node:22 "docker-entrypoint.s…" 54 seconds ago Up 53 seconds test-node_node.2.0ln7zb4v5tf6heqd2o9f025pr
4bc37314a1c7 node:22 "docker-entrypoint.s…" 54 seconds ago Up 53 seconds test-node_node.1.tepsgvoapo6byjpww2uptco10
Check Service Ports #
The service is now exposing the ports and load balances them across replicas:
docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
yc4rxda0obwv test-node_node replicated 2/2 node:22 *:8080->8080/tcp
Test the Service #
Now, check if the ports are exposed and working:
curl localhost:8080
Hello, World!
Your service is now correctly load-balanced across multiple replicas.
Docker Stack Helpful Commands #
Deploy a Stack #
docker stack deploy -c docker_compose.yml <stack_name>
For example:
docker stack deploy -c docker-compose.yml test-node
Creating network test-node_default
Creating service test-node_node
Delete a Stack #
docker stack rm <stack_name>
For example:
docker stack rm test-node
Removing service test-node_node
Removing network test-node_default
List All Stacks #
docker stack ls
For example:
docker stack ls
NAME SERVICES
test-node 1
List Services in a Stack #
docker stack ps <stack_name>
For example:
docker stack services test-node
ID NAME MODE REPLICAS IMAGE PORTS
rrloc0uy9wet test-node_node replicated 2/2 node:22 *:8080->8080/tcp
List Containers in a Stack #
docker stack ps <stack_name>
For example:
docker stack ps test-node
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
l01gp6p6dhgn test-node_node.1 node:22 linuxkit-d68c0953dede Running Running about a minute ago
xc0280lsexja test-node_node.2 node:22 linuxkit-d68c0953dede Running Running about a minute ago
Conclusion #
With Docker Swarm, it is possible to load balance single-threaded applications using the deploy.replicas
count, making efficient use of all available CPU resources. While Docker Compose is excellent for local development, Swarm provides the additional functionality required for production environments that need scaling and load balancing across replicas.
If you’re looking for even more advanced orchestration, Kubernetes might be worth exploring.
Resources #
Docker Stack Command Reference Docker Stack Deploy Command Reference