Solid compose pattern: scale the Node replicas and put nginx in front as the single published listener. The ops detail that usually bites after docker compose up --scale is that a static upstream block only learns peers at reload — new container IPs from a recreate stay invisible until nginx reloads or you use a resolver-aware variable upstream. Keep app ports on the internal network only; expose 80/443 on the proxy.
For the load-balancer side I prefer least_conn once replicas are roughly equal, with modest max_fails/fail_timeout so a crashed task leaves the pool instead of 502-ing every other request. Set proxy_http_version 1.1, clear Connection for keepalive, and pass Host plus X-Forwarded-For/Proto so the API builds redirects correctly. After a scale event I hit each replica by service DNS from inside the nginx container, then through the public vhost, and compare status and latency — mismatches almost always mean a stale upstream list or a trailing-slash join, not the image tag.