Yeah, I’m pretty sure dynamic upstream is the way to go to get load balancing.
The way you’ve configured it is pretty spot-on. Personally maybe I’d leave the refresh a little longer just to reduce the total amount of DNS talk, and passive health checks to ensure Caddy can detect and avoid an upstream that has been removed since the last DNS check until the next one takes place. But that’s just preference, I see no reason the way you proposed wouldn’t work perfectly fine.
The alternative if you use lucaslorentz/caddy-docker-proxy is to take advantage of the templated {{upstreams}}
which will dynamically generate and reload Caddy’s configured upstreams in essentially real time based off information from the Docker socket.