Testing multiple instances locally
Before a service runs in production with multiple replicas in Kubernetes or Docker Swarm, it is worth testing locally with deploy.replicas in Docker Compose. This way, port conflicts, shared volumes and load balancing behavior can be uncovered on the development machine, instead of noticing them only after a production rollout.
Table of Contents
- 1. Why local scaling tests make sense
- 2. Defining deploy.replicas in compose.yaml
- 3. The --scale option for spontaneous scaling
- 4. Avoiding port conflicts with multiple replicas
- 5. Load balancing with Nginx as a reverse proxy
- 6. Shared volumes and session handling with multiple instances
- 7. Practical example: scaling PHP-FPM workers horizontally
- 8. Limits of replicas in Docker Compose compared to Swarm and Kubernetes
- 9. Local scaling compared to production orchestration
- 10. Summary
- 11. FAQ
1. Why local scaling tests make sense
Many applications are developed with the implicit assumption that exactly one instance of a service is always running. As soon as a service runs in production with multiple replicas behind a load balancer, problems show up that were never visible in day to day development with only one instance: sessions stored only in a container's local memory, files written to a local volume instead of shared storage, or race conditions on concurrent access to the same resource.
Docker Compose replicas allow exactly this behavior to be tested locally, long before a service runs in production with real replicas in Kubernetes or Docker Swarm. With deploy.replicas or the --scale option, multiple instances of the same service can be started in parallel, which surfaces problems with stateful containers, missing load balancing or misconfigured shared resources, before they cause real outages in a production environment.
The effort for this test is small compared to the benefit. A single parameter in the compose.yaml or an extra flag on the docker compose up call is enough to simulate the same horizontal scaling that will later be used in production. For teams that want to prepare their application for horizontal scaling, this local test is an inexpensive first step before more expensive orchestration tools come into play.
2. Defining deploy.replicas in compose.yaml
The deploy block in Docker Compose originally comes from the Docker Swarm context, but is also supported by the plain Docker Compose engine for a subset of its options, including replicas. With replicas: 3 inside deploy, docker compose up starts three identical instances of the same service block, each with its own container name, but identical configuration regarding image, environment variables and volumes.
Important to know: not all deploy options are supported by Docker Compose outside of Swarm. resources.limits and resources.reservations are now respected, while other Swarm specific options like update_config are ignored. For the pure purpose of testing multiple instances locally, replicas alone is entirely sufficient.
# compose.yaml — running multiple instances of the same service
services:
api:
build: .
deploy:
replicas: 3
environment:
SERVICE_NAME: api
networks:
- backend
networks:
backend:
A central point: a service with replicas must not define a fixed host side port binding like "8080:8080", because multiple containers cannot occupy the same host port at the same time. Docker Compose reports an error at startup in this case. For scaled services, the port must either be left out entirely, provided access happens through the internal Docker network, or handled through a reverse proxy with dynamic target discovery.
3. The --scale option for spontaneous scaling
Besides the static definition in compose.yaml, Docker Compose also allows spontaneous scaling from the command line with docker compose up --scale service=count. This approach is practical for quick experiments without having to change the compose.yaml itself, and is especially suited for temporary load tests where the number of instances needs to be varied multiple times.
An important difference: if a deploy.replicas value is already set in the compose.yaml, the --scale option overrides that value for the current invocation, without changing the file itself. This allows keeping the default value in the file low for normal development, for example at 1, and only raising it via the command line for a targeted scaling test when needed.
# Scale a service to 5 instances for this run only
docker compose up -d --scale api=5
# Check how many instances are actually running
docker compose ps api
# Scale back down without restarting the whole stack
docker compose up -d --scale api=1
4. Avoiding port conflicts with multiple replicas
The most common mistake on the first attempt to scale a service is a fixed port mapping like ports: ["8080:8080"] on the service in question. As soon as replicas is set greater than 1, Docker Compose tries to bind the same host port for multiple containers, which inevitably fails. The solution is either to entirely forgo an explicit host port binding and make the service reachable only through the internal Docker network, or to let Docker assign the port mapping dynamically.
For the second approach, it is enough to specify only the container port without a fixed host port in the ports entry, for example "8080" instead of "8080:8080". Docker then automatically assigns a free host port for each replica, which can be determined via docker compose port or docker ps. For most production like setups, this approach is less relevant though, because access is meant to go through a reverse proxy anyway, which knows the internal port mapping itself.
# compose.yaml — no fixed host port, safe for multiple replicas
services:
api:
build: .
deploy:
replicas: 3
expose:
- "8080"
networks:
- backend
networks:
backend:
5. Load balancing with Nginx as a reverse proxy
For multiple replicas to actually provide value, an instance is needed that distributes incoming requests across the available containers. In a local Docker Compose environment, this role is typically taken by Nginx as a reverse proxy, which automatically reaches all running replicas through the service's internal Docker DNS name, without having to manually configure each individual IP address.
Docker's built in DNS resolver automatically resolves the service name to all running container IPs of a scaled service, which Nginx can use with the resolver directive and an upstream definition to distribute requests in a round robin fashion. This setup very closely simulates locally how a production load balancer would work in front of multiple replicas in Kubernetes or Swarm.
# nginx.conf — load balancing across scaled replicas
upstream api_backend {
# Docker's embedded DNS resolves "api" to all running replica IPs
server api:8080;
}
server {
listen 80;
location / {
proxy_pass http://api_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# compose.yaml — Nginx in front of a scaled API service
services:
api:
build: .
deploy:
replicas: 3
expose:
- "8080"
networks:
- backend
nginx:
image: nginx:1.27-alpine
ports:
- "8000:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
networks:
- backend
networks:
backend:
6. Shared volumes and session handling with multiple instances
As soon as multiple replicas of a service run in parallel, every assumption that implicitly relied on exactly one instance becomes visible. Sessions stored in a PHP container's local filesystem only work correctly as long as the same user keeps getting routed to the same instance. Once the load balancer forwards a request to a different replica that does not know the session file, the application incorrectly reports the user as logged out.
This class of bugs can only be reliably uncovered through local scaling tests, before they turn into real support cases in production. The usual solution is storing sessions in a central Redis store instead of the local filesystem, so every replica can access the same session state, regardless of which instance actually processes a given request. The same applies to uploaded files, which must live either on a shared volume or in object storage, rather than locally on a single container's filesystem.
7. Practical example: scaling PHP-FPM workers horizontally
For PHP applications, especially Magento shops with high request load, testing multiple PHP-FPM replicas is a direct way to check whether the application actually works statelessly. A PHP-FPM service without a fixed port binding, combined with Redis for sessions and full page cache as well as a shared volume or object storage for media files, can easily be scaled up locally with deploy.replicas: 3.
Such a test reliably reveals whether assumptions still exist in the code that rely on exactly one instance, for example local file caching outside of Redis, in memory counters without central synchronization, or cron jobs that accidentally run multiple times in parallel with several replicas and trigger duplicate processing. For cron jobs, it is advisable in a scaled setup to use a dedicated service with exactly one replica, while the web workers themselves can scale freely.
8. Limits of replicas in Docker Compose compared to Swarm and Kubernetes
Docker Compose replicas are excellent for local tests, but are not a full replacement for a production orchestration platform. Docker Compose does not come with automatic healthcheck based redistribution when a replica fails, no automatic horizontal scaling based on load, and no distribution across multiple physical hosts. All replicas run on the same Docker engine, usually the same development machine or the same CI runner.
For production environments with real requirements for fault tolerance, automatic scaling and distribution across multiple nodes, Kubernetes or Docker Swarm remain the right tools. Docker Compose replicas are meant to test the basic scaling behavior of an application early and cheaply, not to replace a complete production orchestration.
9. Local scaling compared to production orchestration
Comparing local Docker Compose replicas to a production orchestration platform clearly shows what each approach is meant for and where the respective limits lie.
| Capability | Docker Compose replicas | Docker Swarm | Kubernetes |
|---|---|---|---|
| Starting multiple instances | Yes, locally | Yes | Yes |
| Distributing across multiple hosts | No | Yes | Yes |
| Automatic redistribution on failure | No | Yes | Yes |
| Automatic scaling based on load | No | Limited | Yes (HPA) |
| Setup effort for a test | Minimal | Medium | High |
For the pure question of whether an application can even correctly handle multiple concurrent instances, the minimal setup effort of Docker Compose replicas is a clear advantage over Swarm or Kubernetes. Once the application demonstrably masters this behavior, switching to a real orchestration platform is the next logical step for production fault tolerance and scaling across multiple hosts.
Mironsoft
Docker scaling tests and Magento performance
Do you know if your shop really scales horizontally?
We test existing Magento and PHP stacks locally with multiple replicas, uncover stateful assumptions and prepare applications for production scaling.
Scaling test
Setting up deploy.replicas locally and identifying session and storage problems
Load balancing
Configuring and validating an Nginx reverse proxy for scaled services
Production readiness
Setting up Redis sessions, object storage and dedicated cron services
10. Summary
Docker Compose replicas make it possible to test an application's scaling behavior locally, long before a service runs in production with multiple instances in Kubernetes or Docker Swarm. With deploy.replicas in the compose.yaml or the spontaneous --scale option, multiple containers of the same configuration can be started in parallel, which surfaces problems with sessions, shared volumes and missing load balancing early.
For real load distribution, a reverse proxy like Nginx is also needed, using Docker's internal DNS resolver to automatically distribute requests across all running replicas. It remains important that Docker Compose replicas are not a replacement for a full orchestration platform, because neither automatic redistribution on failure nor distribution across multiple hosts is supported. For an early, inexpensive test of an application's scaling behavior, the approach is exactly right, though.
Docker Compose Replicas — The Essentials at a Glance
deploy.replicas
Static definition of the instance count in compose.yaml, ideal for repeatable tests.
--scale option
Spontaneous scaling from the command line, overrides the value from the file for the current run.
No fixed port binding
Scaled services must not occupy a fixed host port, use expose instead of ports.
Checking statelessness
Sessions and uploads must live centrally in Redis or object storage, not locally in the container.