5.4 KiB
Self-hosted HTTP Toolkit
A private distribution repo that builds HTTP Toolkit into a single self-contained binary that runs the backend and serves the web UI locally:
./httptoolkit --port 7070
# → starts the proxy server and opens the web UI at http://localhost:7070
No Electron, no hosted UI, no account/login. Upstream is tracked as git submodules and all local modifications live in this repo as patches + an overlay, so updating to a new upstream release is a controlled, reviewable step.
Layout
.
├── httptoolkit-ui/ # submodule → github.com/httptoolkit/httptoolkit-ui (pinned)
├── httptoolkit-server/ # submodule → github.com/httptoolkit/httptoolkit-server (pinned)
├── patches/
│ ├── ui/ # diffs against upstream UI files
│ └── server/ # diffs against upstream server files
├── overlay/server/ # NEW files copied into the server submodule (never conflict)
│ ├── htk-entry.ts # single-file binary entry (Bun)
│ └── src/
│ ├── htk-app.ts # shared: run backend + serve UI + open browser
│ └── commands/app.ts # oclif fallback command
├── scripts/
│ ├── apply.sh # reset submodules → apply patches → copy overlay
│ ├── build.sh # build UI → embed → bun compile → dist/
│ └── smoke.sh # launch binary & verify it serves + backend is up
├── .build/prod/ # Docker builder (Gitea `docker-compose` runner)
│ ├── Dockerfile # Node + Bun + native toolchain
│ ├── compose.yml # mounts repo, runs build.sh
│ ├── build.sh # build + smoke → out/
│ └── release.sh # Gitea-API release + upload
└── .gitea/workflows/build.yml
What the patches change
| Kind | File | Change |
|---|---|---|
| UI patch | src/model/account/account-store.ts |
injects a synthetic active Pro subscription |
| UI patch | src/components/settings/settings-page.tsx |
removes the account/subscription card |
| UI patch | src/components/app.tsx |
removes the "Give feedback" sidebar button |
| Server patch | src/constants.ts |
allow localhost/127.0.0.1 origins in prod builds |
| Overlay | htk-entry.ts, src/htk-app.ts, src/commands/app.ts |
serve the UI locally + --port |
Security note: upstream restricts a packaged build's API to https://app.httptoolkit.tech
so no other local page can drive your proxy. Because we self-host the UI on localhost we
must allow localhost origins (this matches upstream's own dev-mode allowlist). Trade-off: any
local HTTP page could talk to the server while it's running. Acceptable for a personal tool.
Build locally
Requires node ≥22, npm, bun, and tar.
git submodule update --init --recursive
bash scripts/build.sh # → dist/httptoolkit
bash scripts/smoke.sh # optional: verify it runs
Run it:
./dist/httptoolkit --port 7070 # UI on :7070
./dist/httptoolkit --no-open # don't auto-open a browser
./dist/httptoolkit --help
Updating to a new upstream release
# bump a submodule to a new tag
cd httptoolkit-ui && git fetch && git checkout v<new> && cd ..
git add httptoolkit-ui
# re-apply and see if patches still fit
bash scripts/apply.sh
If a patch no longer applies, apply.sh fails and names the patch. Fix it:
cd httptoolkit-ui
git checkout -- . # reset
# hand-apply the change, then regenerate the patch:
git diff -- <file> > ../patches/ui/000X-....patch
Overlay files never conflict (they're new files), so only the small patches/ diffs
ever need attention on upgrade.
Packaging notes / risks
- Single file via Bun
--compile. The server pulls in native.nodeaddons (node-datachannel,registry-js,adbkit, …). These cannot be cross-compiled, so the binary is built per host platform.scripts/smoke.shis the guard: if a native addon won't load from the embedded FS, it fails there. - Linux only, for now. CI builds inside a Linux Docker container, which produces a trustworthy Linux x64 binary. Windows/macOS binaries are not reliably buildable from Linux Docker (foreign-platform native addons can't be produced or smoke-tested there, and macOS needs signing) — they'd need real Windows/macOS runners.
- The UI is embedded as
ui.tar.gzand extracted to$TMPDIR/httptoolkit-ui-<version>on first run. - No
HTK_SERVER_TOKENis set, so the local UI talks to the backend without a token.
CI (Gitea Actions, Docker)
Matches the docker-compose runner convention:
.gitea/workflows/build.yml— on av*tag (or manual dispatch): checks out with submodules, runsdocker compose run --rm prodin.build/prod/, then creates a Gitea release and uploads the artifacts via the Gitea API (secrets.PAT_GITEA)..build/prod/— the Docker builder:Dockerfile— Node 22 + Bun + native-build toolchaincompose.yml— mounts the repo at/source, caches npm/bunbuild.sh— runsscripts/build.sh+scripts/smoke.sh, drops the binary inout/release.sh— Gitea-API release + asset upload
Tag a release:
git tag v1.27.1 && git push origin v1.27.1