docker compose up --scale api=3 runs three containers of one service, numbered -1, -2 and -3. A fixed host port stops the second one, because only one container can own a host port. A port range lets each replica take its own, and overriding API_PORT in the shell beats the .env file:
docker compose up -d --scale api=3 2>&1 | grep -o 'Bind for.*allocated'
API_PORT=33000-33002 docker compose up -d --scale api=3 >/dev/null 2>&1
docker compose ps api --format '{{.Name}} {{.Ports}}' | sed 's/, \[::\].*//'
for p in 33000 33001 33002; do curl -s localhost:$p/health; echo; done
docker compose exec db getent hosts api
docker compose up -d --scale api=1 >/dev/null 2>&1; docker compose ps --format '{{.Name}}'Bind for 0.0.0.0:33000 failed: port is already allocated
l3-booknest-api-1 0.0.0.0:33002->3000/tcp
l3-booknest-api-2 0.0.0.0:33000->3000/tcp
l3-booknest-api-3 0.0.0.0:33001->3000/tcp
{"status":"ok","version":"1.3"}
{"status":"ok","version":"1.3"}
{"status":"ok","version":"1.3"}
172.19.0.5 api
172.19.0.3 api
172.19.0.4 api
l3-booknest-api-1
l3-booknest-db-1Inside the network the service name api now resolves to all three addresses, so Docker 514 's DNS spreads new connections across the replicas; a client that caches one address does not. container_name: fixes a container's name and therefore forbids scaling, which is one reason BookNest's file leaves names to Compose 514 . Scaling on one machine only adds processes on the same CPUs; spreading load across machines is the job of Swarm 514 (Docker Swarm in 2026) and Kubernetes 5,150 (Kubernetes).