Skip to main content

qhost

qhost publishes a directory of static files — a built site, a rendered report, a coverage tree — at a URL that requires access and expires on a schedule. The operator runs one container, points a DNS record at their own reverse proxy, and manages shares from the command line. It is access-controlled hosting for a single trusted publisher per instance; it does not isolate mutually distrusting shares from one another.

Self-host the server

qhostd ships as ghcr.io/janfasnacht/qhost-server. Drop a docker-compose.yml and .env somewhere on your box and start it:

# docker-compose.yml
services:
  qhostd:
    image: ghcr.io/janfasnacht/qhost-server:0.1.0
    restart: unless-stopped
    environment:
      QHOST_DOMAIN: ${QHOST_DOMAIN:?}
      QHOST_TRUSTED_PROXY: ${QHOST_TRUSTED_PROXY:?}
      QHOST_TOKEN: ${QHOST_TOKEN:-}
    volumes:
      - qhost_data:/data
    ports:
      - "127.0.0.1:8000:8000"

volumes:
  qhost_data:
# .env
QHOST_DOMAIN=share.example.com
QHOST_TRUSTED_PROXY=127.0.0.1   # whatever IP / CIDR your proxy connects from
QHOST_TOKEN=                    # leave blank to auto-generate on first start
docker compose up -d
docker compose exec qhostd cat /data/operator-token   # if you left it blank

qhostd is now on http://127.0.0.1:8000 of the host, ready to be proxied. The full env-var surface is in deploy/.env.example and the canonical compose is in deploy/docker-compose.yml.

Wire a reverse proxy

qhostd doesn't terminate TLS, doesn't strip incoming X-Forwarded-For, and doesn't enforce a body-size cap before the request reaches Python. A proxy in front of it has to do those three things, and qhostd has to trust that proxy via QHOST_TRUSTED_PROXY (an IP or CIDR — see above).

Full ready-to-edit examples are in deploy/proxies/. The interesting line in each:

Caddy (deploy/proxies/Caddyfile)

{$QHOST_DOMAIN} {
    reverse_proxy 127.0.0.1:8000 {
        header_up X-Forwarded-For {http.request.remote.host}   # overwrite, not append
    }
    request_body { max_size 110MB }
    header Strict-Transport-Security "max-age=31536000; includeSubDomains"
}

nginx (deploy/proxies/nginx.conf)

proxy_set_header X-Forwarded-For $remote_addr;   # overwrite, not append
client_max_body_size 110m;

Cloudflare Tunnel (deploy/proxies/cloudflared.md)

ingress:
  - hostname: share.example.com
    service: http://127.0.0.1:8000

cloudflared connects from loopback, so QHOST_TRUSTED_PROXY=127.0.0.1 is what you want.

Install the CLI

pip install qhost
qhost login https://share.example.com

qhost login prompts for the operator token and writes it to ~/.config/qhost/config.toml (mode 0600).

Publish

cd path/to/built/site && qhost publish

qhost publish from a directory containing index.html publishes that directory as-is. From a Quarto project (_quarto.yml present) it runs quarto render first. Otherwise it refuses; pass --dir <path> to point at a built output or --build <name> to force a specific builder.

qhost --help covers the rest: publish, ls, info, extend, rm, open, doctor. Default expiry is seven days. --password gates a share behind HTTP Basic with a generated password; --public makes a guessable URL with no secret; the default link mode treats the URL itself as the credential — /s/<slug>/ carries ~80 bits of entropy and is not enumerable.

qhost publish --sandbox serves a share under Content-Security-Policy: sandbox allow-scripts, the only intra-instance isolation primitive. qhost doctor flags content that probably wants it.

Trust model

One operator token authorizes all publishing on an instance; every visitor is untrusted. All shares serve from the same browser origin, so a share's JavaScript can reach any other share on the same instance with the visitor's ambient Basic-auth credentials. For mutually distrusting publishers, run separate instances on separate origins.


Configuration: deploy/.env.example. Reverse proxy examples: deploy/proxies/. Vulnerability reports: SECURITY.md. MIT.

Metadata

Release files for qhost 0.1.0

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

Source distribution (sdist)

Source distribution for qhost 0.1.0
File Size Uploaded
qhost-0.1.0.tar.gz 69.8 kB Details

Built distribution (wheel)

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

Total release size: 151.5 kB

Release files / qhost-0.1.0.tar.gz

Download URL qhost-0.1.0.tar.gz
Size 69.8 kB
Tags Source
SHA-256 checksum
How to use checksums
62992d974b73afe75be4eda6f45450bb0e2b67847cc8296199fecd60caaa738a
BLAKE2b-256 checksum
How to use checksums
9da3a127736d2cf2b3a0cf8a8c70990df96ad4063c14b945a867bfe8c2592c52
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 24, 2026.

Transparency log

Release files / qhost-0.1.0-py3-none-any.whl

Download URL qhost-0.1.0-py3-none-any.whl
Size 81.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
09114fbf82c80b4b01e5a1458da31c81ecfa3f3cbf0ee466d5b7d971efb339b7
BLAKE2b-256 checksum
How to use checksums
db17b0645a32117bfda5d4a3e933bdd443d64f0a8d933cd4d005f8ceb537eecd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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