Self-hosted service
SteveLTN/https-portal avatar
SteveLTN/https-portal

SteveLTN/https-portal: an automated HTTPS front end for Docker apps

A fully automated HTTPS server powered by Nginx, Let's Encrypt and Docker.

4,695 stars297 forksRubyMIT

At a glance

What is it?
https-portal wraps Nginx, Let's Encrypt and Docker into one image that terminates TLS for the containers behind it. The setup is short, the certificate lifecycle is automatic, and the trade-offs are mostly about exposure and rate limits.
Who is it for?
Adopt https-portal when you already run Docker on a Linux host with ports 80 and 443 free and you want certificate issuance and renewal handled for you. Skip it if you cannot give the container those two ports, if you need a proxy that is configured through a versioned file rather than environment variables, or if you are unwilling to mount the Docker socket for automatic container discovery.
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 127 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 September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem https-portal removes from a Docker deployment

Terminating TLS in front of a container usually means three separate jobs: writing an Nginx server block, obtaining a certificate from Let's Encrypt, and remembering to renew it before it expires. https-portal compresses that into one image. The README describes it as a fully automated HTTPS server powered by Nginx, Let's Encrypt and Docker, and the claim it makes is specific: you can run an existing web application over HTTPS with one extra line of configuration.

That line is the DOMAINS environment variable. For a single site it is just the hostname. For a proxied application it is a hostname followed by an arrow and the upstream address, for example wordpress.example.com -> http://wordpress:80. The intended reader is someone who already has a Docker host and a domain, and who would rather not maintain certificate renewal by hand. Knowledge of Docker is described as helpful but not required, because the documented examples are Docker Compose files you copy and edit.

What actually runs inside the image

The Dockerfile starts from an nginx base image (the ARG defaults to nginx:1.30.2) and installs Ruby, Python 3, cron, logrotate, inotify-tools and apache2-utils on top. Process supervision comes from s6-overlay, pinned to v3.2.3.0, and the image sets S6_BEHAVIOUR_IF_STAGE2_FAILS=2 so the container comes down if a startup stage fails rather than serving a half-configured proxy.

Two more components are fetched at build time. docker-gen, at version 0.16.3, is what watches Docker and regenerates Nginx configuration; acme-tiny, at version 5.0.3, is the small ACME client used to talk to Let's Encrypt. Nginx's default site config and the system crontab are deleted during the build, which is consistent with the project generating its own server blocks and running renewal on its own schedule.

The data flow is therefore: the container reads DOMAINS from the environment, acme-tiny obtains or renews the certificate for those names, docker-gen writes the Nginx configuration for the upstreams you declared, and Nginx serves 80 and 443. Certificates and generated state live under /var/lib/https-portal, which is why the README mounts a named volume there.

Installing it and serving a first domain

The prerequisite is a Linux host, local or remote, with ports 80 and 443 available and exposed, Docker Engine installed, and every domain you plan to use already resolving to that host. Docker Compose is recommended rather than required, and the documented examples are in Compose format.

Create a docker-compose.yml in any directory. This is the minimal example from the README, with the staging stage left at its default:

yaml
version: '3'

services:
  https-portal:
    image: steveltn/https-portal:1
    ports:
      - '80:80'
      - '443:443'
    environment:
      DOMAINS: 'example.com'
      # STAGE: 'production' # Don't use production until staging works
    volumes:
      - https-portal-data:/var/lib/https-portal

volumes:
    https-portal-data:

Run docker-compose up in that directory. A moment later, according to the README, you get a welcome page on https://example.com. The certificate at this point is a test certificate from Let's Encrypt, because STAGE defaults to staging. That default exists for a reason: the README warns not to use production until staging works, and it has a separate section on Let's Encrypt rate limits.

The more realistic example puts an application behind the proxy. Only the environment variables under the https-portal service are specific to this project:

yaml
version: '3'

https-portal:
  image: steveltn/https-portal:1
  ports:
    - '80:80'
    - '443:443'
  restart: always
  environment:
    DOMAINS: 'wordpress.example.com -> http://wordpress:80'
    STAGE: 'production'
  volumes:
    - https-portal-data:/var/lib/https-portal

wordpress:
  image: wordpress

Run docker-compose up -d to start it in the background. The upstream hostname is the service name of the application container on the same Compose network. For local testing before any of this touches a real domain, set STAGE: local and https-portal issues a self-signed certificate instead; the README notes your browser will not trust it, and that you need either a hosts entry, DNSMasq, or a DOMAINS value such as mysite.lvh.me, a wildcard DNS entry that resolves any second level name to 127.0.0.1.

Redirections, custom ports and the automatic discovery warning

Two operators distinguish the ways a domain entry is interpreted. The normal arrow, ->, proxies to an upstream. The double arrow, =>, sets up a redirect to a target URL, and the README states that all paths are carried over: https://example.com/foo/bar is 307 redirected to https://target.example.com/foo/bar. The status code is configurable through REDIRECT_CODE, which the README shows set to 301 for a permanent redirect. The canonical use case is pointing www.example.com at example.com.

The feature list also covers multiple domains, custom ports, multiple upstreams, serving static sites, sharing certificates with other applications, HTTP Basic Auth, access restriction, logging configuration and internationalized domain names. There is also an advanced path: Nginx can be configured through environment variables, changed dynamically, or overridden entirely by supplying your own configuration files.

