Skip to main content

webmd

CI

Serve any directory in your browser, with Markdown files rendered as proper web pages. Point it at a folder of notes, docs, or a repo and browse it like a website: folders become clickable listings, .md files render GitHub-style, and everything else (images, PDFs, source files) is served as-is.

Pure Python, with a single dependency (cryptography, used for HTTPS certificates).

cd ~/notes
webmd --open

Features

  • GitHub-flavored Markdown rendering: headings, tables, task lists, strikethrough, autolinks, and fenced code blocks with syntax highlighting.
  • Automatic light/dark mode that follows your OS setting.
  • Directory browsing with breadcrumbs, sorted folders → Markdown → other files. Dotfiles are hidden unless you pass --all.
  • Heading anchors: every heading gets an id, so page.md#some-section links work, just like on GitHub.
  • Raw view: append ?raw to any Markdown URL (or click raw in the header) to see the source.
  • Static files served as-is, so images and relative links inside your Markdown just work.
  • Safe by default:
    • binds to 127.0.0.1 (localhost only) unless you say otherwise
    • auto-enables HTTPS and HTTP Basic Auth whenever it's exposed beyond localhost, with a generated self-signed certificate and password
    • refuses paths that escape the served directory (../, symlinks out)
    • never serves or lists .env / .env.* files
  • No build step, no config. Works on Python 3.9+.

Install

webmd is a command-line tool, so the recommended way is an isolated tool install with uv or pipx.

From PyPI:

uv tool install webmd
# or
pipx install webmd
# or, into the current environment
pip install webmd

Straight from GitHub:

uv tool install git+https://github.com/lets-qa/webmd.git
# or
pipx install git+https://github.com/lets-qa/webmd.git

From a local clone (add --editable to pick up code changes without reinstalling):

git clone git@github.com:lets-qa/webmd.git
cd webmd
uv tool install .

If your shell says webmd: command not found after installing, the tool directory isn't on your PATH. Run uv tool update-shell (or pipx ensurepath) and open a new terminal.

Check it worked:

webmd --version

To upgrade or remove:

uv tool upgrade webmd     # pipx upgrade webmd
uv tool uninstall webmd   # pipx uninstall webmd

Usage

webmd [DIR] [-p PORT] [-b BIND] [--all] [--open] [--no-auth] [--env-file PATH]
      [--tls | --no-tls] [--cert PATH --key PATH]
Option Default Description
DIR . Directory to serve
-p, --port 8000 Port to listen on
-b, --bind 127.0.0.1 Address to bind. Any non-loopback address turns on HTTPS and auth
--all off Show dotfiles in directory listings
--open off Open your browser when the server starts
--no-auth off Disable Basic Auth even when bound to a non-loopback address
--env-file ~/.config/webmd/.env Where credentials are read from / generated to
--tls / --no-tls on if non-loopback Force HTTPS on (even on localhost) or off
--cert, --key self-signed in ~/.config/webmd/ Use your own certificate and private key (PEM)
-V, --version Print the version and exit

Examples

webmd                         # serve the current dir at http://127.0.0.1:8000
webmd ~/projects/docs         # serve a specific directory
webmd -p 9000 --open          # different port, open the browser
webmd --all                   # include dotfiles in listings
python -m webmd               # same as `webmd`, no console script needed

URLs

URL What you get
/ or /some/dir/ Directory listing
/notes/todo.md Rendered Markdown page
/notes/todo.md?raw Raw Markdown source
/notes/todo.md#next-steps Rendered page, scrolled to that heading
/images/diagram.png The file itself

Sharing on your network (HTTPS + auth)

By default webmd only listens on localhost, so nothing else on your network can reach it. To share it, bind to another address. HTTPS and Basic Auth then both turn on automatically:

webmd -b 0.0.0.0              # reachable from your LAN at https://, login required

First start: webmd generates a self-signed certificate and a random password into ~/.config/webmd/ (honours $XDG_CONFIG_HOME), and prints both:

webmd serving /Users/you/notes
  -> https://0.0.0.0:8000/  (Ctrl-C to stop)
  tls: generated self-signed certificate /Users/you/.config/webmd/cert.pem
       SHA-256 79:6F:B0:A8:...:67:51:53
  auth: generated credentials in /Users/you/.config/webmd/.env
        user=webmd password=<random-generated-password>
File Mode Contents
~/.config/webmd/.env 0600 Basic Auth username and password
~/.config/webmd/cert.pem 0644 Self-signed certificate
~/.config/webmd/key.pem 0600 Certificate private key

Every start after that reuses the existing file. Edit it to choose your own credentials; changes apply on the next restart:

# ~/.config/webmd/.env
WEBMD_USER=me
WEBMD_PASSWORD=correct-horse-battery-staple

