Working on Multiple Web Projects with Docker Compose and Traefik
georgek.github.io
georgek.github.io
> What if that compose.yaml file is checked in as part of the project? Does the whole team have to agree on a set of port numbers to use for each project?
That's only if you choose to use hard coded values. You can use environment variables instead.
You can change `- "8000:80"` to `- "${DOCKER_WEB_PORT_FORWARD:-127.0.0.1:8000}:${PORT:-80}"` and now any developer can customize the forwarded port however they see fit in a git ignored `.env` file. This is what I've done in all of my example Docker web apps at: https://github.com/nickjj?tab=repositories&q=docker-*-exampl...
No Traefik or override file is needed, at least not for allowing a user to customize the forwarded port.
I like the override file and used it for years but I stopped using it entirely about 6 months ago. It's too much of a headache to commit a `docker-compose.override.yml.example` file to version control and then have folks copy that to a git ignored `docker-compose.override.yml` file. You end up with serious config drift, especially if you have a team with a few developers. It's been a source of so many "oh yeah, I forgot to update my real file" type of issues.
Between environment variables and Docker Compose profiles[0] you can have a single committed `docker-compose.yml` file that is usable in all environments for all users.
[0]: https://nickjanetakis.com/blog/docker-tip-94-docker-compose-...
I did also consider using environment variables instead of the override file. It's not a bad idea and perhaps gets around some of the limitations with !reset.
I still think using traefik to get names instead of a load of different port numbers is cool, though, especially if you want to share these links with others on your network. For that I think you would need the override file, or you could just commit the labels to the main file as they wouldn't cause any harm, but then you've you a load of noise that people might not care about (and what if they have their own label based tool of choice?)
Thanks for the link to your work, it looks very useful.
If you go to a server you get https for free, no extra config.
It combines caddy with docker-compose labels, making it super easy to spin up new projects that can immediately be exposed.
Most of the time, I just have to set the port from the container and a host, the rest gets expanded, but I have macros to add the tedious middleware lines
Also, does local host subdomain resolution work on all OSes OOB now? Iirc this was an apple exclusive feature in the past.
Works fine on current ubuntu, both with systemd stub-nameserver and even if I replace it with a real one in /etc/resolv.conf.
Edit: indeed, it says this:
> You warrant to ISRG and the public-at-large that You have taken, and You agree that at all times You will take, all appropriate, reasonable, and necessary steps to assure control of, secure, properly protect, and keep secret and confidential the Private Key corresponding to the Public Key in Your Certificate (and any associated activation data or device, e.g. password or token).
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-20... via https://letsencrypt.org/repository/
So it's probably only a problem for people who use traefik.me, they might be tricked into thinking they're visiting their own locally hosted site, and instead might be man-in-the-middled. Though that's a fairly specific attack coming from an APT or as a means to escalation from someone who already gained access through other means.
I do not use docker compose because each server is in active development so I cannot compile it into an image and I have to spin them all up manually. But I will use docker compose after i am done for front-end developers so they can keep developing the UI locally.
I’m curious to know, are you actively developing all 5+2 codebases, or could you theoretically run half of them in docker and another half (the ones you’re actively changing) on the host?
That's something I've seen many times said by many people but I don't understand. Your code can be mounted from the host into the container. There's no need to rebuild images with each change. Inside such "development" container some form of watcher/auto-rebuild can run and recompile & restart your program after changes are made in code. Isn't it standard practice?
What about isolation? Of runtime, ports, dependencies?
I think a lot of people here can't imagine how this is possible. Docker is literally just the same thing as what's running on the server except in a chroot environment. And it gives you a ton of benefits like isolation, portability, infrastructure as code, a layer of security, etc.
Cloudflare manages my domain and it allows Traefik to get letsencrypt certificates even for internal services not exposed to the outside world.
I also have multiple Traefik entrypoints for internal and external services. And cloudflared tunnel container set up to manage access to the public resources.
Then on the home router level I set/override DNS entries for internal services so they would connect directly to Traefik, instead of going through Cloudflare.
Incredibly these Cloudflare services cost exactly 0$ for now.
But I do not use compose overrides, don't really see the benefits.
See also this interaction I had with Cloudlfare's CEO a couple years ago on this topic: https://news.ycombinator.com/item?id=30285554
But the first thing that struck me was "ok, cool auto-discovery", and then "wait, it needs access to all my containers?".
Maybe I'm from the old school but we used to separate services into their own service users so that if one service falls it can't take others with it as easy.
Now we just accept that all services are under the same user because we use containerization. Well I'm still separating them so I really can't take advantage of the amazing auto-discovery.
This way it would allow the use of Traefik and similar services which depend on reading/writing labels while severely minimizing attack surface.
EDIT: Found this on Github https://github.com/Tecnativa/docker-socket-proxy
But something has to be exposed to get these certs to the internal services?
However, even with my tweaks, the overall solution is still limited. Because it's not on "localhost", the browser considers it an "insecure context" unless you also set up local HTTPS.
[0] https://github.com/CGamesPlay/dotfiles/blob/13659d19ca899cea...
In theory traefik.me can be set up with HTTPS but I haven't tried it yet.
Letsencrypt certificates, reverse proxy and all this stuff. Took a bit, but at the end of the day, I'd only have to specify "testing.example.org" in the docker-compose, docker-compose up and everything would be routed correctly. I could check out a different branch and bring up "staging.example.org" within 10 seconds. Sadly, it only worked at home, where this real domain would be hooked up to my private router.
Then, however, it was 10 times better than port routing, localhost development certificates and all the other stuff you need to have just to test something that uses the camera on the mobile device.
An easier way is to make the port range dynamic by adding a prefix variable in .env/example.env. So, once configured, the whole localdev binds to ports in the prefix range, eg: 342xx.
Experience shows that localdevs will need that env file anyway and adding this config step to the readme is quite effective.
```
ports:
- "${PORT_PREFIX}01:80"
```
This means that devs can drive the port range that the project bind to by editing their .env file.
However I'd suggest at least specifying a default value so developers don't need to mess with version-controlled .env files to customize their local setups.
That's a good point. Most of my projects tend to ship an `example.env` which documents each env var and introduces a sane default.
When introducing the principle to projects some times a dev will complain about config drift and the like but experience in practice shows that these files tend to change a bit when introduced and then rarely at all so it really is not that much of a deal.
It just so happens that I wrote a gist recently that explains how to do this.
https://gist.github.com/Ravenstine/707180ef29e9d37a8f816e019...
I'm using Dnsmasq (https://thekelleys.org.uk/dnsmasq/doc.html) to map anything at .lo to the currently running project, like so:
brew install dnsmasq
sh -c 'echo "address=/.lo/127.0.0.1\naddress=/.lo/::1\n" > /usr/local/etc/dnsmasq.conf'
sudo mkdir -p /etc/resolver
sudo sh -c 'echo "nameserver 127.0.0.1\n" > /etc/resolver/lo'
sudo brew services start dnsmasq
Would love to expand on that to route to specific projects, but since it's working "well enough" I probably won't touch that for the foreseeable future.The main thing I want to improve is to not use one big compose file for all services, as it would be cleaner to have one per service and just deploy them to the same network. But I haven't figured the best way to auto-deploy each service's compose file to the server (as the current auto-deploy only updates container images).
location ~ ^/([a-z0-9_-]+)/ {
proxy_pass http://internal-$1:8000;
}
We pickup the service name from the URL and use it to select where to proxy_pass to. So /service1 would route to the docker container named internal-service1 . We can reach it via the name only as long as Nginx is also running in Docker and on the same network.Use a utility container to learn some of the network/options of docker. The network-multitool[1] container is a good one I use.
So for example, create a few containers in a manifests/compose file with different network settings and try pinging, telneting, etc from one container to another.
This container can also be used to help debug workload containers that are having connectivity issues. If you need more tooling just create an image with this container as a base.
And then even plain nginx under that to proxy to non docker services...
(And ipv6 for really short urls. example.com.--1.sslip.io etc)
Instead of localdev, i'd just spin up a DO server and develop things there. It's not great -- it's not as fast as my macs, have to have 2 sets of local dev env. But once i get things working in DO it serves as my staging/prod env.
For some reason the idea got new
Still remember https://lando.dev/ ?One source: https://www.powermapper.com/products/sortsite/rules/accwcag2...
Is it flexible enough to handle any http app? Does is support websockets, or maybe arbitrary tcp connections (such as jdbc postgres or activemq broker)?