Real life use case for Docker networks
admin, June 21, 2024, 8:45 a.m.Virtual networks are almost as omnipresent in Docker projects as Docker volumes. Especially in Compose they're a magic sause that makes developing and hosting services so easy when using Docker. Yet I've struggled to find examples of advanced use of Docker networks -- and so I decided to share an example of mine.
If you just came here for the sauce, feel free to just browse to the code.
I rarely host a single web service on one host, and so often need a reverse proxy to access these services. Even when there only is one service, the app might not have built in support for SSL certificates, thus necessitating a reverse proxy. Sometimes these proxy set ups might become confusing, especially if one doesn't revisit them often. Docker's networking stack makes it easy to not also let multiple containers talk to each other, but even multiple container stacks. Then it's a breeze to expand the host with new services, all behind the same reverse proxy.
An example running on my home lab is my observability stack. It consists of Grafana and Prometheus; but only Grafana needs HTTP access. I also have a custom httpd container in a separate stack. These are tied together with a virtual network calledmillim-proxy
in the following way:
- The network is defined in the proxy stack.
- It is referenced in the observability stack as
external
network. - Grafana container gets a memorable host name.
- The Apache config refers to
http://grafana
instead ofhttp://localhost
.
Trade offs
Nothing has only upsides and no downsides, but I believe this kind of reverse proxy setup is more maintainable. The only real downside I see is somewhat increased complexity of the setup.
The benefits, though, are a few. Fewer ports are exposed on localhost, increasing security. With a misconfigured firewall a hacker can bypass the reverse proxy, and access a service that only communicates over insecure HTTP. Network port configuration gets simpler, since the port number is not configured in docker-compose.yml
. The code gets easier to read and maintain with host names instead of localhost
everywhere. Although it just sounds like syntactic sugar, for me it's important that my code is easy to read, especially if I don't look at it often.
Code sample
Here's how the real code looks:
services:
prometheus:
container_name: prometheus
image: prom/prometheus
dns: 100.100.100.100
networks:
- default
volumes:
- "/srv/prometheus:/etc/prometheus"
- "data:/prometheus"
grafana:
container_name: grafana
image: grafana/grafana:latest
dns: 100.100.100.100
restart: always
networks:
- millim-proxy
- default
environment:
- GF_DEFAULT_INSTANCE_NAME="grafana.ts.millim.no"
volumes:
- "grafana-data:/var/lib/grafana"
- "/srv/grafana:/etc/grafana"
volumes:
data:
grafana-data:
networks:
default:
millim-proxy:
external: true
<VirtualHost *:443>
ServerName grafana.ts.millim.no
SSLEngine On
SSLCertificateFile /srv/certs/cert.pem
SSLCertificateKeyFile /srv/certs/privkey.pem
SSLCertificateChainFile /srv/certs/fullchain.pem
ProxyPreserveHost On
ProxyPass "/api/live/ws" "wss://grafana:3000/api/live/ws"
ProxyPassReverse "/api/live/ws" "wss://grafana:3000/api/live/ws"
ProxyPass / http://grafana:3000/
ProxyPassReverse / http://grafana:3000/
</VirtualHost>
What do you think?
I'd like to hear your thoughts on this post! Please hit me up on either LinkedIn or Mastodon.