Adding Traefik, custom domains, and automatic TLS with Let’s Encrypt
This was a pretty big step for Uploy…
Across the Traefik commit, the TLS one, and the bootstrap one, I added Traefik as the reverse proxy and made TLS provision automatically through Let’s Encrypt. That means users can now start attaching their own domains to applications.
What made this work interesting is that it was much more than just storing domains in the database. Behind the scenes, the whole flow depends on ACME certificate issuance, which means Traefik has to act as the ACME client and communicate properly with Let’s Encrypt. So this feature was not only about domain management… it was also about making certificate provisioning actually work end to end.
One small but important detail here is that I used the HTTP-01 challenge for ACME. Because of that, port 80 has to be reachable on the target VM. Without that, Let’s Encrypt cannot verify domain ownership, and certificate issuance will fail.
That requirement is one of the reasons I added a one-time bootstrap shell script in the third commit. The idea is that users should run this script the first time they prepare a VM for Uploy. It handles setup work that I do not want users to keep doing manually, like installing Docker and Docker Compose depending on the Linux distro, and adding the user to the docker group so Docker commands can run without extra friction later.
I also ran into a pretty sneaky edge case while trying to make domains work reliably. Inside the container, the resolver was 127.0.0.11, which is Docker’s internal DNS. That resolver then forwarded the query to 127.0.0.53, which belongs to systemd-resolved on the host. So the request was basically bouncing through a loopback chain. Because of that, DNS queries from inside the container failed, the domain could not be resolved properly, and ACME certificate issuance never completed.
That was a useful reminder that infra features like this often fail because of small environment details, not just application logic. On the surface, “add custom domain” sounds simple… but once TLS, ACME, bootstrap requirements, and DNS behavior enter the picture, the implementation becomes much more involved.
Getting this working felt like a major milestone. Being able to point a real domain to an app and have TLS provision automatically makes the project feel much closer to an actual PaaS product.
I also recorded a longer demo for this build log (~11 minutes). It ended up being longer than my usual videos because this topic needed more explanation than usual. It was also the first time I recorded my face for a Uploy build log, which felt a bit different… but for this kind of infrastructure-heavy feature, I think the extra context helps.
Here’s the demo video…