A simple reverse proxy for local development environments. Automatically discovers Docker Compose services and provides HTTPS access via *.dev.localhost.
"Use the highway (Traefik) for production, take the back alley (roji) for development"
- Auto-discovery: Automatically detects and routes containers on the shared network
- Auto HTTPS: Generates and installs TLS certificates with zero configuration
- Live Dashboard: Real-time route updates, request logging, and project management
- Docker Compose Operations: Start/stop/restart projects from dashboard or API
- Label-based Configuration: Customize hostnames, ports, and paths via container labels
- WebSocket & gRPC: Full bidirectional WebSocket and HTTP/2 gRPC proxying
- Request Mocking: Define mock responses via labels for frontend development
- Basic Authentication: Protect routes with username/password via labels or config
- Static File Hosting: Serve static files with directory listing
- Service Management: Run as system service (systemd/launchd/Windows Service)
- Environment Diagnostics:
roji doctorchecks and fixes common issues
Install roji:
# macOS
brew install kan/roji/roji
# Linux / macOS (one-liner)
curl -fsSL https://raw.githubusercontent.com/kan/roji/v1.0.0/install.sh | bashAdd your app to the roji network:
# your-app/docker-compose.yml
services:
myapp:
image: your-app
expose:
- "3000"
networks:
- roji
networks:
roji:
external: trueStart your app and open https://myapp.dev.localhost — that's it!
For a step-by-step walkthrough, see Getting Started.
brew install kan/roji/rojicurl -fsSL https://raw.githubusercontent.com/kan/roji/v1.0.0/install.sh | bashThis will:
- Download the roji binary for your platform (Linux/macOS, x86_64/arm64)
- Install to
~/.local/binby default (interactive prompt for location) - Run
roji doctor --fixto set up the environment - Install CA certificate to system trust store
- Register and start roji as a system service
Options:
curl -fsSL ... | bash -s -- --global # Install to /usr/local/bin
curl -fsSL ... | bash -s -- --local # Install to ~/.local/bin (default)
curl -fsSL ... | bash -s -- --no-service # Skip service registration
curl -fsSL ... | bash -s -- --upgrade # Skip upgrade promptsRe-run the install script. It detects existing installations and upgrades automatically:
curl -fsSL https://raw.githubusercontent.com/kan/roji/v1.0.0/install.sh | bashDownload from GitHub Releases or build from source:
git clone https://github.com/kan/roji.git && cd roji && make build
sudo ./bin/roji doctor --fix # Set up environment
sudo ./bin/roji ca install # Install CA certificate
sudo ./bin/roji service install && sudo ./bin/roji service start- Docker with Docker Compose v2
- roji installed and running (see Installation)
roji doctorAll checks should pass. If not, run sudo roji doctor --fix to auto-repair.
Create a project with a web frontend and an API backend:
# my-project/docker-compose.yml
services:
web:
image: nginx:alpine
expose:
- "80"
networks:
- roji
api:
image: node:alpine
working_dir: /app
command: ["node", "server.js"]
expose:
- "3000"
networks:
- roji
networks:
roji:
external: truecd my-project
docker compose up -dYour services are now available at:
https://web.dev.localhost— the nginx frontendhttps://api.dev.localhost— the Node.js API
Open https://roji.dev.localhost to see the live dashboard with all your routes, request logs, and project controls.
Custom hostname:
labels:
- "roji.host=myapp.dev.localhost"Path-based routing (multiple services on one host):
# https://myapp.dev.localhost/api/* -> api service
labels:
- "roji.host=myapp.dev.localhost"
- "roji.path=/api"Specific port (when multiple ports are exposed):
expose:
- "3000"
- "9229" # debugger
labels:
- "roji.port=3000"Multiple projects — just connect each to the roji network. Each service gets its own *.dev.localhost subdomain automatically.
Configuration file: ~/.config/roji/config.yaml
network: roji # Docker network(s) to watch (comma-separated)
domain: dev.localhost # Base domain
http_port: 80 # HTTP port (redirect to HTTPS)
https_port: 443 # HTTPS port
certs_dir: ~/.local/share/roji/certs # Certificate directory
data_dir: ~/.local/share/roji # Data directory
dashboard: roji.dev.localhost # Dashboard hostname
log_level: info # Log level (debug, info, warn, error)
auto_cert: true # Auto-generate certificates
static_sites: # Static file hosting (see below)
- host: docs
root: ~/projects/docs/buildManage with roji config show | path | init | edit.
| Variable | Description | Default |
|---|---|---|
ROJI_NETWORK |
Docker network(s) to watch (comma-separated) | roji |
ROJI_DOMAIN |
Base domain | dev.localhost |
ROJI_HTTP_PORT |
HTTP port | 80 |
ROJI_HTTPS_PORT |
HTTPS port | 443 |
ROJI_CERTS_DIR |
Certificate directory | ~/.local/share/roji/certs |
ROJI_DATA_DIR |
Data directory (project history) | ~/.local/share/roji |
ROJI_DASHBOARD |
Dashboard hostname | roji.{domain} |
ROJI_LOG_LEVEL |
Log level | info |
ROJI_AUTO_CERT |
Auto-generate certificates | true |
Highest to lowest: CLI flags > Environment variables > Config file > Defaults
| Label | Description | Default |
|---|---|---|
roji.host |
Custom hostname | {service}.dev.localhost |
roji.port |
Target port | First EXPOSE'd port |
roji.path |
Path prefix | none |
roji.mock.{METHOD}.{PATH} |
Mock response body | none |
roji.mock.status.{METHOD}.{PATH} |
Mock response status code | 200 |
roji.auth.basic.user |
Basic auth username | none |
roji.auth.basic.pass |
Basic auth password | none |
roji.auth.basic.realm |
Basic auth realm | Restricted |
roji.self |
Reserved: excludes container from routing (used internally) | none |
services:
# Custom hostname
api:
image: my-api
labels:
- "roji.host=api.dev.localhost"
networks:
- roji
# Path-based routing: https://myapp.dev.localhost/api/* -> this service
api-service:
image: my-api
labels:
- "roji.host=myapp.dev.localhost"
- "roji.path=/api"
networks:
- roji
# Request mocking (returns mock JSON without a real backend)
mock-api:
image: alpine
command: ["sleep", "infinity"]
labels:
- "roji.host=api.dev.localhost"
- 'roji.mock.GET./api/users=[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]'
- 'roji.mock.GET./api/health={"status":"ok"}'
- "roji.mock.status.POST./api/users=201"
networks:
- rojiServe static files without Docker containers. Configure in ~/.config/roji/config.yaml:
static_sites:
- host: docs # -> docs.dev.localhost
root: ~/projects/docs/build
# index: true # Directory listing (default: enabled)
- host: private.example.com # FQDN (dot in hostname)
root: /var/www/private
index: false # Disable directory listing
auth:
basic:
user: admin
pass: secret
realm: Private AreaUse roji config reload or the dashboard's "Reload Config" button to apply changes without restarting.
Protect routes with username/password. Via Docker labels:
labels:
- "roji.auth.basic.user=admin"
- "roji.auth.basic.pass=secret"
- "roji.auth.basic.realm=Admin Area" # optionalOr via config file for static sites (see Static File Hosting above).
Access the dashboard at:
https://roji.dev.localhost(dashboard host)https://dev.localhost(redirects to dashboard host)
The dashboard provides:
- Live Route Updates: Real-time updates via Server-Sent Events when containers start/stop
- Browser Notifications: Optional desktop notifications for route changes
- Active Projects: Currently running Docker Compose projects with start/stop/restart controls
- Project History: Recently stopped projects with one-click start or copy of restart commands
- Project Auto-start: Not Found page detects inactive projects and offers a start button
- Request Log Viewer: Real-time request logging with filtering by host and path
- Log Export: Download request logs as JSON or CSV
- Container Restart: Restart containers directly from the dashboard
- Route Management: Stop projects directly from the routes list
- Dark Mode: Toggle between light/dark themes or follow system preferences
- System Status: Build version, uptime, connection status
The dashboard automatically updates without page refresh.
Start the reverse proxy server:
sudo roji # Run in foreground
sudo roji --network web,api # Watch multiple networks
sudo roji --domain test.localhost # Custom domain
sudo roji --http-port 8080 --https-port 8443 # Custom ports
sudo roji --log-level debug # Verbose loggingFlags: --network, --domain, --http-port, --https-port, --certs-dir, --data-dir, --dashboard, --log-level, --auto-cert, --config
Check environment and fix common issues:
roji doctor # Run all checks
roji doctor --fix # Auto-fix issues where possible
roji doctor --json # Output as JSONChecks: Docker daemon, socket access, network existence, port availability, CA certificate, server certificate validity, DNS resolution, config file validation.
Manage configuration:
roji config show # Display current settings
roji config path # Show config file locations
roji config init # Create default config file
roji config edit # Open in $EDITORManage CA certificate:
roji ca status # Check installation status
roji ca install # Install to system trust store
roji ca install --user # Install to user store (no sudo)
roji ca install --windows # Install to Windows (from WSL)
roji ca install --firefox # Also install to Firefox (Linux)
roji ca uninstall # Remove from trust store
roji ca export [path] # Export CA certificate (.pem or .crt)Manage roji as a system service:
roji service install # Register as system service
roji service uninstall # Remove service registration
roji service start # Start the service
roji service stop # Stop the service
roji service restart # Restart the service
roji service status # Show service statusPlatform support:
- Linux: systemd (
/etc/systemd/system/roji.service) - macOS: launchd (
~/Library/LaunchAgents/com.roji.agent.plist) - Windows: NSSM-based Windows Service
View server logs:
roji log # Follow logs in real-time (like tail -f)
roji log -n 100 # Show last 100 lines, then follow
roji log --no-follow # Print current logs and exitLog file location:
- Linux/WSL:
~/.local/share/roji/roji.log - macOS:
~/Library/Logs/roji.log
List all registered routes from the running server.
Show version, commit hash, build date, and Go version.
Check if the roji server is healthy. Exits with code 0 if healthy, 1 otherwise.
All API endpoints are served on the dashboard host (e.g., https://roji.dev.localhost).
| Endpoint | Method | Description |
|---|---|---|
/_api/health |
GET | Health check ({"status":"healthy","routes":N}) |
/healthz |
GET | Alias for /_api/health |
/_api/status |
GET | Detailed status (version, uptime, certificates, Docker) |
/_api/routes |
GET | List all registered routes |
/_api/projects |
GET | List active and inactive projects |
/_api/events |
GET | SSE stream for real-time route updates |
/_api/logs |
GET | Recent request logs (last 100) |
/_api/logs/events |
GET | SSE stream for real-time log updates |
/_api/logs/export |
GET | Export logs as JSON or CSV (query: format, service, host, method, from, to) |
/_api/containers/{id}/restart |
POST | Restart a container |
/_api/projects/{name}/up |
POST | Start project (docker compose up -d) |
/_api/projects/{name}/down |
POST | Stop project (docker compose down) |
/_api/projects/{name}/restart |
POST | Restart all services in project |
/_api/projects/{name}/logs |
GET | Stream project logs (SSE) |
/_api/projects/{name}/delete |
DELETE | Remove project from history |
/_api/config/reload |
POST | Reload config file (updates static sites) |
Health status values: healthy (all systems operational), degraded (certificates expiring within 30 days), unhealthy (Docker connection lost).
Always start with diagnostics:
roji doctor # Check for issues
sudo roji doctor --fix # Auto-fix where possibleThis checks Docker, networking, certificates, ports, and DNS — and can fix most common problems automatically.
macOS: .localhost resolves to 127.0.0.1 automatically. No action needed.
Linux (systemd-resolved): Most modern distributions resolve .localhost automatically. If not:
# Check if systemd-resolved handles it
resolvectl query myapp.dev.localhost
# If not, add entries to /etc/hosts
echo "127.0.0.1 myapp.dev.localhost roji.dev.localhost dev.localhost" | sudo tee -a /etc/hostsFirefox: Firefox may not use the system DNS resolver. Go to about:config and set browser.fixup.domainsuffixwhitelist.localhost to true.
Alternative: Use *.lvh.me — a public domain that always resolves to 127.0.0.1:
# ~/.config/roji/config.yaml
domain: lvh.meThe CA certificate is not trusted by your browser or OS. Run:
sudo roji ca install # Install to system trust storeThen restart your browser completely (close all windows).
Firefox uses its own certificate store. Either:
- Run
sudo roji ca install --firefox(Linux, requiresnss-tools) - Or manually import: Firefox → Settings → Privacy & Security → Certificates → View Certificates → Authorities → Import → select the CA certificate file
WSL: You need certificates in both Linux and Windows:
sudo roji ca install # Linux trust store
sudo roji ca install --windows # Windows trust store (for Windows browsers)Export CA certificate for manual installation:
roji ca export ~/roji-ca.pem # PEM format (macOS/Linux)
roji ca export ~/roji-ca.crt # DER format (Windows)Using mkcert (alternative): If you prefer mkcert, generate certificates to the roji certs directory before starting. roji will use existing certificates and skip auto-generation.
-
Verify the container is connected to the
rojinetwork:docker network inspect roji
-
Check if the port is exposed (use
exposein docker-compose.yml orEXPOSEin Dockerfile):docker inspect <container> | jq '.[0].Config.ExposedPorts'
-
Check the dashboard at
https://roji.dev.localhostfor configuration warnings.
Check what is using the port:
sudo lsof -i :80
sudo lsof -i :443Common culprits: Apache, nginx, or another reverse proxy. Stop the conflicting service, or configure roji to use alternative ports:
# ~/.config/roji/config.yaml
http_port: 8080
https_port: 8443Or via environment variables: ROJI_HTTP_PORT=8080 ROJI_HTTPS_PORT=8443
Certificates: Install in both Linux and Windows for browsers to trust HTTPS:
sudo roji ca install # Linux
sudo roji ca install --windows # Windows (for Chrome/Edge on Windows)PATH: If installed to ~/.local/bin, ensure it's in your PATH:
export PATH="$HOME/.local/bin:$PATH" # Add to ~/.bashrc or ~/.zshrcDocker: WSL2 with Docker Desktop works out of the box. Ensure Docker Desktop's WSL integration is enabled.
Keychain: roji ca install adds the CA to the System Keychain (requires password). Use --user for the login keychain (no sudo).
Docker Desktop: Containers run in a Linux VM. roji connects via the Docker socket, which works transparently.
Port permissions: Ports below 1024 require sudo. The installer sets this up via launchd. For manual runs, use sudo roji.
Certificate stores: roji ca install supports Debian/Ubuntu (update-ca-certificates) and RHEL/Fedora (update-ca-trust).
Chrome/Chromium NSS: Chrome uses its own certificate store on Linux. If you see certificate errors only in Chrome:
# Install nss-tools
sudo apt install libnss3-tools # Debian/Ubuntu
sudo dnf install nss-tools # Fedora
# Add CA to Chrome's store
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "roji CA" -i ~/.local/share/roji/certs/ca.pemPort permissions: Ports below 1024 require root. Use sudo roji or the service installer (roji service install uses systemd with proper privileges).
roji log # Follow logs in real-time
roji log -n 50 # Show last 50 lines
roji log --no-follow # Print and exitLog file locations:
- Linux/WSL:
~/.local/share/roji/roji.log - macOS:
~/Library/Logs/roji.log
Logs are automatically rotated when they exceed 10MB.
roji means "back alley" or "narrow lane" in Japanese. The concept is to use the highway (Traefik) for production and casually take the back alley (roji) for local development.
MIT