missbetty
v1.7.0
Published
Local Docker development domains through one Traefik switchboard
Maintainers
Readme
Betty
Betty is a lightweight CLI for local Docker development domains. It links running Docker containers to local domains through one global Traefik reverse proxy, with HTTPS certificates generated by mkcert when available.
The name is inspired by a 1950s telephone switchboard operator: Betty does not become your reverse proxy. She links local domains to running containers and keeps proxy infrastructure out of individual projects.
Betty currently orchestrates:
- Docker
- Docker Compose
- one global Traefik proxy
- mkcert for local HTTPS certificates
Status
Betty is an early prototype. The current goal is a small, Valet-like workflow:
betty project load
betty project create
betty project link
betty project stop
betty project status
betty serve
betty link
betty relink
betty status
betty doctor
betty setup
betty setup --fix
betty unlink
betty stop
betty restThe core workflow does not require a project configuration file. Start your
containers with Docker or Docker Compose, then let Betty link them to local
domains. Projects that want a single command can add .betty.yml and run
betty project load.
Requirements
- Node.js 24 or newer
- Docker with Docker Compose
- Access to the Docker socket
- mkcert for HTTPS support
On Linux, macOS, WSL, and devcontainers, Betty expects Docker commands such as
docker ps and docker compose version to work from the shell where Betty runs.
Installation
Install without Node.js or npm
Betty publishes standalone binaries on GitHub Releases. The installer scripts verify SHA256 checksums before installation. Release assets are also signed with Sigstore Cosign (keyless certificates).
Current prebuilt targets:
- Linux x64
- Linux arm64
- macOS x64
- macOS arm64
- Windows x64
- Windows arm64
Linux/macOS:
curl -fsSL https://raw.githubusercontent.com/mcKanses/missbetty/main/install.sh | sudo shWindows PowerShell:
irm https://raw.githubusercontent.com/mcKanses/missbetty/main/install.ps1 | iexOptional version pinning:
BETTY_VERSION=v1.6.0 curl -fsSL https://raw.githubusercontent.com/mcKanses/missbetty/main/install.sh | sh$env:BETTY_VERSION = 'v1.6.0'; irm https://raw.githubusercontent.com/mcKanses/missbetty/main/install.ps1 | iexWindows installer options:
- Skip dependency installation (Docker/mkcert):
$env:BETTY_SKIP_DEPS = 'true'; irm https://raw.githubusercontent.com/mcKanses/missbetty/main/install.ps1 | iex- Increase Docker daemon wait timeout (seconds, default 240, minimum 30):
$env:BETTY_DOCKER_WAIT_SECONDS = '420'; irm https://raw.githubusercontent.com/mcKanses/missbetty/main/install.ps1 | iexThe binary install path is:
- Linux/macOS:
/usr/local/bin/betty(or$BETTY_INSTALL_DIR/betty) - Windows:
%LOCALAPPDATA%\\Programs\\betty\\betty.exe(or$env:BETTY_INSTALL_DIR\\betty.exe)
You still need runtime tools for Betty workflows (Docker and optionally mkcert), but Node.js and npm are no longer required for using Betty.
Uninstall standalone binary
Linux/macOS:
curl -fsSL https://raw.githubusercontent.com/mcKanses/missbetty/main/uninstall.sh | shWindows PowerShell:
irm https://raw.githubusercontent.com/mcKanses/missbetty/main/uninstall.ps1 | iexInstall from source (Node.js + npm)
From this repository:
npm install
npm run build
npm linkAfter npm link, the CLI is available as:
betty --helpQuick Start
For project-level orchestration, add .betty.yml to the project root:
project: my-app
up:
command: docker compose up --build -d
down:
command: docker compose down
domains:
- host: my-app.dev
target: http://127.0.0.1:3000
https:
enabled: true
certificateAuthority: missbetty
permissions:
hosts: prompt
trustStore: prompt
docker: allowedThen start the project:
betty project loadBetty prepares the local route and prints the available URLs:
https://my-app.devFor the lower-level workflow, start the global proxy once, start your containers yourself, then link a running container:
betty serve
betty link my-app --domain my-app.localhost --port 3000Commands
betty project
Manages project-level orchestration from .betty.yml.
betty project load
Reads .betty.yml, prepares hosts entries and mkcert certificates, starts
Betty's global proxy, writes project routes, runs the configured up.command,
then prints the available URLs. Loopback targets such as 127.0.0.1 and
localhost are routed through host.docker.internal inside the Traefik
container.
betty project load
betty project load --file ./.betty.yml
betty project load --dry-run
betty project load -yExample .betty.yml:
project: mckanses-auth
up:
command: docker compose --env-file .env -f compose.yml -f compose.override.yml up --build -d
down:
command: docker compose -f compose.yml -f compose.override.yml down
domains:
- host: my-app.dev
target: http://127.0.0.1:5173
- host: api.my-app.dev
target: http://127.0.0.1:8080
https:
enabled: true
certificateAuthority: missbetty
permissions:
hosts: prompt
trustStore: prompt
docker: allowedConfig fields:
| Field | Description |
| --- | --- |
| project | Stable project name used for Betty route file names |
| up.command | Shell command run after hosts, certificates, and proxy routes are ready |
| down.command | Shell command run on project stop |
| domains[].host | Local domain Betty should expose |
| domains[].target | Local HTTP(S) target for the domain, for example http://127.0.0.1:5173 |
| https.enabled | Enables HTTPS routes and mkcert certificates |
| https.certificateAuthority | Currently supports missbetty |
| permissions.hosts | prompt, allowed, manual, or denied for hosts-file changes |
| permissions.trustStore | prompt, allowed, manual, or denied for mkcert CA setup |
| permissions.docker | prompt, allowed, manual, or denied for Docker commands |
betty project create
Creates a new .betty.yml interactively.
betty project create
betty project create --name my-appbetty project link
Links project domains to the Betty proxy without running the up.command.
Auto-detects .betty.yml in the current directory.
betty project link
betty project link --file ./.betty.yml
betty project link -ybetty project stop
Runs the configured down.command and removes all domain links for the
project. Auto-detects .betty.yml in the current directory.
betty project stop
betty project stop --file ./.betty.yml
betty project stop -ybetty project status
Shows which project domains are currently linked. Auto-detects .betty.yml
in the current directory, or accepts --name to look up a project by name.
betty project status
betty project status --file ./.betty.yml
betty project status --name my-appbetty dev (legacy)
betty dev is a legacy alias for betty project load. Use betty project load
instead.
betty serve
Starts Betty's global local switchboard service.
It creates:
~/.betty~/.betty/docker-compose.yml~/.betty/dynamic~/.betty/certs- Docker network
betty_proxy - Traefik container
betty-traefik
Traefik publishes HTTP on host port 80 and HTTPS on host port 443 by
default. Both ports must be available on the host while Betty is running.
If another local proxy already holds 80/443, you can publish Betty on
different host ports via the BETTY_HTTP_PORT / BETTY_HTTPS_PORT environment
variables, or by adding httpPort / httpsPort to ~/.betty/config.json.
Traefik still listens on 80/443 inside the container, so routing and
certificates are unaffected — but non-default ports mean URLs need an explicit
port, e.g. https://my-app.dev:8443.
Betty keeps routing config globally in ~/.betty/dynamic, and local
certificates in ~/.betty/certs, so individual projects do not need
Betty-specific files.
Betty also maintains ~/.betty/links.json — the source container for each
linked route, written when you link and pruned when you unlink — and, when you
change settings, ~/.betty/config.json. Commands that change this shared state
(serve, link, relink, unlink, dev/project) take a short exclusive
lock on ~/.betty, so running two Betty commands at once is refused with a
"please retry" hint instead of corrupting the files.
betty stop
Stops Betty's global local switchboard service.
betty rest is available as a legacy alias with the same behavior.
It runs Docker Compose down against Betty's global compose file:
~/.betty/docker-compose.ymlbetty status
Shows Betty's proxy status and linked domains.
betty status
betty status --short
betty status --long
betty status --json| Option | Description |
| --- | --- |
| --long | Show detailed proxy container info |
| --short | Show a compact linked-domain table |
| --json | Output status as JSON |
| --format <format> | Output format, for example json |
betty link [container]
Links a running container to a local domain.
betty link my-container --domain my-app.localhost --port 3000If required values are missing, Betty asks interactively for:
- container
- domain
- internal container port
Betty links the container into the global Docker network betty_proxy, writes
a route file to ~/.betty/dynamic, and reloads the global Traefik container.
If mkcert is installed, Betty also creates a certificate in ~/.betty/certs
and enables HTTPS for the linked domain.
Use .localhost domains when possible, for example my-app.localhost. Betty
also accepts custom domains such as .dev and can add an append-only hosts
entry for them. Browsers require HTTPS for some TLDs, including .dev, so
mkcert should be installed before linking those domains.
| Option | Description |
| --- | --- |
| --domain <domain> | Target domain, for example my-app.localhost |
| --port <port> | Internal container port |
| --dry-run | Preview planned changes without applying them |
| --open | Open the linked domain in the browser after linking |
betty relink [target]
Updates an existing local domain link.
Use it when the container, domain, or internal port changes:
betty relink
betty relink my-app --port 5173
betty relink --domain my-app.example.dev
betty relink my-app.localhost --container new-container --port 3000If values are missing, Betty asks interactively. When the domain changes to a
custom domain outside .localhost, Betty attempts to add a new append-only
hosts entry. It does not remove the previous hosts entry.
| Option | Description |
| --- | --- |
| --container <container> | New target container |
| --domain <domain> | New linked domain |
| --port <port> | New internal container port |
betty unlink [target]
Removes an existing local domain link.
betty unlink my-app.localhost
betty unlink --domain my-app.localhost
betty unlink --allFor custom domains that are not under .localhost, betty unlink removes
the hosts entry when no other link uses the same domain. If removal fails,
Betty prints the domain so you can remove it manually.
| Option | Description |
| --- | --- |
| --domain <domain> | Domain to unlink |
| --all | Remove all links at once |
betty config [action] [key] [value]
Reads or updates Betty configuration.
betty config
betty config get <key>
betty config set <key> <value>Supported keys: domainSuffix, httpPort, httpsPort.
betty config set domainSuffix .localhost
betty config set httpPort 8080
betty config set httpsPort 8443Ports set here are also overridable per shell via the BETTY_HTTP_PORT /
BETTY_HTTPS_PORT environment variables, which take precedence.
Development
npm install
npm run build
npm run lint
npm test
npm run test:serial
npm run test:coverageThe default test script runs Jest serially so it works in restricted environments that cannot spawn parallel Jest workers. Coverage also runs serially for the same reason.
Release
Development happens on development. Releases are published from main by
GitHub Actions with semantic-release.
Use Conventional Commits so semantic-release can determine the next version:
fix: Repair npm package contents
feat: Add a new link option
feat!: Change the link command contractRelease behavior:
fix:creates a patch releasefeat:creates a minor releaseBREAKING CHANGE:or!creates a major releaseci:andchore:do not trigger a release by default
Before merging to main, run the full local release check:
npm run release:checkAfter a merge to main, GitHub Actions runs semantic-release. If releasable
commits exist, it creates the Git tag, GitHub release, and npm publish
automatically without pushing a release commit back to main.
Configure the repository secret NPM_TOKEN with an npm automation or granular
access token that can publish missbetty.
The publish workflow uses Node.js 24. Betty itself requires Node.js 24 or newer at runtime.
Manual fallback release scripts are still available:
npm run release:patch
npm run release:minor
npm run release:majorThe fallback scripts are guarded and only run on the main branch.
For emergency manual publishing:
npm run release:publishprepack builds the TypeScript output before npm creates the package. Keep the
manual scripts as fallback only; the normal release path is semantic-release on
main.
Devcontainer
This repository includes a devcontainer setup using Docker-outside-of-Docker. That means Node and TypeScript run inside the devcontainer, while Docker containers are created through the host Docker daemon.
This is suitable for CLI development and Docker orchestration tests. Host browser access to custom domains may still require host-specific handling for the hosts file.
Notes
Betty is not intended to replace Traefik, Caddy, nginx, or Docker Compose. Instead, Betty provides a small CLI layer around them for local development.
License
Betty is released under the MIT License. See LICENSE for the full terms.
Support Betty
If Betty saves you time, consider supporting development:
- GitHub Sponsors: https://github.com/sponsors/mcKanses
- Buy me a coffee: https://buymeacoffee.com/mckanses
Support helps me work on:
- WSL support improvements
- TLS automation
- better Docker integration
Future work may include:
- cleaner persistent link state
- optional project discovery
Troubleshooting
Port 443 is already in use
If betty serve reports a conflict on 443, list the containers using it:
docker ps --filter "publish=443" --format "table {{.Names}}\t{{.Ports}}"If an old Traefik stack is still running, stop it:
docker stop traefik-traefik-1Then run:
betty serve