Developing localghost¶
Repository layout¶
compose.yamlis the stable public interface and must remain self-contained.examples/compose.yamlsupplies primary, secondary, and unlabelled fixtures.scripts/integration-test.shexercises the compatibility contract.src/localghost/contains the packaged Click CLI, bundled proxy Compose definition, override generator, and source-loaded Traefik provider.pyproject.tomlanduv.lockdefine the build and locked development environment..github/workflows/ci.ymlvalidates the project on Linux with Docker Compose 5.1.4, which is the fixed CI baseline rather than a required user-side patch version..github/dependabot.ymlproposes reviewed Python, Traefik, and GitHub Actions updates. The pinned Caddy host-bridge image lives ingenerator.py, so review it explicitly during dependency maintenance.
The proxy Compose file must not gain application-specific mounts, state, or
configuration files. Consumers rely on the fixed localghost project and
localghost network names.
Static validation¶
Resolve and validate both Compose files:
LOCALGHOST_IMAGE_TAG="v$(uv run localghost --version)" \
docker compose -f compose.yaml config --quiet
COMPOSE_PROJECT_NAME=localghost-fixture-a \
docker compose -f examples/compose.yaml config --quiet
bash -n scripts/integration-test.sh
uv run ruff check .
uv run pytest
(cd src/localghost/traefik_plugin/src/github.com/SmileyChris/traefik-localghost-ca \
&& go test ./...)
The pytest suite covers generator logic in isolation and exercises the CLI against resolved Compose models. It validates generated Compose, Dockerfile, host-bridge, extension, backup, and refusal behavior.
Review the fully rendered fixture configuration when changing interpolated labels:
Integration suite¶
Run:
The suite is destructive only to Docker resources named localghost,
localghost-fixture-a, localghost-fixture-b, localghost-fixture-host, and
localghost-fixture-dockerfile. It refuses to begin if any of those resources
already exist and cleans up resources it creates even after failure. Ports 80,
18080, 18443, and 19090 must be available.
Coverage includes:
- validation and proxy-first external-network failure;
- repeated idempotent proxy startup and container health;
- exact loopback port publication and absence of raw API port publication;
- two concurrent fixture projects with isolated primary routes;
- secondary-service routing and rejection of an unlabelled container;
- explicit backend-port selection when another port is exposed;
- generated bridging to an HTTP application running directly on the host;
- trusted HTTPS routing with the bootstrapped public root, without changing the host trust store;
- dashboard root redirection and internal dashboard access;
- removal of one application without affecting another or the proxy;
- proxy restart and forced reconciliation without consumer recreation; and
- recreation on a non-default loopback port.
Override test names or ports only when necessary:
TEST_DEFAULT_PORT=18081 \
TEST_ALTERNATE_PORT=18082 \
TEST_HTTPS_PORT=18444 \
./scripts/integration-test.sh
CI should retain the default port-80 run because loopback publication on the public default is part of the release contract.
Documentation¶
Documentation is built with Zensical. Preview it locally with:
Build the static site with:
Build the CLI package¶
Build both the source distribution and wheel:
Test the local package through the same isolated tool mechanism used after PyPI publication:
uvx --from . localghost --help
uvx --from . localghost down --help
uvx --from . localghost generate --help
Release-candidate test¶
The checked-in source test does not prove that the packaged CLI contains the proxy definition it starts. Before release, build a candidate wheel, then run the lifecycle commands from that wheel:
uv build --no-sources
wheel=$(find dist -maxdepth 1 -name '*.whl' -print -quit)
uvx --isolated --from "$wheel" localghost
uvx --isolated --from "$wheel" localghost down
CI performs the same wheel smoke test before the source integration suite.
PyPI authentication¶
For releases from GitHub Actions, use PyPI Trusted Publishing (recommended).
After configuring the repository, workflow, and environment in PyPI, uv
publish --trusted-publishing always obtains a short-lived credential through
GitHub Actions OIDC; no long-lived PyPI token is stored in GitHub.
For a manual publish, provide a project-scoped PyPI API token to uv:
uv auth login https://upload.pypi.org/legacy/ is an alternative for storing
the token in uv's credential store. Do not commit tokens or credentials. PyPI
does not support account username-and-password uploads; use an API token.
Release checklist¶
- Review dependency changes and security implications, including the Traefik proxy image and generated Caddy host-bridge image.
- Run static validation and the local integration suite.
- Pilot two independent checkouts with unique Compose project names (e.g. two checkouts of the same application).
- Update
CHANGELOG.mdandpyproject.tomlversion, and all example version tags. - Make a trial build with
uv build --no-sources, test the resulting wheel withuvx, and exercise the lifecycle commands from the release commit in CI. Push that exact commit tomain, then wait for its CI run to succeed. When documentation changed, also wait for the GitHub Pages deployment to succeed: - After those workflows pass, use a clean checkout of that exact commit to
build the final artifacts once, smoke-test the wheel, and record both
checksums:
test -z "$(git status --porcelain)" rm -rf dist uv build --no-sources wheel=$(find dist -maxdepth 1 -name '*.whl' -print -quit) sdist=$(find dist -maxdepth 1 -name '*.tar.gz' -print -quit) test -n "$wheel" && test -n "$sdist" uvx --isolated --from "$wheel" localghost uvx --isolated --from "$wheel" localghost down sha256sum "$wheel" "$sdist" - Create the immutable SemVer tag and a draft GitHub release for that commit,
attaching the exact artifacts from step 6. Patch-release notes should be short and
front-facing: one summary sentence, 3–6 practical highlights, and an
uvx --refresh localghostupgrade command. Reserve a full introduction and CLI reference for major releases. - Publish the exact artifacts from step 6 without rebuilding them:
- Wait for the published version to resolve from PyPI, then refresh and verify its lifecycle. Confirm a refreshed unpinned resolution selects the same version: PyPI propagation can take a short time; retry the exact-version check until it resolves rather than assuming an upload is immediately available.
- Publish the GitHub release draft only after the PyPI verification succeeds:
Consumer-visible changes to fixed names, labels, hostname conventions, or lifecycle commands require a major version. Additive compatible features may be minor releases; compatible fixes, reviewed image updates, and documentation updates may be patch releases.