Skip to content

ratazzi/coulson

Repository files navigation

Coulson

A macOS local development gateway. Say goodbye to localhost:port — each project gets its own domain, ready to use on first visit with automatic startup.

One solution covering local, LAN, and public access — .local domains let phones and nearby devices connect directly, and a single command generates a public URL via Tunnel. Works great with AI IDEs like Cursor and Windsurf.

Why Not Just localhost:port?

Every project on localhost shares the same origin. This causes real problems:

  • Cookies collide — session tokens, JWTs, and auth cookies from one project leak into another, causing mysterious login loops and 403 errors
  • Saved passwords mix up — the browser autofills credentials from project A into project B because they're both localhost
  • Browser history is uselesslocalhost:3000, localhost:3001, localhost:8080... which project was which?
  • localStorage/IndexedDB overlap — data from different projects stomps on each other when they use the same keys
  • Port conflicts — "address already in use" when two projects default to the same port; you waste time hunting which process to kill
  • Remembering ports — was it :3000 or :3001? You end up grepping configs or checking lsof
  • AI coding gets confused too — when you tell Cursor or Claude Code "restart my server", the AI has to figure out which port, which process, which command — and often gets it wrong

Coulson gives every project its own domain (myapp.coulson.local). Cookies, storage, passwords, and history are isolated by the browser automatically — the way they were designed to work. And when you tell your AI assistant "restart myapp", it just runs coulson restart myapp — no ports to remember, no processes to hunt down. The entire process group is killed cleanly (SIGTERM → SIGKILL), so child processes like file watchers, worker threads, and bundlers don't linger as orphans hogging ports.

Features

  • Zero-config routing — directory/file name becomes the domain (myappmyapp.coulson.local)
  • Auto-managed Python ASGI — starts on first request, stops after idle timeout
  • Auto-managed Node.js — detects package manager and start script, starts on first request
  • Auto-managed Docker Composedocker compose up on first request, down on idle
  • Static directory hosting — just drop a public directory
  • Multi-route — path-prefix routing to different backends under one domain
  • .coulson.toml — per-app configuration for routes, env, hooks, and proxy options
  • Lifecycle hooks — run scripts or fire webhooks on app start/stop/ready events
  • mDNS.local domains work out of the box, LAN and mobile devices connect directly
  • Cloudflare Tunnel — one command generates a public URL for sharing
  • Web Dashboard + Menu bar app — visual management

Request Inspector

Install

Download Coulson.app and open it. The daemon starts automatically.

Click Install Command Line Tool... in the menu bar to use the coulson command in the terminal.

Trust Certificate (optional)

Generate a local CA certificate and add it to the system keychain for HTTPS support:

sudo coulson trust

Port Forwarding (optional)

Take over ports 80/443 so you can omit port numbers when accessing:

sudo coulson trust --forward

Quick Start

Listens on 127.0.0.1:18080 (HTTP) and 127.0.0.1:18443 (HTTPS) by default.

Port Proxy

Map an existing service to a local domain:

echo 3000 > ~/.coulson/myapp
curl -i http://myapp.coulson.local:18080/

Python ASGI App

Example project structure:

~/Projects/hello/
  app.py              # async def app(scope, receive, send): ...
  pyproject.toml
  .venv/bin/uvicorn

Symlink to Coulson directory:

ln -s ~/Projects/hello ~/.coulson/hello
curl -i http://hello.coulson.local:18080/

First request auto-starts uvicorn. Reaped after 15 minutes idle.

Node.js App

Example project structure:

~/Projects/myapi/
  index.js            # const http = require("http"); ...
  package.json        # scripts: { "dev": "bun run index.js" }
  bun.lock

Symlink to Coulson directory:

ln -s ~/Projects/myapi ~/.coulson/myapi
curl -i http://myapi.coulson.local:18080/

First request auto-detects the package manager (bun/pnpm/yarn/npm), allocates a free port via the PORT environment variable, and runs the dev or start script. Reaped after 15 minutes idle.

Procfile App

Projects with a Procfile (or Procfile.dev) containing a web: process are auto-managed:

~/Projects/myapp/
  Procfile            # web: bundle exec rails server -p $PORT
ln -s ~/Projects/myapp ~/.coulson/myapp
curl -i http://myapp.coulson.local:18080/

