Container Ports
Which ports the OSS Docker image binds inside the container, and how to override them.
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
- Point DNS at the host.
- Set
TLS_DOMAIN(for examplepwpush.example.com). - 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.
Behind your own reverse proxy (recommended for many LAN / NPM / Traefik setups)
- Unset
TLS_DOMAIN(terminate TLS at Nginx, Caddy, Traefik, etc.). - Point the proxy upstream at container port 5100 (the application), or at the built-in proxy’s
HTTP_PORTif you prefer that edge. - 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 |