The one feature the README actively discourages is automatic container discovery. It carries a warning in capitals recommending strongly against using it unless absolutely necessary, on the grounds that exposing the Docker socket to a container is risky even when mounted read-only. Read that as the maintainer telling you the convenience is not worth the blast radius for a typical deployment.

Where https-portal is the wrong tool

The prerequisite list is also the constraint list, and it is not negotiable. The host must have ports 80 and 443 free. If something else already owns those ports, or if you are deploying on a platform that assigns a single port and routes to it, https-portal does not fit, because it is built to answer HTTP and HTTPS challenges directly.

Every domain in DOMAINS must resolve to the host. That is stated as a prerequisite, and it follows from how Let's Encrypt validates ownership. A domain that points elsewhere will fail issuance, and the failure surfaces at container start rather than at configuration time, which is why the image is built to exit when stage 2 fails.

Certificate issuance is also subject to Let's Encrypt rate limits, which the README treats as a distinct topic with its own section. Repeatedly destroying the container without the /var/lib/https-portal volume mounted means re-requesting certificates each time, and the README calls the volume recommended specifically to avoid re-signing when upgrading https-portal. If you cannot persist that volume, this is the wrong design for you.

Finally, configuration through environment variables is convenient and hard to review. There is no single declarative file to diff in a pull request the way there is with a hand-written Nginx config, and the advanced override path exists precisely because the environment variable surface does not cover everything.

How it differs from nginx-proxy and Caddy

The closest comparison is nginx-proxy with its letsencrypt companion. Both generate Nginx configuration from Docker metadata, and https-portal's Dockerfile actually pulls in docker-gen, the same generator nginx-proxy is built around. The difference is packaging and default posture. nginx-proxy expects you to label each application container so it can be discovered; https-portal expects you to declare the mapping yourself in DOMAINS, and it ships the ACME client and renewal scheduling inside the same image. That makes https-portal a smaller number of moving parts for a fixed set of sites, and a worse fit when the set of upstreams changes constantly, which is the case automatic discovery was built for.

Caddy takes the other approach: TLS is handled by the server itself, configured from a Caddyfile, with no separate ACME client and no cron job. Caddy is a single binary you can run outside Docker; https-portal is a Docker image whose reason to exist is the Compose workflow. If your deployment is not containerized, https-portal is not the tool, and the README says as much by listing Docker Engine as a prerequisite.

Maintenance, licensing and upgrade cost

The repository is not archived, and the last push was on 2026-05-26. Releases are frequent and closely spaced: 1.25.5 on 2026-05-27, 1.25.4 on 2026-05-23, 1.25.3 on 2026-05-20. The image is published under the steveltn namespace on Docker Hub, and the README's examples pin the major version tag, steveltn/https-portal:1, rather than a patch release, so an upgrade is a pull and a restart.

The upgrade cost is mostly about certificates. Because the named volume at /var/lib/https-portal holds the issued certificates, the README recommends mounting it so that upgrading does not trigger re-signing. Skipping it means new certificates on every container replacement, which is exactly the pattern Let's Encrypt rate limits punish. The Makefile shows the project builds multi-arch images for linux/386, linux/amd64, linux/arm/v7 and linux/arm64/v8, so the same tag works across those architectures.

On licensing: the repository carries the MIT license, and the image assembles third-party components including Nginx, s6-overlay, docker-gen and acme-tiny, each under its own terms. That is a description of what is in the repository, not legal advice; if you redistribute the image or a derivative, check the licences of those bundled components yourself.

Editorial conclusion

Adopt https-portal when you already run Docker on a Linux host with ports 80 and 443 free and you want certificate issuance and renewal handled for you. Skip it if you cannot give the container those two ports, if you need a proxy that is configured through a versioned file rather than environment variables, or if you are unwilling to mount the Docker socket for automatic container discovery. Before you deploy, confirm that every domain in DOMAINS resolves to the host, start with STAGE left at its default staging value, and mount the https-portal-data volume so certificates survive an upgrade.

Frequently asked questions

What is https-portal used for?

It is a Docker image that runs Nginx and obtains Let's Encrypt certificates so an existing web application can be served over HTTPS. The README frames it as adding HTTPS to an application with one extra line of configuration, the DOMAINS environment variable.

Does https-portal give my site a trusted certificate by default?

No. STAGE defaults to staging, which the README says results in a test certificate from Let's Encrypt that is not trusted. You set STAGE: 'production' once staging works, and STAGE: local produces a self-signed certificate for testing.

Which ports does https-portal need?

The prerequisite is a Linux host with ports 80 and 443 available and exposed, and the Compose examples publish both. Every domain listed in DOMAINS must also resolve to that host.

How do I avoid re-requesting certificates when I upgrade https-portal?

Mount a volume at /var/lib/https-portal. The README marks the https-portal-data volume as recommended specifically to avoid re-signing when upgrading, and the image also has a section on Let's Encrypt rate limits.

Should I enable automatic container discovery in https-portal?

The README strongly recommends against it unless absolutely necessary, because it requires exposing the Docker socket to the container, which it calls risky even with a read-only mount.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. SteveLTN/https-portal on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/steveltn-https-portal.svg)](https://hysenlabs.com/projects/steveltn-https-portal)