One config file with paths and hostnames. roost generates the Dockerfiles, Compose stack, Caddy routing, and Cloudflare Tunnel — and creates every DNS record via API. No files added to your repos, ever.
You supply where each app lives and what hostname it answers on. Framework, port, database, start command, and memory cap are inferred — every one overridable, none required.
# ~/.roost/config.yml — minimal domain: rajan.dev apps: - ~/code/blog # → blog.rajan.dev - ~/code/shop # → shop.rajan.dev
# …or with every knob tunnel: name: roost access: emails: [[email protected]] apps: - path: ~/code/trackaru domain: trackaru.com # bare apex, own zone memory: 768m profile: extras
One tunnel, one wildcard DNS record per suffix, host-header routing inside. That architecture is why adding an app is a purely local change with zero API calls.
| Signal in the folder | Framework | Port | Start command |
|---|---|---|---|
Gemfile + config/application.rb | rails | 3000 | puma, bound to 0.0.0.0 |
Gemfile + config.ru + sinatra | sinatra | 4567 | rackup |
package.json with next | next | 3000 | npm run start |
package.json with vite | static | 80 | built, served by Caddy |
package.json with express | node | 3000 | npm run start |
manage.py + requirements/pyproject | django | 8000 | gunicorn |
index.html, no manifest | static | 80 | served by Caddy |
Also inferred: runtime version, database need (one shared MySQL/Postgres, one database per app), and memory caps. Detection is explainable — roost detect prints the exact signal — and never guesses silently.
Prerequisites: Docker, and a domain on Cloudflare. You create one API token — roost automates every other Cloudflare step, including all DNS records. There is no "add this CNAME in your dashboard" step.
$ brew install cdrrazan/tap/roost $ roost init # picks your domain from your live zone list $ roost auth login # paste the API token (init links the page + scopes) $ roost doctor # every failure comes with a specific fix $ roost tunnel setup # tunnel + ALL DNS records via API $ roost up # build, start, route — live on HTTPS $ roost enable # come back up at every login
A day in the life after setup: check what's running, tail a log, park heavy apps in a profile, and let the doctor explain anything odd.
| Command | What it does |
|---|---|
roost init | interactive setup; picks your domain from your live Cloudflare zone list, scans a folder for apps, writes explicit hostnames |
roost add / remove | edit the app list — your config comments are preserved |
roost list / detect | resolved apps and URLs; detection with the signal that triggered it |
roost generate | write ~/.roost/build/* without starting anything |
roost up [--profile] / down | start the stack (staggered, memory-capped) / stop it |
roost status / logs / restart | state, health, memory vs cap, public URL; tail logs; bounce one app |
roost doctor | preflight everything — Docker, token scopes, zones, DNS shadowing, SSL depth — with a fix per failure |
roost tunnel setup | create the tunnel + every DNS record via API; idempotent, refuses to clobber records it didn't create |
roost tunnel access | apply the Cloudflare Access policy across every suffix |
roost auth login | verify and store the API token (~/.roost/credentials, 0600) |
roost enable / disable | run at login via launchd (macOS) / systemd --user (Linux); fully removable |
Full schema and hostname rules: configuration reference · runnable configs: examples, including a fully-populated demo.
The failure modes that cost hours are encoded, tested, and checked by roost doctor.
Cloudflare terminates TLS and forwards plain HTTP; roost sets RAILS_ASSUME_SSL so force_ssl apps don't redirect forever.
Free Universal SSL covers one subdomain level. Doctor flags hostnames nested deeper — before you hit an unexplainable browser TLS error.
An exact DNS record silently beats a wildcard. roost pre-flights every record and names the collision and both ways out.
Databases and apps never publish host ports; everything rides the internal network. One shared MySQL/Postgres, one database per app.
Hostnames leak via Certificate Transparency within hours. Set access.emails and every suffix gets an auth wall before first up.
Memory caps per app, single-worker Rails defaults, staggered starts so six apps don't spike your CPU at once.
Label-driven tools automate tunnel routing for containers you already maintain. roost starts one step earlier: from source folders.
| roost | DockFlare / TunnelDock | Coolify | |
|---|---|---|---|
| Input | a list of source folders | running containers + labels | git repos, web UI |
| Dockerfiles & Compose | generated for you | you write them | buildpacks / you |
| DNS strategy | one wildcard per suffix | per-hostname API records | your server's DNS |
| Adding an app | local change only | labels + API calls | UI / git push |
| Runs on | your laptop | your Docker host | a server you operate |
Your apps roost while the laptop is open and leave when it closes. Lid shut — apps down; lid open — cloudflared reconnects in seconds. roost is for demos, previews, and personal projects, and it says so plainly.
Creator & maintainer of roost — Ruby and Go developer building tools that keep side projects fun. roost exists because putting ten local apps online should be one config file, not ten YAML rituals.