Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Portwyrm

Portwyrm is a self-hosted reverse proxy control plane with a built-in web UI, designed to replace Nginx Proxy Manager while remaining compatible with npmctl's current API contract.

The 1.0.0 MVP target is literal p100 coverage of the frozen compatibility envelope. Early alpha, beta, and release-candidate versions are milestones toward that target, not claims of partial MVP completion.

Status

The frozen S0-S5 runtime scope is under active implementation on master: composed Python CLI/API/UIX, deterministic Nginx protocols, durable identity and MFA, certificate lifecycle, portable configuration, NPM migration, npmctl-compatible plan/apply/drift/audit behavior, and live PostgreSQL restart persistence have executable coverage. External ACME and MySQL restart conformance remain fail-closed release gates; implementation does not imply a published 1.0.0.

Development

uv sync --dev
uv run ruff check .
uv run ruff format --check .
uv run pytest
uv run ssot-registry validate . --write-report

Run the development control plane and open http://localhost:81/ui/:

uv run portwyrm --host 127.0.0.1 --port 81

The container exposes two deliberately separate network surfaces:

  • port 81 is the control plane: its built-in UI lives at /ui/, alongside the administration APIs and health endpoints;
  • ports 80 and 443 are the Nginx data plane for configured proxy traffic and do not serve a second administration UI.

On a fresh container data volume, Portwyrm creates the initial superuser as admin@example.com with a deployment-specific one-time password. Read that password from the first-start container log. The account cannot access control-plane resources until the UI's mandatory password-change ceremony succeeds. The plaintext bootstrap file is removed after the change commits. Set PORTWYRM_INITIAL_ADMIN_EMAIL and PORTWYRM_INITIAL_ADMIN_PASSWORD to supply your own bootstrap credentials; automatic bootstrap credentials are never embedded in the image.

The same package includes an operator CLI. It speaks the npmctl-compatible API, accepts a token through --token or PORTWYRM_TOKEN, and prints stable JSON for automation:

uv run portwyrm setup --email admin@example.com --password 'change-me-now'
uv run portwyrm login --email admin@example.com --password 'change-me-now'
uv run portwyrm status
uv run portwyrm list proxy-hosts --token "$PORTWYRM_TOKEN"
uv run portwyrm create proxy-hosts --token "$PORTWYRM_TOKEN" --data proxy-host.json

serve, status, schema, setup, login, list, get, create, update, delete, export, import, npm-preflight, and npm-import are available without Node.js or npm.

The UI is packaged as standards-based browser assets. Node.js and npm are not required to build, install, deploy, or operate Portwyrm.

Compatibility principles

  • Preserve the NPM-shaped /api surface used by npmctl.
  • Keep compatibility DTOs separate from native domain models.
  • Preserve IDs and npmctl owner metadata during import and round trips.
  • Compile deterministic Nginx generations, validate them, activate atomically, and retain a last-known-good rollback target.
  • Use Tigrbl engines for memory, SQLite, and PostgreSQL metadata. MySQL/MariaDB requires the separately published synchronous tigrbl_engine_mysql plugin; filesystem/object storage holds certificate and immutable generated-config artifacts with explicit consistency and high-availability boundaries.

Deliberate boundaries

  • mTLS and HTTP/3 termination remain out of the frozen NPM-compatible 1.0.0 scope.
  • WebTransport termination is not provided by Nginx. The optional QUIC gateway performs opaque hostname routing and leaves TLS/QUIC/WebTransport termination to the backend.
  • Portwyrm never requires Node.js or npm to install, build, deploy, or operate.

Licensed under Apache-2.0.

Opaque QUIC hostname passthrough

compose.quic-gateway.yaml runs Portwyrm as the sole host UDP/443 publisher while Nginx Proxy Manager continues to own TCP/443. The gateway decrypts only the QUIC Initial metadata needed to read SNI and ALPN, chooses an enabled quic_passthrough_host, and forwards every datagram without terminating TLS. The backend therefore presents its own certificate and owns the HTTP/3/WebTransport session.

The native API exposes authenticated capability discovery at /api/v2/capabilities and owner-scoped CRUD at /api/v2/quic-passthrough-hosts. Wyrmctl is the supported declarative controller for these resources. Unknown SNI, unsupported ALPN, malformed Initial packets, and direct-IP traffic have no route. Application Compose stacks expose container UDP ports only; they never publish host UDP ports.

Metadata

Release files for portwyrm 0.1.0a12

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for portwyrm 0.1.0a12
File Size Uploaded
portwyrm-0.1.0a12.tar.gz 693.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for portwyrm 0.1.0a12
File Interpreter ABI Platform
portwyrm-0.1.0a12-py3-none-any.whl Python 3 none any Details

Total release size: 1.4 MB

Release files / portwyrm-0.1.0a12.tar.gz

Download URL portwyrm-0.1.0a12.tar.gz
Size 693.3 kB
Tags Source
SHA-256 checksum
How to use checksums
05ac33e2ebf6f5944a71d6df8505f7a7b1f762250ac5932dbd06b970968031ab
BLAKE2b-256 checksum
How to use checksums
31e73bc93d09a7c7bd307d5206701a3a47ae5a69b6fd182ded328a9304566a27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / portwyrm-0.1.0a12-py3-none-any.whl

Download URL portwyrm-0.1.0a12-py3-none-any.whl
Size 721.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
36e046ed42ec500acff8e2fa7778c8bb9d64cb45b029896efc1d0d55123327e8
BLAKE2b-256 checksum
How to use checksums
21c4e1544461e126adaf6f749a03482e75ee1c579cfde94ce214bf88444fe28d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page