First request allocates a free port via $PORT, runs the web command, and proxies traffic. Procfile.dev takes priority over Procfile when both exist.

To start companion processes (workers, etc.) alongside the web process, add to .coulsonrc:

COULSON_MANAGED_SERVICES=web,worker

All listed process types from the Procfile are started together and share the same lifecycle — idle timeout reaps the entire group.

Docker Compose App

Projects with a compose.yml (or docker-compose.yml) are auto-managed:

~/Projects/myapp/
  compose.yml         # services: web: ...
ln -s ~/Projects/myapp ~/.coulson/myapp
curl -i http://myapp.coulson.local:18080/

First request runs docker compose up -d --build. Idle containers are stopped via docker compose down after the idle timeout. Port discovery uses compose port mappings or $PORT env var.

Static Directory

Projects with a public subdirectory are automatically served as static files:

~/Projects/docs/
  public/
    index.html
    style.css
ln -s ~/Projects/docs ~/.coulson/docs
curl -i http://docs.coulson.local:18080/

Changes are picked up automatically within 2 seconds.

.coulsonrc — Per-App Environment

Add a .coulsonrc file to any managed app directory to set environment variables:

# ~/Projects/myapp/.coulsonrc
PORT=4000
DATABASE_URL=postgres://localhost/myapp_dev

When PORT is set, the app always starts on that fixed port instead of auto-allocating one. Supports KEY=VALUE format with # comments, optional quoting, and export prefix.

.coulson.toml — Per-App Configuration

For full control, add a .coulson.toml in the app directory:

name = "myapp"
domain = "myapp"            # prefix only, suffix appended at runtime
kind = "asgi"               # asgi, node, procfile, docker

# Process
module = "mymodule:app"     # ASGI module
server = "uvicorn"          # ASGI server

# Proxy options
port = 5006
timeout = 5000
cors = false
spa = false

# Remote env injection (fetched before each cold start)
env_url = "https://vault.example.com/env/myapp"
env_url_headers = { Authorization = "Bearer xxx" }

# Environment variables
[env]
DATABASE_URL = "postgres://localhost/myapp_dev"

# Multi-route
[[routes]]
path = "/api"
target = "127.0.0.1:3000"
timeout = 30000

# Lifecycle hooks
[hooks]
[hooks.app_ready]
run = "mise run db:migrate"
webhook = "https://hooks.slack.com/xxx"

Global Hooks

Coulson fires hooks on app lifecycle events. Global hooks are executable scripts in ~/.coulson/hooks/:

~/.coulson/hooks/
  app_ready             # runs when any app becomes ready
  app_stop              # runs when any app stops
  scan_complete         # runs after directory scan

Per-app hooks are configured via [hooks] in .coulson.toml (see above). Each hook receives COULSON_APP_NAME, COULSON_APP_URL, COULSON_APP_DOMAIN, and other context as environment variables.

Per-app events: app_add, app_remove, app_start, app_ready, app_stop, app_idle, tunnel_start, tunnel_stop. Global-only events: scan_complete.

Cloudflare Tunnel

Start/stop tunnels via CLI:

coulson tunnel start myapp
coulson tunnel stop myapp

Also available via the Web Dashboard or the menu bar app.

Quick Tunnel

No configuration needed — assigns a random *.trycloudflare.com URL, great for ad-hoc sharing. Requires cloudflared:

brew install cloudflared

Named Tunnel (recommended)

Configure wildcard DNS for your own domain (e.g. *.example.com) pointing to a Cloudflare Tunnel. Coulson automatically routes subdomains to local projects:

  • myapp.example.com → local myapp
  • hello.example.com → local hello

All projects share one Tunnel connection — no per-app setup needed, new projects are instantly accessible from the public internet.

Management

  • Web Dashboard: http://coulson.local:18080
  • CLI: coulson ls, coulson add, coulson restart, coulson open
  • Menu bar app: Coulson.app menu bar icon

Configuration

Supports TOML config file (~/.config/coulson/config.toml) and environment variables. See example.

Priority: defaults < config file < environment variables.

Built With

Disclaimer

This project is not affiliated with Cloudflare. It uses official Cloudflare APIs and respects all rate limits and account restrictions. Users are responsible for complying with Cloudflare's Terms of Service.

License

See LICENSE for details.