Local reverse-proxy & DNS manager for macOS. Zero port conflicts, parallel development, trusted HTTPS — all from a single CLI.
proxypark replaces the infrastructure layer of tools like Laravel Valet with a framework-agnostic setup built on Traefik, dnsmasq, and mkcert.
Browser → https://myapp.test
│
┌────┴─────┐
│ Traefik │ :443 (only port listener)
└────┬──────┘
│
File-provider
(~/.proxypark/dynamic/)
│
┌─────────┼──────────────┐
│ │ │
▼ ▼ ▼
park link park link:docker park valet enable
localhost container:port Valet catchall
(exposed) (no exposed on localhost:8080
ports needed)
If you run Docker-based projects alongside native services (PHP, Node, etc.), you've likely hit port conflicts — every project wants :80 and :3000. proxypark solves this by routing everything through Traefik using hostnames instead of ports.
- No port conflicts — Everything routes by hostname, not port.
- Parallel development — Run 5+ projects at once, each on its own
*.testdomain. - Trusted HTTPS — Per-domain mkcert certificates, accepted by all browsers including Firefox.
- Mix Docker & native — Services registered via
park link, Valet as optional fallback. - Framework-agnostic — Works with any stack. Laravel, Nuxt, Rails, Go, whatever.
- No Docker socket needed — Uses Traefik's file-provider exclusively. No socket permission headaches.
# Clone and symlink
git clone git@github.com:McGo/proxypark.git ~/proxypark
ln -sf ~/proxypark/park /usr/local/bin/park
# Run setup (installs mkcert, dnsmasq, starts Traefik)
park install
# Link a local service
park link myapp 3000
# → https://myapp.test
# Optional: route unmatched domains to Valet
park valet enable| Command | Description |
|---|---|
park install |
First-time setup |
park uninstall |
Remove everything |
park start |
Start Traefik |
park stop |
Stop Traefik |
park restart |
Restart Traefik |
park status |
Health check |
park doctor [--fix] |
Diagnose and auto-fix common issues |
park trust |
(Re-)install mkcert CA (system + Firefox) |
park certs |
Regenerate certificates from current links |
park logs |
Tail Traefik logs |
park dashboard |
Open Traefik dashboard |
Use dot notation for subdomains: api.myapp → https://api.myapp.test
| Command | Description |
|---|---|
park link <name> <port> |
Link local service → https://<name>.test |
park link:docker <name> <container>[:<port>] |
Link Docker container (no exposed ports) |
park unlink <name> |
Remove link |
park list |
Show all routes |
park open <name> |
Open project in browser |
park scaffold <name> |
Generate docker-compose.yml template |
| Command | Description |
|---|---|
park valet enable [port] |
Route unmatched *.test to Valet (default: 8080) |
park valet disable |
Remove Valet catchall |
proxypark uses Traefik's file-provider exclusively. No Docker socket is mounted — this avoids permission issues with Docker Desktop, OrbStack, Colima, etc.
Each park link creates a small YAML config in ~/.proxypark/dynamic/. Traefik watches this directory and picks up changes instantly — no restart needed.
.test is on the Public Suffix List, which means browsers reject wildcard certificates (*.test) just like they would reject *.com. proxypark solves this by generating explicit per-domain certificates via mkcert.
Every park link and park unlink automatically regenerates the certificate bundle to include all currently linked domains. Traefik picks up the new certs via its file-watcher.
proxypark installs its own dnsmasq config (independent of Valet) that resolves all *.test domains to 127.0.0.1. A macOS resolver entry at /etc/resolver/test tells the OS to use the local dnsmasq for .test lookups.
If you still have Valet projects, park valet enable creates a low-priority catchall that routes any *.test domain not matched by an explicit link to Valet on port 8080. Explicit park link entries always take priority.
# Shift Valet to port 8080
valet stop && valet port 8080 && valet start
# Enable catchall in proxypark
park valet enableIf your containers expose ports to the host, use park link like any local service:
park link myapp 3000
# → https://myapp.testpark link:docker connects containers directly via Docker's proxy network — no port mapping needed:
park link:docker myapp myapp-container
# → https://myapp.test → myapp-container:80
park link:docker myapp myapp-container:3000
# → https://myapp.test → myapp-container:3000When called without arguments in a directory with a docker-compose.yml, it shows available services:
park link:docker
# SERVICE CONTAINER STATE
# api myapp-api running
# app myapp-app stoppedAfter linking, park link:docker checks your docker-compose.yml and offers to add container_name and the proxy network if missing — so containers stay reachable after docker compose down/up.
Use dot notation for subdomains with both link and link:docker:
park link api.myapp 8001
# → https://api.myapp.test
park link:docker api.myapp api-container:3000
# → https://api.myapp.test → api-container:3000Use park scaffold <name> to generate a boilerplate docker-compose.yml.
If you already have a docker-compose.yml with shared services (Postgres, Redis, etc.), you can add Traefik as a service. Add this to your existing compose file:
services:
traefik:
image: traefik:v3.2
container_name: proxypark
restart: unless-stopped
command:
- --api.insecure=true
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls=true
- --providers.file.directory=/etc/traefik/dynamic
- --providers.file.watch=true
- --log.level=INFO
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- ~/.proxypark/dynamic:/etc/traefik/dynamic:ro
- ~/.proxypark/certs:/etc/traefik/certs:ro
networks:
- proxy
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
proxy:
name: proxy
external: trueThen use park link to route services with web UIs:
park link mailpit 8025 # https://mailpit.test
park link meilisearch 7700 # https://meilisearch.test- Run
park install - Shift Valet to port 8080:
valet stop && valet port 8080 && valet start - Enable Valet catchall:
park valet enable - For each project you want explicit HTTPS:
park link projectname <port>
- Once all projects are migrated:
valet stop && valet uninstall && park valet disable
All runtime data lives in ~/.proxypark/:
~/.proxypark/
├── docker-compose.yml # Traefik container (standalone mode)
├── certs/
│ ├── wildcard.test.pem # Per-domain cert bundle
│ └── wildcard.test-key.pem
└── dynamic/ # Traefik file-provider configs
├── tls.yml # TLS certificate reference
├── valet.yml # Valet catchall (optional)
├── link-myapp.yml # park link
├── link-api-myapp.yml # park link (subdomain)
└── link-docker-myapp.yml # park link:docker
Customize via environment variables:
export PROXYPARK_TLD=dev # Use .dev instead of .test
export PROXYPARK_DATA_DIR=... # Different data pathPort 80 already in use:
lsof -i :80 # Find the culprit
# Common: nginx, apache, ValetDNS not resolving:
dscacheutil -flushcache
dig myapp.test @127.0.0.1 # Should return 127.0.0.1
park status # Full health checkHTTPS certificate not trusted:
park trust # Reinstall CA (system + Firefox via nss)
# Then restart your browserCertificate doesn't cover a domain:
park certs # Regenerate from all current links- macOS (Apple Silicon & Intel)
- Docker Desktop (or OrbStack, Colima, etc.)
- Homebrew
-
park doctor– Automated diagnostics and fixes - Zsh/Bash completions
- Homebrew tap (
brew install McGo/tap/proxypark) -
park serve– Start a simple PHP/Node server and auto-link
MIT — see LICENSE.