-
Notifications
You must be signed in to change notification settings - Fork 0
custom https subdomains with portless #351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jason-t-hankins
wants to merge
13
commits into
main
Choose a base branch
from
portless
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
f37eb77
initial portless config, remove old subdomain work, add workarounds f…
jason-t-hankins 8d297dd
fix docusaurus config to use portless
jason-t-hankins 6cf3896
update knip to ignore unlisted portless binary
jason-t-hankins 2f20998
Potential fix for pull request finding 'Unused variable, import, func…
jason-t-hankins eebfeb0
create ADR to document experience and recommend portless
jason-t-hankins 6a16268
remove unnecessary cleanup script, move proxy cleanup to package script
jason-t-hankins 322bb9f
error handling for doc/api ports, comment cleanup
jason-t-hankins 532bcbb
refactor dev script, use env vars in payment server, spawn child proc…
jason-t-hankins b4bda86
update storybook to use portless
jason-t-hankins 0229ed0
fix storybook to use proper port
jason-t-hankins 53e0484
sourcery suggestions
jason-t-hankins d5dbabe
automatically install portless globally for simple execution of dev env
jason-t-hankins ace142d
Merge branch 'main' into portless
jason-t-hankins File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { spawn } from 'node:child_process'; | ||
|
|
||
| const envPort = process.env.PORT; | ||
|
|
||
| if (!envPort) { | ||
| console.error( | ||
| 'PORT environment variable is not set. Ensure portless (or your dev environment) is running and has injected a port.', | ||
| ); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const port = envPort; | ||
| const child = spawn('func', ['start', '--typescript', '--port', port], { stdio: 'inherit' }); | ||
| child.on('exit', (code, signal) => { | ||
| process.exitCode = signal ? 1 : (code ?? 1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| --- | ||
| sidebar_position: 25 | ||
| sidebar_label: 0025 Portless Local HTTPS | ||
| description: "Decision record for replacing mkcert + manual HTTPS proxy with portless for local development." | ||
| status: | ||
| date: 2026-02-26 | ||
| deciders: | ||
| --- | ||
|
|
||
| # Local HTTPS Development: portless vs mkcert + manual proxy | ||
|
|
||
| ## Context and Problem Statement | ||
|
|
||
| Local development benefits from HTTPS with named subdomains (e.g. `*.sharethrift.localhost`) to accurately mirror production behaviour: OAuth redirect URIs, CORS policies, cookies, and AI agents all depend on a consistent HTTPS origin. Without this, the local environment diverges from production in ways that are hard to detect until deployment. | ||
|
|
||
| The original solution used `mkcert` to generate a wildcard certificate stored in `.certs/`, combined with a hand-written `local-https-proxy.js` to front Azure Functions, necessary due to `func start`'s broken `--cert` flag in v4.2.2. Each service ran on its own numbered port, requiring developers to remember and configure many separate port assignments. A solution is needed that is easier to maintain and reduces the mental load for developers. | ||
|
|
||
| ## Decision Drivers | ||
|
|
||
| - Developers should not need to manually manage TLS certificates | ||
| - All local services should be accessible via consistent, named HTTPS URLs | ||
| - The approach must not affect production builds or CI pipelines | ||
| - Multiple ports across services makes `.env` configuration error-prone | ||
| - AI Agents should be able to work on the subdomains | ||
|
|
||
| ## Considered Options | ||
|
|
||
| - **portless** - globally installed reverse proxy daemon that maps subdomains to local ports with auto-trusted TLS certificates | ||
| - **mkcert + manual HTTPS proxy** - existing approach using a wildcard cert and a custom Node.js HTTPS proxy | ||
|
|
||
|
|
||
| ### Consequences - portless | ||
|
|
||
| **Positive** | ||
|
|
||
| - No certificate management; TLS certs are auto-generated and auto-trusted | ||
| - Single port (`1355`) for all services: `.env` and `local.settings.json` are simpler | ||
| - Subdomain names in URLs (`data-access`, `mock-auth`, etc.) make it immediately obvious which service is being called | ||
| - `local-https-proxy.js` is deleted, now one less script to maintain | ||
| - Removes compatibility issues with Azure Functions' `--cert` flag | ||
|
|
||
| **Negative** | ||
|
|
||
| - Requires a one-time global install: `pnpm install -g portless` | ||
jason-t-hankins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - `func start` and Docusaurus do not respect the `PORT` environment variable injected by portless; thin `start-dev.mjs` wrapper scripts are required to read `process.env.PORT` and pass `--port` explicitly | ||
| - On macOS, bare `localhost` resolves to `::1` (IPv6), but portless connects via `127.0.0.1` (IPv4); Docusaurus must be started with `--host 127.0.0.1` to avoid a Bad Gateway error | ||
| - `portless proxy start --https` silently no-ops if the proxy is already running in HTTP mode; a `dev-cleanup.mjs` script is needed to run `portless proxy stop` and kill zombie processes before each dev session | ||
|
|
||
| ### Consequences - mkcert | ||
|
|
||
| **Positive** | ||
|
|
||
| - No global tooling dependency and certificates live in the repo (gitignored) | ||
| - Works with any port assignment without daemon management | ||
|
|
||
| **Negative** | ||
|
|
||
| - Developers need to run `mkcert -install` and `mkcert` on each machine | ||
| - Certificate files require explicit exclusion from version control | ||
| - `local-https-proxy.js` must be kept in sync with Azure Functions behaviour | ||
| - CI required `fs.existsSync()` guards to skip HTTPS when certs are absent | ||
| - Many different port numbers to configure across `.env`, `local.settings.json`, and docs | ||
|
|
||
| ## Decision Outcome | ||
|
|
||
| Chosen option: **portless** | ||
|
|
||
| `portless` eliminates the entire certificate lifecycle (generation, installation, `.gitignore` entries, CI detection guards) and replaces the custom `local-https-proxy.js` with a zero-config daemon. All services become reachable via `https://<name>.sharethrift.localhost:1355`, a single consistent pattern. The old multi-port layout is replaced by one port and named subdomains that will remain standard throughout the developer lifecycle. | ||
|
|
||
| ## More Information | ||
|
|
||
| - [portless on npm](https://www.npmjs.com/package/portless) | ||
| - [portless docs](https://port1355.dev/) | ||
| - [mkcert repo](https://github.com/FiloSottile/mkcert) | ||
| - `apps/api/start-dev.mjs`, `apps/docs/start-dev.mjs` — wrapper scripts that pass `PORT` to tools that require an explicit `--port` flag | ||
133 changes: 0 additions & 133 deletions
133
apps/docs/docs/technical-overview/localhost-subdomain-setup.md
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { spawn } from 'node:child_process'; | ||
|
|
||
| const envPort = process.env.PORT; | ||
|
|
||
| if (!envPort) { | ||
| console.error( | ||
| 'PORT environment variable is not set. Ensure portless (or your dev environment) is running and has injected a port.', | ||
| ); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const port = envPort; | ||
| // Use 127.0.0.1 explicitly to ensure IPv4 binding — portless proxy connects via IPv4, | ||
| // but Node.js may resolve 'localhost' to ::1 (IPv6) on macOS, causing Bad Gateway. | ||
| const child = spawn('pnpm', ['exec', 'docusaurus', 'start', '--host', '127.0.0.1', '--port', port, '--no-open'], { stdio: 'inherit' }); | ||
| child.on('exit', (code, signal) => { | ||
| process.exitCode = signal ? 1 : (code ?? 1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.