Container Ports

Which ports the OSS Docker image binds inside the container, and how to override them.

This article applies to: OSS Self-Hosted

Pro Self-Hosted? Ports differ. Pro’s public HTTP default is 80 (not 5100). See Pro Container Ports.

The OSS Docker image (pglombardo/pwpush) puts a built-in HTTP proxy in front of the application. That proxy provides HTTP/2, optional Let’s Encrypt TLS, asset caching, and compression. Knowing which layer binds which port avoids upgrade surprises (for example after moving from older images that only exposed 5100).


Default ports inside the container

Port Layer Role
80 Built-in HTTP proxy Public HTTP listener (HTTP_PORT)
443 Built-in HTTP proxy Public HTTPS listener (HTTPS_PORT) when TLS_DOMAIN is set
5100 Application App behind the proxy (TARGET_PORT)

The image declares EXPOSE 80 443 5100. Compose healthchecks probe http://localhost:5100/up (the application directly).

Client / reverse proxy
        │
        ▼
   Built-in HTTP proxy (:80 / :443)  ← public edge (override with HTTP_PORT / HTTPS_PORT)
        │
        ▼
   Application (:5100)               ← TARGET_PORT (set by the container entrypoint)

Important: Compose mappings such as "5100:5100" or "8095:80" only change how the host reaches the container. They do not stop the built-in proxy from binding :80 inside the container unless you set HTTP_PORT.


Environment variables

These control the built-in HTTP proxy and how it reaches the app (they are not PWP__… settings). You can optionally prefix any of them with THRUSTER_ (for example THRUSTER_HTTP_PORT); the prefixed form wins if both are set.

Variable Description Default in OSS image
TLS_DOMAIN Domain(s) for automatic Let’s Encrypt certificates. Comma-separated for multiple names. If unset, the proxy serves HTTP only (no cert provisioning). Unset
HTTP_PORT Port the built-in proxy listens on for HTTP 80
HTTPS_PORT Port the built-in proxy listens on for HTTPS (used when TLS_DOMAIN is set) 443
TARGET_PORT Port the application listens on; the proxy forwards to it 5100 (entrypoint; or value of PORT if you set that instead)
PORT If set before start, the entrypoint copies it to TARGET_PORT Unset → TARGET_PORT=5100
FORWARD_HEADERS Whether the built-in proxy forwards inbound X-Forwarded-* to the application On when TLS_DOMAIN is unset; off when TLS_DOMAIN is set. See Proxies.

Application settings such as PWP__HOST_DOMAIN and PWP__TRUSTED_PROXIES are separate; see Configuration and Proxies.


Common setups

Automatic TLS in the container

  1. Point DNS at the host.
  2. Set TLS_DOMAIN (for example pwpush.example.com).
  3. Publish 80 and 443 to the host so certificate issuance and HTTPS work.
environment:
  TLS_DOMAIN: "pwpush.example.com"
ports:
  - "80:80"
  - "443:443"

Browse to https://pwpush.example.com. See Installation.

  1. Unset TLS_DOMAIN (terminate TLS at Nginx, Caddy, Traefik, etc.).
  2. Point the proxy upstream at container port 5100 (the application), or at the built-in proxy’s HTTP_PORT if you prefer that edge.
  3. Set X-Forwarded-* headers — details in Proxies & Password Pusher.
# TLS_DOMAIN unset
ports:
  - "5100:5100"   # or omit publish and use a Docker network + proxy_pass http://pwpush:5100

Change the public HTTP port (host networking, rootless, or bind denied)

The built-in proxy still defaults to :80. That fails when the process cannot bind privileged ports—common with network_mode: host, rootless Podman, or locked-down runtimes:

Failed to start HTTP listener","error":"listen tcp :80: bind: permission denied"

Set an unprivileged HTTP_PORT and align publish/healthcheck if you remap:

services:
  pwpush:
    image: pglombardo/pwpush:latest
    environment:
      HTTP_PORT: "5100"
      # TLS_DOMAIN unset when TLS is terminated elsewhere
    # Example with bridge networking:
    ports:
      - "5100:5100"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5100/up"]

With network_mode: host, there is no ports: remap—clients use the host IP and whatever HTTP_PORT / TARGET_PORT you chose. Setting HTTP_PORT=5100 (and leaving TARGET_PORT at 5100) makes the public HTTP port match docs and examples that mention 5100.

Alternative: keep container port 80

If your runtime allows it:

sysctls:
  - net.ipv4.ip_unprivileged_port_start=0
# or
cap_add:
  - NET_BIND_SERVICE

On some rootless hosts those options are blocked; use HTTP_PORT instead.


Quick reference

Goal What to set / publish
Let’s Encrypt inside the container TLS_DOMAIN + host ports 80 and 443
HTTP only / external TLS Unset TLS_DOMAIN; proxy or publish 5100 (or your HTTP_PORT)
Avoid privileged bind on :80 HTTP_PORT ≥ 1024 (often 5100)
Change the application’s internal port TARGET_PORT or PORT (entrypoint → TARGET_PORT)
Wrong URLs / audit IPs behind a proxy Proxies (FORWARD_HEADERS, PWP__TRUSTED_PROXIES, …)

Related

Topic Doc
Install & Compose quick start Installation
Reverse proxy headers & trusted proxies Proxies
App / PWP__ settings Configuration
Kubernetes (Service on 5100) Deploy OSS on Kubernetes
Pro container ports Pro Container Ports