Credential precedence, highest first:

  1. WEBMD_USER / WEBMD_PASSWORD environment variables
  2. The env file (--env-file PATH, default ~/.config/webmd/.env)
  3. Generated on first boot (user webmd, random password)

Turning auth off for a trusted network:

webmd -b 0.0.0.0 --no-auth    # prints a warning on start

HTTPS and the self-signed certificate

The generated certificate covers localhost, 127.0.0.1, ::1, your hostname (plus <hostname>.local), your LAN IP, and the -b address. It's valid for 825 days and is regenerated automatically when it's within 30 days of expiry or no longer covers your current hostname or IP (for example after you join a different network). Otherwise the same certificate is reused, so its fingerprint stays stable.

Because it's self-signed, browsers will warn "Your connection is not private" the first time on each device. That's expected:

  1. Compare the SHA-256 fingerprint printed at startup with the one the browser shows (click the warning → view certificate).
  2. If they match, proceed. If they don't, something is intercepting the connection, so don't continue.

Visiting the http:// URL of an HTTPS server returns a short "This server only speaks HTTPS" message.

No warnings, using your own certificate. With mkcert you can create a certificate your own devices trust:

mkcert -install
mkcert localhost 192.168.1.20 my-laptop.local
webmd -b 0.0.0.0 --cert localhost+2.pem --key localhost+2-key.pem

Any PEM certificate and key work, including ones from Let's Encrypt.

Other combinations:

webmd --tls                   # HTTPS on localhost too
webmd -b 0.0.0.0 --no-tls     # plain HTTP on the LAN (warns: password not encrypted)

Note: with --no-tls, Basic Auth credentials travel base64-encoded, not encrypted. Keep TLS on unless something else (a reverse proxy, Tailscale, cloudflared) already provides HTTPS in front of webmd.

How it works

webmd extends Python's built-in http.server. In HTTPS mode the TLS handshake runs on each connection's own thread, so a slow or stalled client can't block anyone else. When a request hits a Markdown file, the server embeds the raw source in a small HTML page, and the browser renders it with marked, styles it with github-markdown-css, and highlights code with highlight.js.

Things to be aware of:

  • Those libraries load from the jsDelivr CDN, so viewing rendered pages needs internet access. Offline, you'll see an unstyled page.
  • Raw HTML inside Markdown is rendered as-is. That's fine for your own files, but don't serve untrusted Markdown to other people.

Development

git clone git@github.com:lets-qa/webmd.git
cd webmd
uv tool install --editable .   # `webmd` now runs your working copy
uv run --group dev pytest      # run the test suite
uv run --group dev ruff check . # lint
uv build                       # build sdist + wheel into dist/

The version lives in src/webmd/__init__.py.

CI

Every push to main and every pull request runs .github/workflows/ci.yml:

  • lint: ruff check
  • test: pytest on Linux and macOS, Python 3.9 through 3.14
  • build: builds the sdist and wheel, runs twine check, and smoke-tests the installed webmd command

Releasing

Releases publish to PyPI from .github/workflows/release.yml using Trusted Publishing, so no API token is stored in GitHub.

One-time setup:

  1. On PyPI, go to Your projects → Publishing (or, before the first release, Account → Publishing → Add a new pending publisher) and add:
    • Owner: lets-qa, Repository: webmd
    • Workflow: release.yml, Environment: pypi
  2. In GitHub, create an environment named pypi under Settings → Environments. Optionally add yourself as a required reviewer so each publish waits for approval.

Each release:

# bump __version__ in src/webmd/__init__.py, commit, merge to main, then:
git tag v0.2.0
git push origin v0.2.0

The workflow checks the tag matches __version__, runs the tests, builds, publishes to PyPI, and creates a GitHub release with the built files attached.

License

MIT

Metadata

Release files for webmd 0.2.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 webmd 0.2.0
File Size Uploaded
webmd-0.2.0.tar.gz 14.0 kB Details

Built distribution (wheel)

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

Total release size: 29.6 kB

Release files / webmd-0.2.0.tar.gz

Download URL webmd-0.2.0.tar.gz
Size 14.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9fbf8be39972e7708f02041eababc8fe405737712db876383420f8b5bb14c75f
BLAKE2b-256 checksum
How to use checksums
aa4544506cb2220abc4405e7ce5be46cf15365b915e8a52998084961f7dace0c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 9, 2026.

Transparency log

Release files / webmd-0.2.0-py3-none-any.whl

Download URL webmd-0.2.0-py3-none-any.whl
Size 15.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ec9dca9ee4c07bdc8090d30d1c724ad890a1c7a3a2413239a5ca4ff031e3b816
BLAKE2b-256 checksum
How to use checksums
ac1d6e40be2425df4c01f88b16ae394ba010588b9bcff6db046a42f89c9b7c90
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.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