HTTPS-PORTAL Is One Environment Variable, and Its Best Feature Is the One It Warns You Off
A fully automated HTTPS server powered by Nginx, Let's Encrypt and Docker.
At a glance
- What is it?
- A Docker image that puts Nginx and Let's Encrypt in front of an application you already run, where the whole proxy configuration is a single DOMAINS value, where the default certificate is a test one, and where automatic container discovery is strongly discouraged by the project itself.
- Who is it for?
- HTTPS-PORTAL fits a team running a Docker application that needs a public hostname and a real certificate without anyone touching Nginx configuration or a certificate renewal cron. It does not fit anyone who cannot expose ports 80 and 443 on a Linux host, and it does not fit someone who wants the zero-configuration container discovery behaviour, because the project tells you not to use it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 19 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- SteveLTN/https-portal4,685 ★ · -9
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| SteveLTN/https-portal | 4.7k | 4.7k |
-9 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The whole proxy configuration is one variable, and the arrow decides
The smallest example in the README is a compose file with an image, two port mappings, one volume and one environment entry:
environment:
DOMAINS: 'example.com'
# STAGE: 'production' # Don't use production until staging worksOnce you have an application behind it, the same variable carries a routing instruction. A value of the form domain to upstream host and port, written with an arrow, reverse-proxies that host, which is how the WordPress example points at a container by its Compose service name. A value written with a different arrow redirects instead, and all paths are redirected with a temporary 307 unless you set REDIRECT_CODE to 301 for a permanent one. The consequence is that the arrow character is load-bearing: `->` proxies and `=>` redirects. That makes the variable worth reading character by character rather than copying from an example.
The default certificate is a test one, and the comment says why
Two defaults in the compose examples are worth internalising before you point a real domain at this. The first is STAGE, which is staging by default and results in a test, untrusted certificate from the certificate authority. The production line is present in every example but commented out, with the instruction not to use production until staging works. The reasoning is sound, since Let's Encrypt's rate limits are real and a misconfigured production request can burn your allowance, so the conservative default protects you. The cost is that a deployment where nobody reads the environment block serves a browser warning, and the natural first reaction is that the tool is broken. The second default is about storage. The example mounts a named volume at the data directory and marks it recommended, with the stated reason that it avoids re-signing when you upgrade the image. Without it, certificate state does not survive an upgrade, so you pay for issuance again.
Container discovery is the feature the project tells you not to use
Among the documented capabilities is automatic container discovery, and it carries the loudest warning in the file. The README states in capitals that it strongly recommends against using this feature unless absolutely necessary, and gives the reason: exposing the Docker socket to a container, even mounted read-only, effectively grants control over the host. Read that as the author describing his own feature accurately. Automatic discovery works by letting the proxy container inspect the Docker daemon to find other containers and their ports, which is the only way it can learn about a container you never named. The cost is that the socket controlling every container on the machine, including root-level actions, is now reachable from a process that is parsing network traffic. For a single-host deployment where one application needs one hostname, naming the upstream in the environment variable costs one line and removes the entire exposure. The warning is the reason to believe the rest of the documentation.
Testing locally needs a DNS trick, and the container listens to one name
There is a local mode, and it is the part most likely to waste an afternoon if you read it quickly. Setting the stage to local makes the container create a self-signed certificate, which the README is clear your browser will not trust, and the purpose is to let you verify that the compose file and your application stack work together before touching a real domain. The catch is that the container only listens to the domain you wrote in the compose file, so your browser will not reach it until that name resolves to your machine. Three approaches are offered. Modify your hosts file so the name resolves to the loopback address, or to any address pointing at your Docker host. Or run DNSMasq on your computer or router, which the README says provides more flexibility. Or use a wildcard DNS service instead of a placeholder name, writing the subdomain of that service in the DOMAINS variable so it resolves to the loopback address automatically. Pick the last one and local testing becomes a one-line change rather than a hosts file edit.
The image is Nginx, a supervisor, a template generator and an ACME client
The Dockerfile shows how much is inside the image, and the pinned versions are in it as plain environment variables. The base is an Nginx image whose version is a build argument, the Nginx access and error logs are deleted so logrotate can take over, and a set of packages is installed including Python, Ruby, cron, logrotate and inotify tools. Three components are then downloaded at fixed versions: an overlay-based init system, a template generator for the proxy configuration, and a small ACME client that performs the certificate issuance. Two details stand out. The init system is configured to bring the container down if its second startup stage fails, which is the behaviour you want for a proxy whose job is to be reachable. And a helper script copied in early is what makes the multi-platform build work, since the architecture-specific archives for the supervisor and the generator are selected by running that script at build time.
Four architectures are published, and one is excluded with a reason
The build is a multi-platform image assembled with the buildx builder and a set of emulated architectures. The platform list in the Makefile names four targets, thirty-two bit x86, sixty-four bit x86, thirty-two bit ARM and sixty-four bit ARM. Just above that list sits a comment explaining what is missing and why: the image is not built for thirty-two bit ARM version five, because the build fails while loading the Debian package tools it needs. That is the kind of note that saves an afternoon, and it also tells you the image is built on a Debian-derived base rather than a minimal one, since it depends on the packaging tools being present. The tagging logic is worth a glance too. The version tags are derived from an exact tag match in the repository history, and for a three-part version the build emits a full tag, a two-part tag and a one-part tag, plus latest, while anything that is not a clean version emits no tags at all. An untagged checkout therefore produces an image you have to refer to by digest.
The translation is credited and disclaimed in the same paragraph
One small detail says more about the project than most changelogs do. A Japanese version of the README exists and is credited to a named contributor, and immediately after the credit the author says that because he cannot understand Japanese he cannot guarantee the Japanese version is up to date. That is an unusual thing to publish about your own documentation, and it is the correct thing to publish, because a translation nobody maintains is a translation that quietly diverges. If you are reading the translated file, treat it as a pointer to the English original rather than as an equivalent, and check anything you depend on, especially the environment variable names, since those are what your compose file will contain. The repository root shows a consistent preference for keeping configuration next to the code, with example directories for a minimal setup, a WordPress setup and a custom configuration.
Editorial conclusion
HTTPS-PORTAL fits a team running a Docker application that needs a public hostname and a real certificate without anyone touching Nginx configuration or a certificate renewal cron. It does not fit anyone who cannot expose ports 80 and 443 on a Linux host, and it does not fit someone who wants the zero-configuration container discovery behaviour, because the project tells you not to use it. Before you deploy, verify three things: that STAGE is set deliberately, since staging is the default and produces an untrusted certificate, that your domain already resolves to the host, and that you mounted the named data volume, since the README recommends it specifically to avoid re-signing certificates when you upgrade the image.
Frequently asked questions
What does HTTPS-PORTAL actually do?
It is a Docker image that runs an Nginx reverse proxy in front of an application you already have, obtaining and renewing certificates from Let's Encrypt automatically. You run any existing web application over HTTPS with one extra line of configuration, declaring your domain and, when there is an upstream container, its address in the DOMAINS environment variable.
How do I test HTTPS-PORTAL locally before using real certificates?
Set STAGE to local, which makes the container generate a self-signed certificate your browser will not trust but which is enough to verify the compose file and your application stack. The container only listens to the domain you declared, so that name must resolve to your machine: add it to your hosts file pointing at the loopback address, run DNSMasq on your computer or router, or use a wildcard DNS subdomain in the DOMAINS variable instead of a placeholder name.
Why does my HTTPS-PORTAL site show an untrusted certificate warning?
Because STAGE defaults to staging, which results in a test certificate from Let's Encrypt that browsers do not trust. The production setting is present but commented out in every example, with a note not to use it until staging works, because of Let's Encrypt rate limits. When you do switch, mount the named data volume at the data directory, which the README recommends to avoid re-signing when you upgrade.
Should I use the automatic container discovery feature?
The README strongly recommends against it unless absolutely necessary, because exposing the Docker socket to a container, even mounted read-only, effectively grants control over the host. For a single application needing a single hostname, declaring the upstream in the DOMAINS variable costs one line of configuration and avoids that exposure entirely.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/steveltn-https-portal)Star history chart for your README
[](https://hysenlabs.com/projects/steveltn-https-portal)Was this useful?