Autoscaling with Docker Compose (practical patterns)¶
Docker Compose does not provide native autoscaling (no HPA equivalent). However, you can achieve practical, metrics-informed scaling during local/dev or simple single-host deployments using the following approaches.
These patterns work with this repo’s compose stack (hub, workers, redis, prometheus, grafana, dashboard).
1) Manual scaling via Compose¶
- Scale workers up/down interactively:
- docker compose up -d --scale worker1=1 --scale worker2=1 --scale worker3=0
- If you use a single worker service name (recommended for scaling), refactor worker1/worker2/worker3 into one service
worker
and then: - docker compose up -d --scale worker=3
Notes:
- When using multiple distinct services (worker1, worker2, worker3), each has a unique NODE_ID and port mapping; scaling replicas per service is less convenient. If you want simple scaling, consider one worker
service with NODE_ID derived from hostname or container index.
2) Profiles & overrides for preset sizes¶
Create an override file (docker-compose.override.autoscale.yml) with increased replicas for workers. With Compose v2 on Swarm you can use deploy.replicas
; in plain Compose, replicas are controlled by --scale
.
Example override (document-only; not required to commit):
# docker-compose.override.autoscale.yml
services:
worker1:
# No deploy.replicas in classic Compose; use --scale
# Keep environment identical, scale with CLI.
worker2:
worker3:
Usage: - docker compose -f docker-compose.yml -f docker-compose.override.autoscale.yml up -d - docker compose up -d --scale worker1=1 --scale worker2=2 --scale worker3=1
3) Metrics-informed scaling with a small helper script¶
You can run a simple loop that watches Prometheus for hub_borrow_queue_length and adjusts replicas using docker compose up --scale
.
Example script (save as scripts/compose-scaler.sh):
#!/usr/bin/env bash
set -euo pipefail
PROM=${PROMETHEUS_URL:-http://127.0.0.1:9090}
SERVICE=${SERVICE_NAME:-worker2}
MIN=${MIN_REPLICAS:-1}
MAX=${MAX_REPLICAS:-5}
LABEL_MATCH=${LABEL_MATCH:-AppB:Chromium:UAT}
INTERVAL=${INTERVAL_SECONDS:-30}
# Query sums queue across labels matching exactly; change query as needed
QUERY="sum(hub_borrow_queue_length{label=\"$LABEL_MATCH\"})"
scale() {
local n=$1
n=$(( n < MIN ? MIN : n ))
n=$(( n > MAX ? MAX : n ))
echo "[scaler] Scaling $SERVICE to $n (queue=$2)"
docker compose up -d --scale "$SERVICE=$n" >/dev/null
}
current=$MIN
scale "$current" 0
while true; do
# Prometheus instant query
val=$(curl -fsSL "$PROM/api/v1/query?query=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$QUERY")" | jq -r '.data.result[0].value[1]' || echo 0)
# Basic policy: queue 0-> keep; 1..3 -> +1; >3 -> +2
q=${val%.*}
desired=$current
if (( q == 0 )); then
desired=$(( current > MIN ? current - 1 : current ))
elif (( q > 3 )); then
desired=$(( current + 2 ))
else
desired=$(( current + 1 ))
fi
if (( desired != current )); then
scale "$desired" "$q"
current=$desired
else
echo "[scaler] No change (queue=$q, replicas=$current)"
fi
sleep "$INTERVAL"
done
Usage: - Ensure Prometheus is up (default http://127.0.0.1:9090 per compose). Install curl, jq, python3 in your host. - export SERVICE_NAME=worker2; export LABEL_MATCH="AppB:Chromium:UAT"; export MIN_REPLICAS=1; export MAX_REPLICAS=4 - bash scripts/compose-scaler.sh
Notes:
- This is a simple local tool; no TLS/auth or advanced backoff. Adjust query and policy for your needs.
- It acts on a single service name. If you consolidate workers to one service (worker
) you’ll get better ergonomics.
4) Consolidate workers for easier scaling¶
Current compose defines worker1/worker2/worker3 with different ports and NODE_IDs. To leverage --scale
, consider switching to a single worker
service and using ephemeral published ports or reverse proxy for WS routing. For local dev, you can:
- Keep just worker2 as worker
and scale it: docker compose up -d --scale worker=3
- Set PUBLIC_WS_HOST to your host and omit explicit host port mappings by using an ingress or a small TCP router if needed.
Example (conceptual) single service snippet:
services:
worker:
build:
context: .
dockerfile: worker/Dockerfile
environment:
- HUB_URL=http://hub:5000
- REDIS_URL=redis:6379
- NODE_SECRET=node-secret
- POOL_CONFIG=AppB:Chromium:UAT=3
- PUBLIC_WS_HOST=127.0.0.1
- PUBLIC_WS_PORT=5200
# No fixed host port mapping; rely on docker network or a simple proxy
Then scale: - docker compose up -d --scale worker=3
Be mindful that each replica needs a unique NODE_ID. If omitted, the worker can default NODE_ID from container hostname (recommended enhancement); otherwise, pass NODE_ID via environment using Compose’s index: not natively supported, but you can use docker-compose variable expansion with service index in Swarm; for plain Compose, leave NODE_ID empty and let the app generate a unique ID.
5) CPU-based hints¶
Compose does not provide CPU-based autoscaling. You can still: - Set CPU limits/ reservations (deploy.resources) for documentation and portability (effective in Swarm; for plain Compose, they map to Docker constraints):
- Feed CPU metrics to your script from cAdvisor/Prometheus (node-exporter + cAdvisor) and combine with queue length similar to the example script.
TL;DR¶
- Use docker compose up --scale worker=N for manual scaling.
- Prefer a single worker service for ergonomic scaling.
- Optionally run a tiny scaler script that polls Prometheus hub_borrow_queue_length and adjusts replicas.
- For production-grade autoscaling, use Kubernetes/KEDA as documented in Autoscaling Hints (HPA).