Docker Compose: Scaling Services Locally with deploy.replicas
AI generated
FROM
RUN
Docker · Docker Compose · Scaling · Load Balancing
Docker Compose Replicas
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.

16 min read deploy.replicas · --scale · Load Balancing Docker Compose 2.x

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.

11. FAQ: Scaling Docker Compose Services with deploy.replicas

1What does deploy.replicas do?
Starts multiple identical instances of the same service with its own container name.
2--scale vs. deploy.replicas?
deploy.replicas is static in the file, --scale overrides it temporarily via the command line.
3Why does replicas with ports fail?
Multiple containers cannot share a fixed host port, use expose instead of ports.
4How do I distribute load?
Nginx as reverse proxy with Docker's DNS resolver for round robin distribution across replicas.
5What problems does a test uncover?
Local sessions, non shared volumes, and cron jobs running multiple times in parallel.
6Suitable for production?
Not recommended, no automatic redistribution on failure or multi host distribution.
7How do I handle cron jobs?
Dedicated service with exactly one replica, while web workers scale freely.
8Are all deploy options supported?
No, only a subset, Swarm specific options are ignored.
9Where should sessions live?
In a central Redis store, not in a single container's local filesystem.
10Does this replace a real load test?
No, it uncovers structural problems, but does not replace a load test under real conditions.