Lightweight local development domain router with ASGI and WSGI middleware support
Project description
🌐 Devhost
Secure, flexible local domain routing for developers.
Devhost allows you to map subdomains of a base domain (default: localhost, e.g. myapp.localhost) to local app ports, with optional HTTPS and wildcard routing via Caddy and a Python backend.
Installation
PyPI Package (Recommended)
pip install devhost
After installation, use the devhost CLI directly:
devhost add hello 3000
devhost list
devhost open hello
Git Clone (Development)
For development or customization:
git clone https://github.com/Patoruzuy/devhost.git
cd devhost
python install.py --linux # or --macos, --windows
Features
- CLI Tool: Map subdomains to ports (e.g.,
app.localhost→localhost:1234) - Zero-Config Runner: One line to run your app with subdomain support (v2.3+)
- Project Config:
devhost.ymlper-project configuration - Custom Base Domain: Change from
localhostto anything (e.g.,app.flask,api.devhost) - ASGI Middleware: Embed subdomain routing in FastAPI/Starlette apps
- WSGI Middleware: Flask and Django support (v2.2+)
- Remote Network Devices: Map to any IP on your network (e.g.,
rpi.localhost→192.168.1.100:8080) - HTTPS Support: Via Caddy's internal CA (use
--https) - Cross-Platform: Works on macOS, Linux, and Windows
- Hot Reload: Changes take effect immediately without restart
- Auto-Registration: Apps register themselves on startup, cleanup on exit
Use Cases
1. Zero-Config Runner (Recommended - v2.3+)
The simplest way to run your app with subdomain support:
# Create project config
devhost init
# → Creates devhost.yml with your app name
# In your app
from flask import Flask
from devhost_cli.runner import run
app = Flask(__name__)
@app.route('/')
def index():
return "Hello!"
if __name__ == '__main__':
run(app) # That's it! Accessible at http://myapp.localhost
Output:
🚀 Starting myapp...
✓ Registered: myapp.localhost → 127.0.0.1:8000
🌐 Access at: http://myapp.localhost:8000
Framework-specific wrappers:
# Flask
from devhost_cli.frameworks.flask import run_flask
run_flask(app)
# Flask with SocketIO
from devhost_cli.frameworks.flask import run_flask
run_flask(app, socketio=socketio)
# FastAPI
from devhost_cli.frameworks.fastapi import run_fastapi
run_fastapi(app)
# Django
from devhost_cli.frameworks.django import run_django
run_django()
2. CLI Tool (Traditional Usage)
Manage local development domains from the command line:
devhost add api 8000
devhost add frontend 3000
devhost list
3. ASGI Middleware (v2.1+)
Embed Devhost routing directly in your FastAPI/Starlette application:
from fastapi import FastAPI
from devhost_cli.middleware.asgi import DevhostMiddleware
app = FastAPI()
app.add_middleware(DevhostMiddleware)
@app.get("/")
async def read_root():
return {"message": "Hello from FastAPI with Devhost routing!"}
3. WSGI Middleware (v2.2+)
Embed Devhost routing in your Flask or Django application:
Flask:
from flask import Flask
from devhost_cli.middleware.wsgi import DevhostWSGIMiddleware
app = Flask(__name__)
app.wsgi_app = DevhostWSGIMiddleware(app.wsgi_app)
@app.route("/")
def index():
return {"message": "Hello from Flask with Devhost routing!"}
Django:
# In your wsgi.py file
from devhost_cli.middleware.wsgi import DevhostWSGIMiddleware
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
application = DevhostWSGIMiddleware(application)
4. Factory Function (ASGI)
Quick ASGI app setup with built-in routing:
Create a complete Devhost-enabled app with one function:
from devhost_cli.factory import create_devhost_app
# Creates FastAPI app with subdomain routing + proxy endpoints
app = create_devhost_app()
More examples: View all integration patterns on GitHub →
Custom Base Domains
By default, Devhost uses .localhost (e.g., app.localhost, api.localhost), but you can change it to any domain you want.
Change the base domain:
devhost domain flask
Now all your routes use .flask instead:
devhost add app 3000 # Creates app.flask → localhost:3000
devhost add api 8000 # Creates api.flask → localhost:8000
devhost add rpi 192.168.1.100:8080 # Creates rpi.flask → 192.168.1.100:8080
Visit app.flask, api.flask, or rpi.flask in your browser!
Why use custom domains?
- Project-specific namespaces: Use
myprojectdomain for all related services - Better organization: Separate domains for different projects (
frontend.prod,api.staging) - Avoid conflicts: If another tool uses
.localhost, switch to.devor.local - Memorable names:
blog.gatsbyis easier to remember thanblog.localhost
Setup requirements:
After changing the domain, re-run the installer to update DNS/resolver configuration:
devhost domain myproject
python install.py --linux --domain myproject # or --macos, --windows
All routes automatically use your custom domain - both localhost ports AND remote network devices!
Project Configuration (devhost.yml)
Create a devhost.yml in your project root for per-project settings:
devhost init
Or create manually:
# devhost.yml
name: myapp # App name (becomes subdomain)
port: 8000 # Port to run on (omit for auto-detect)
domain: localhost # Base domain
auto_register: true # Register route on startup
auto_caddy: true # Prompt to start Caddy for port 80
The runner reads this config automatically:
from devhost_cli.runner import run
run(app) # Uses settings from devhost.yml
Benefits for Devs
- No need to remember localhost:PORT combos
- Clean and memorable dev URLs
- HTTP by default; HTTPS available with
--https - Works with any language/framework running locally
- Zero-config: Just run your app, it registers itself
Quickstart
Install via pip:
pip install devhost
Add your first route:
devhost add hello 3000
devhost list
devhost open hello
Visit hello.localhost in your browser.
From Source (Development)
git clone https://github.com/Patoruzuy/devhost.git
cd devhost
python install.py --linux
devhost add hello 3000
devhost list
devhost remove hello
Note: the devhost CLI is implemented in Python (cross-platform).
Installation Options
The installer supports various flags to customize the setup process:
| Flag | Description | Platforms |
|---|---|---|
--yes |
Accept all prompts automatically (non-interactive mode) | All |
--dry-run |
Show what would be done without making any changes | All |
--domain <name> |
Set custom base domain (default: localhost) | All |
--start-dns |
Automatically start DNS service (dnsmasq) | macOS, Linux |
--install-completions |
Install shell completions for bash/zsh | macOS, Linux |
--caddy |
Install and configure Caddy web server | Windows |
--clean |
Remove existing installation before reinstalling | Windows |
Cross-platform installer (uses the Python CLI):
python install.py --linux
macOS example:
python install.py --macos --yes --start-dns --install-completions
Windows example (PowerShell):
python .\install.py --windows --caddy
To change the base domain (for example, hello.flask), set it once and re-run your installer to update DNS/resolvers:
devhost domain flask
python install.py --domain flask
Run the router locally (development):
cd router
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn app:app --host 127.0.0.1 --port 5555 --reload
Run the router in Docker (quick):
docker compose up --build -d
# then open http://127.0.0.1:5555 with Host header set to <name>.localhost
docker-compose.yml mounts the repo devhost.json into the container so edits take effect immediately.
Notes & safety
install.pyhandles Linux/macOS/Windows and will prompt you about DNS changes. Review DNS/resolver changes before applying them on systems usingsystemd-resolved.- We now generate the project
caddy/Caddyfile, the user~/.config/caddy/Caddyfile, and (if present)/etc/caddy/Caddyfileto keep system Caddy installs in sync. - The router loads
devhost.jsonper request so CLI changes take effect immediately without restarting the router.
Quick Commands
devhost add <name> <port|host:port>— add a mapping (e.g.devhost add hello 3000).devhost add <name> <ip:port>— add remote IP mapping (e.g.devhost add rpi 192.168.1.100:8080).devhost add <name> --http <port|host:port>— force HTTP when opening the dev URL.devhost add <name> --https <port|host:port>— force HTTPS when opening the dev URL.devhost remove <name>— remove a mapping.devhost list— show active mappings.devhost list --json— show mappings as JSON.devhost url <name>— print the URL and press Ctrl+O to open it in the browser.devhost open <name>— open the URL in the default browser.devhost validate— quick health checks (config JSON, router health, DNS).devhost export caddy— print the generated Caddyfile to stdout.devhost edit— opendevhost.jsonin$EDITOR(fallback:nano/vi).devhost resolve <name>— show DNS resolution and port reachability for a mapping.devhost doctor— deeper diagnostics (dnsmasq/systemd-resolved/Caddy).devhost doctor --windows— Windows-specific diagnostics (Caddy, port 80, hosts).devhost doctor --windows --fix— attempt Windows fixes (hosts sync + free port 80 + start Caddy).devhost info— show all commands and usage.devhost status --json— print router status as JSON (running, pid, health).devhost domain [name]— show or set the base domain (default:localhost).devhost hosts sync— re-apply hosts entries for all mappings on Windows (admin).devhost hosts clear— remove all devhost entries from the Windows hosts file (admin).devhost caddy start|stop|restart|status— manage Caddy on Windows.devhost fix-http— convert allhttps://mappings tohttp://and regenerate Caddyfile.
Remote IP Support
Devhost supports mapping subdomains to devices on your local network, not just localhost ports. Remote devices use the same base domain as your localhost routes.
Perfect for:
- Raspberry Pi projects
- IoT devices
- Other computers on your network
- Docker containers with bridge networking
Examples
With default .localhost domain:
Raspberry Pi running a web server:
devhost add rpi 192.168.1.100:8080
# Access at http://rpi.localhost
Network attached storage (NAS):
devhost add nas 192.168.1.50:5000
devhost open nas # Opens http://nas.localhost
With custom .devhost domain:
# First, change the base domain
devhost domain devhost
# Then add remote devices
devhost add rpi 192.168.1.100:8080 # Access at rpi.devhost
devhost add nas 192.168.1.50:5000 # Access at nas.devhost
devhost add router 192.168.1.1:80 # Access at router.devhost
Mix localhost and remote IPs (all use same domain):
devhost domain myproject
devhost add frontend 3000 # frontend.myproject → localhost:3000
devhost add api 8000 # api.myproject → localhost:8000
devhost add rpi 192.168.1.100:8080 # rpi.myproject → 192.168.1.100:8080
devhost add staging 10.0.0.25:3000 # staging.myproject → 10.0.0.25:3000
Configuration Format
Remote IPs can be added in several formats:
192.168.1.100:8080- Full IP with port10.0.0.25:3000- Any valid IPv4 address- Combined with other targets in
devhost.json:
{
"hello": 3000,
"api": 8000,
"rpi": "192.168.1.100:8080",
"nas": "192.168.1.50:5000"
}
Notes
- The remote device must be reachable from your machine
- Ensure firewalls allow traffic to the target port
- IP addresses are validated when adding routes
- Use
devhost resolve <name>to test connectivity
Configuration
The project uses a devhost.json file (project root) with a simple mapping of names to ports. Example:
{
"hello": 3000,
"api": 8000
}
This file is created/updated by the CLI and is meant to be local (it’s gitignored). The router reads DEVHOST_CONFIG if set; otherwise it looks for the project root devhost.json (even when run from router/). The base domain comes from DEVHOST_DOMAIN or .devhost/domain (default: localhost).
Router endpoints
GET /health— liveness + route count + uptime.GET /metrics— basic request metrics (totals, per-status, per-subdomain).GET /routes— current routes with parsed targets.GET /mappings— current routes with basic TCP health checks.
Logging
DEVHOST_LOG_LEVELcontrols router log verbosity (default:INFO).DEVHOST_LOG_FILEwrites logs to a file in addition to stdout.DEVHOST_LOG_REQUESTS=1enables per-request logging.
Quick test (curl)
Run the router (locally or via Docker) and test with curl by setting the Host header:
curl -H "Host: hello.localhost" http://127.0.0.1:5555/
Regenerating Caddyfile
The devhost CLI writes both the project caddy/Caddyfile (generated, gitignored) and, when present, the user ~/.config/caddy/Caddyfile. Inspect generated files before reloading system Caddy and, when appropriate, reload the service:
# inspect
less caddy/Caddyfile
# if using system Caddy (Linux)
sudo systemctl reload caddy
Troubleshooting
General Issues
- Check mappings:
devhost list- Verify your routes are configured correctly. - Router health:
curl http://127.0.0.1:5555/healthshould return{ "status": "ok" }. - Check router logs: Look for request IDs in logs to trace specific requests (X-Request-ID header).
- Validate setup:
devhost validate- Quick health checks for config, router, and DNS. - Deep diagnostics:
devhost doctor- Comprehensive system diagnostics.
DNS & Domain Issues
- Linux DNS issues: Check
systemd-resolvedand/etc/resolv.conffor unintended changes. - macOS DNS issues: Verify
/etc/resolver/<domain>file exists and containsnameserver 127.0.0.1. - Windows DNS issues: Wildcard DNS requires a local resolver like Acrylic DNS. Alternatively, use
devhost hosts syncto add individual entries to hosts file. - Domain resolution:
devhost resolve <name>- Show DNS resolution and port reachability.
Windows-Specific Issues
Hosts File Management
When to use hosts file (Windows only):
- You don't have a wildcard DNS resolver installed (like Acrylic DNS)
- You want individual domain entries instead of wildcard DNS
- Testing specific routes without full DNS setup
Admin elevation required:
devhost hosts sync- Adds/updates all routes inC:\Windows\System32\drivers\etc\hosts(requires admin)devhost hosts clear- Removes all devhost entries from hosts file (requires admin)
Admin NOT required:
devhost add/remove/list- Regular route managementdevhost start/stop/status- Router process managementdevhost validate- Health checks
The hosts file commands modify system files and therefore require administrator privileges. The CLI will automatically attempt to relaunch with elevation if needed.
Other Windows Issues
- Port 80 conflicts: Run
devhost doctor --windowsto check what's using port 80. - Port 80 auto-fix:
devhost doctor --windows --fix- Attempts to free port 80 and start Caddy. - Caddy not running:
devhost caddy status- Check Caddy status. - Start Caddy:
devhost caddy start- Start Caddy web server. - Windows diagnostics:
devhost doctor --windows- Windows-specific checks.
HTTPS & Certificate Issues
- Browser forcing HTTPS: Clear HSTS settings for the domain or change base domain (
devhost domain devhost2). - Caddy not running: Ensure Caddy is running if you depend on system TLS (
systemctl status caddyon Linux). - Convert to HTTP:
devhost fix-http- Convert all HTTPS mappings to HTTP.
Remote IP Issues
- Remote device unreachable: Verify the remote IP/port is accessible from your machine (
curl http://192.168.1.100:8080). - Network firewall: Ensure firewall rules allow traffic to the remote device.
- Wrong IP address: Check the device's current IP (
devhost listto see configured IPs).
Platform notes
install.pytargets Linux/macOS/Windows; it readsDEVHOST_DOMAINor.devhost/domainto configure DNS for the base domain.- On Windows, run the installer from an elevated PowerShell if you want hosts entries updated automatically, or use a local DNS resolver (Acrylic) for wildcard domains.
Release notes
See CHANGELOG.md for the v1.0.0 release notes.
macOS installer
Run the Python installer to generate the LaunchAgent plist (from router/devhost-router.plist.tmpl), create /etc/resolver/<domain>, and optionally start dnsmasq via Homebrew:
# dry-run (print actions)
python install.py --macos --dry-run
# run interactively (will prompt for username and uvicorn path)
python install.py --macos
Non-interactive example (accept all prompts and start dnsmasq if available):
python install.py --macos --yes --start-dns
To use a custom base domain on macOS:
devhost domain flask
python install.py --macos --domain flask
Windows installer
Run the Python installer from PowerShell to prepare the venv, router deps, and initial config:
python .\install.py --windows --caddy
python .\devhost add hello 8000
To clean and reinstall:
python .\install.py --windows --clean
If you want a shortcut in PowerShell without typing python, use:
.\devhost.ps1 add hello 8000
.\devhost.ps1 start
Note: the router requires a Host header. Don’t browse http://127.0.0.1:5555 directly — use devhost open <name> or:
curl -H "Host: hello.localhost" http://127.0.0.1:5555/
Tip (Windows): if your app only listens on IPv4, Devhost uses `127.0.0.1` for numeric ports to avoid IPv6 `::1` connection errors.
Tip: On Windows, devhost.ps1 start will try to start Caddy (if installed) before starting the router.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file devhost-2.3.0.tar.gz.
File metadata
- Download URL: devhost-2.3.0.tar.gz
- Upload date:
- Size: 44.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6610d784281e80bbfcc9a0bffad574730b71277ded831b7d14c7909753675f90
|
|
| MD5 |
00cdc7a0b8a1ffd65fbe366ecc46f215
|
|
| BLAKE2b-256 |
9affa5157e5ff2ff3f98cc6ace166ab2ea286b61fe2d7ca5263248027255a72b
|
Provenance
The following attestation bundles were made for devhost-2.3.0.tar.gz:
Publisher:
publish.yml on Patoruzuy/Devhost
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
devhost-2.3.0.tar.gz -
Subject digest:
6610d784281e80bbfcc9a0bffad574730b71277ded831b7d14c7909753675f90 - Sigstore transparency entry: 895586330
- Sigstore integration time:
-
Permalink:
Patoruzuy/Devhost@e4900c4f16b28238eab16989d60b5970fbe759c1 -
Branch / Tag:
refs/tags/v2.3.0 - Owner: https://github.com/Patoruzuy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e4900c4f16b28238eab16989d60b5970fbe759c1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file devhost-2.3.0-py3-none-any.whl.
File metadata
- Download URL: devhost-2.3.0-py3-none-any.whl
- Upload date:
- Size: 44.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8457cbdc28fc1f21366806609abba52ec61c4fdfa6380e9200f16ee1681340c7
|
|
| MD5 |
93e9a5f2c8828f89946e23f977eb523c
|
|
| BLAKE2b-256 |
111e6f479865699d6dc3244a75d324b12c8e98362e0d7b349c0e1c243f9159d9
|
Provenance
The following attestation bundles were made for devhost-2.3.0-py3-none-any.whl:
Publisher:
publish.yml on Patoruzuy/Devhost
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
devhost-2.3.0-py3-none-any.whl -
Subject digest:
8457cbdc28fc1f21366806609abba52ec61c4fdfa6380e9200f16ee1681340c7 - Sigstore transparency entry: 895586363
- Sigstore integration time:
-
Permalink:
Patoruzuy/Devhost@e4900c4f16b28238eab16989d60b5970fbe759c1 -
Branch / Tag:
refs/tags/v2.3.0 - Owner: https://github.com/Patoruzuy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e4900c4f16b28238eab16989d60b5970fbe759c1 -
Trigger Event:
push
-
Statement type: