Zero-dependency, single-binary Docker-native ingress router with automatic TLS.
- π Zero external dependencies β Pure Go stdlib, no external packages
- π Automatic TLS β Let's Encrypt HTTP-01 challenge built-in
- π·οΈ Label-based discovery β Configure routing via Docker labels
- π¦ Single binary β <12MB, runs anywhere
- ποΈ Built-in dashboard β Admin UI on port 9090
- π₯ Hot reload β Routes update instantly when containers start/stop
- π‘οΈ Production-ready β Rate limiting, health checks, circuit breaker, CORS
- π WebSocket support β Transparent WebSocket proxying
- π Prometheus metrics β Built-in metrics endpoint
- βοΈ Load Balancing β Round-robin, weighted, IP hash, least connections
- π Proxy Support β X-Forwarded-For, X-Real-IP, Cloudflare headers
docker run -d \
--name dockrouter \
-p 80:80 -p 443:443 -p 9090:9090 \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v dockrouter-data:/data \
-e DR_ACME_EMAIL=you@example.com \
-e DR_ADMIN_BIND=0.0.0.0 \
-e DR_ADMIN_USER=admin -e DR_ADMIN_PASS=change-me \
dockrouter/dockrouter:latestNote: the admin server binds to
127.0.0.1by default, which inside a container is unreachable from a published port. SetDR_ADMIN_BIND=0.0.0.0to use the dashboard β and setDR_ADMIN_USER/DR_ADMIN_PASSwhen you do.
git clone https://github.com/DockRouter/dockrouter.git
cd dockrouter
docker-compose up -d# Linux/macOS
curl -sL https://github.com/DockRouter/dockrouter/releases/latest/download/dockrouter-$(uname -s)-$(uname -m) -o dockrouter
chmod +x dockrouter
sudo ./dockrouter --docker-socket /var/run/docker.sock
# Or with automatic installation
curl -sL https://raw.githubusercontent.com/DockRouter/dockrouter/main/install.sh | bashAdd labels to your containers:
# docker-compose.yml
services:
api:
image: myapp/api
labels:
dr.enable: "true"
dr.host: "api.example.com"
dr.tls: "auto"# Or with docker run
docker run -d \
--label dr.enable=true \
--label dr.host=api.example.com \
--label dr.tls=auto \
myapp/apiversion: "3.8"
services:
# DockRouter - The ingress router
dockrouter:
image: dockrouter/dockrouter:latest
container_name: dockrouter
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "9090:9090"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- dockrouter-data:/data
environment:
- DR_ACME_EMAIL=admin@example.com
- DR_LOG_LEVEL=info
# Required for the published 9090 port to reach the dashboard
- DR_ADMIN_BIND=0.0.0.0
- DR_ADMIN_USER=admin
- DR_ADMIN_PASS=change-me
labels:
- "com.dockrouter.description=Ingress Router"
# Example API Service
api:
image: nginx:alpine
labels:
dr.enable: "true"
dr.host: "api.example.com"
dr.tls: "auto"
dr.healthcheck.path: "/health"
dr.ratelimit: "100/m"
# Example Web Service
web:
image: nginx:alpine
labels:
dr.enable: "true"
dr.host: "www.example.com"
dr.tls: "auto"
dr.cors.origins: "https://example.com"
dr.compress: "true"
volumes:
dockrouter-data:| Label | Description | Example |
|---|---|---|
dr.enable |
Enable routing for this container | true |
dr.host |
Domain to route to this container | api.example.com |
| Label | Default | Description |
|---|---|---|
dr.port |
Auto-detect | Container port to proxy to |
dr.path |
/ |
Path prefix for routing |
dr.priority |
0 |
Route priority (higher wins) |
dr.address |
Auto | Explicit backend address |
dr.loadbalancer |
roundrobin |
LB strategy: roundrobin, iphash, leastconn, weighted |
dr.weight |
1 |
Backend weight (used with weighted strategy) |
| Label | Default | Description |
|---|---|---|
dr.tls |
auto |
TLS mode: auto, manual, off |
dr.tls.domains |
Same as dr.host |
Additional SAN domains |
dr.tls.cert |
β | Path to manual cert file |
dr.tls.key |
β | Path to manual key file |
| Label | Description |
|---|---|
dr.ratelimit |
Rate limit: 100/m, 10/s, 5000/h |
dr.ratelimit.by |
Rate limit key: client_ip, X-API-Key |
dr.cors.origins |
Allowed CORS origins |
dr.cors.methods |
Allowed CORS methods |
dr.compress |
Enable gzip compression (true) |
dr.auth.basic.users |
Basic auth: user:bcrypt_hash |
dr.ipwhitelist |
Allowed IPs (CIDR) |
dr.ipblacklist |
Blocked IPs (CIDR) |
dr.stripprefix |
Strip path prefix before forwarding |
dr.addprefix |
Add path prefix before forwarding |
dr.maxbody |
Max request body size (e.g., 10mb) |
dr.retry |
Retry count on failure |
dr.circuitbreaker |
Circuit breaker: 5/30s |
| Label | Default | Description |
|---|---|---|
dr.healthcheck.path |
/ |
Health check path |
dr.healthcheck.interval |
10s |
Check interval |
dr.healthcheck.timeout |
5s |
Check timeout |
dr.healthcheck.threshold |
3 |
Failures before the backend leaves rotation |
dr.healthcheck.recovery |
2 |
Consecutive passes before it returns to rotation |
Backends are health checked actively. A backend that starts failing is taken out of rotation and put back automatically once it recovers; the last healthy backend of a route is never ejected, so a partial outage cannot become a total one.
All configuration can be set via environment variables with DR_ prefix:
| Variable | Default | Description |
|---|---|---|
DR_HTTP_PORT |
80 |
HTTP listener port |
DR_HTTPS_PORT |
443 |
HTTPS listener port |
DR_ADMIN_PORT |
9090 |
Admin dashboard port |
DR_ADMIN_BIND |
127.0.0.1 |
Admin bind address |
DR_ADMIN_USER |
β | Admin username |
DR_ADMIN_PASS |
β | Admin password |
DR_ADMIN |
true |
Enable admin interface |
DR_DOCKER_SOCKET |
/var/run/docker.sock |
Docker socket path |
DR_DATA_DIR |
/data |
Data directory |
DR_ACME_EMAIL |
β | ACME account email |
DR_ACME_PROVIDER |
letsencrypt |
ACME provider |
DR_ACME_STAGING |
false |
Use staging server |
DR_LOG_LEVEL |
info |
Log level |
DR_ACCESS_LOG |
true |
Enable access logging |
DR_DEFAULT_TLS |
auto |
Default TLS mode for routes that set no dr.tls |
DR_TRUSTED_IPS |
β | Proxy CIDRs whose forwarded-for headers are trusted |
Same options available as CLI flags:
dockrouter --http-port=8080 --https-port=8443 --acme-email=you@example.com# Show version information
dockrouter version
# Docker healthcheck
dockrouter healthcheck
# Show help
dockrouter --helplabels:
dr.enable: "true"
dr.host: "api.example.com"
dr.tls: "auto"
dr.tls.domains: "api.example.com,www.example.com"Set ACME email:
docker run -e DR_ACME_EMAIL=admin@example.com dockrouter/dockrouterPoint dr.tls.cert and dr.tls.key at the certificate and key files as seen
from inside the DockRouter container. Both labels are required when
dr.tls: "manual".
labels:
dr.enable: "true"
dr.host: "api.example.com"
dr.tls: "manual"
dr.tls.cert: "/certs/api.example.com/cert.pem"
dr.tls.key: "/certs/api.example.com/key.pem"Mount the certificates into DockRouter:
docker run -v /path/to/certs:/certs:ro dockrouter/dockrouterAny extra domains listed in dr.tls.domains are served with the same pair, so a
SAN certificate covers all of them. Manual TLS does not require DR_ACME_EMAIL;
replacing the files on disk takes effect on the next discovery sync.
# Health check
curl http://localhost:9090/health
# Readiness check
curl http://localhost:9090/readycurl http://localhost:9090/metricsAvailable metrics:
| Metric | Type | Description |
|---|---|---|
dockrouter_http_requests_total |
counter | Total requests handled |
dockrouter_http_errors_total |
counter | Responses with status >= 400 |
dockrouter_http_request_duration_seconds |
histogram | Request latency |
dockrouter_http_requests_active |
gauge | Requests in flight |
dockrouter_active_connections |
gauge | Active backend connections |
dockrouter_backend_requests_total |
gauge | Requests forwarded to backends |
dockrouter_backend_errors_total |
gauge | Backend failures |
dockrouter_routes_total |
gauge | Active routes |
dockrouter_containers_total |
gauge | Discovered containers |
dockrouter_certificates_total |
gauge | Loaded certificates |
/metrics and /api/v1/* require admin credentials when DR_ADMIN_USER is set.
/health and /ready are always unauthenticated so container and orchestrator
probes work.
# List routes
curl http://localhost:9090/api/v1/routes
# List containers
curl http://localhost:9090/api/v1/containers
# List certificates
curl http://localhost:9090/api/v1/certificates
# Get status
curl http://localhost:9090/api/v1/status
# Get config
curl http://localhost:9090/api/v1/config
# Get metrics
curl http://localhost:9090/api/v1/metrics
# Stream live route, container and certificate events (Server-Sent Events).
# This is what the dashboard uses to refresh itself without polling.
curl -N http://localhost:9090/api/v1/eventslabels:
dr.ipwhitelist: "192.168.0.0/16,10.0.0.0/8"
dr.ipblacklist: "192.168.100.1/32"Behind Load Balancers: IP filtering reads X-Forwarded-For, X-Real-IP and
CF-Connecting-IP, but only from proxies you explicitly trust β otherwise any
client could spoof its own address. List the balancer's CIDRs:
# Set trusted proxy IPs (via environment variable)
DR_TRUSTED_IPS=10.0.0.0/8,172.16.0.0/12Without DR_TRUSTED_IPS, filtering uses the peer address of the connection.
labels:
dr.cors.origins: "https://example.com,https://app.example.com"
dr.cors.methods: "GET,POST,PUT,DELETE"labels:
dr.ratelimit: "100/m"
dr.ratelimit.by: "client_ip"labels:
dr.circuitbreaker: "5/30s" # Open after 5 failures in 30slabels:
dr.auth.basic.users: "admin:$2a$10$N9qo8uLOickgx2ZMRZoMy..." βββββββββββββββββββββββββββββββββββββββ
β DockRouter Binary β
:80 HTTP βββββββββΆβ Listener β Middleware β Router β
:443 HTTPS βββββββΆβ β ββββΆ Container
β Backend Pool β
:9090 Admin ββββββΆβ Dashboard + REST API β
βββββββββββββββββββββββββββββββββββββββ
β
/var/run/docker.sock
- Go 1.21+
- Docker & Docker Compose
- Make (optional)
# Clone
git clone https://github.com/DockRouter/dockrouter.git
cd dockrouter
# Build
go build -o dockrouter ./cmd/dockrouter
# Or use Make
make build
# Run tests
make test
# Run with coverage
make test-coverage
# Run locally
make run# Build Docker image
docker build -t dockrouter:dev .
# Run locally
docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-p 80:80 -p 443:443 -p 9090:9090 \
dockrouter:dev- All 12 packages pass
go vet, the full test suite, andgo test -race ./... - Behaviour verified end-to-end β
internal/integration/drives a real proxy through the production middleware chain to prove WebSocket upgrades, SSE streaming, replica load balancing, scale-down, retry body replay and backend health recovery actually work, rather than asserting against mocks - Security hardened β CSP and HSTS headers, TLS 1.3, CORS origin checks,
constant-time basic auth, unauthenticated surface limited to
/healthand/ready - Responses are streamed, not buffered β a slow or large upstream response cannot pin memory in the router
dockrouter/
βββ cmd/dockrouter/ # Main application (84.1% coverage)
β βββ main.go # Entry point
β βββ dashboard/ # Admin dashboard
βββ internal/
β βββ admin/ # Admin server (95.7% coverage)
β βββ config/ # Configuration (95.1% coverage)
β βββ discovery/ # Docker discovery (96.6% coverage)
β βββ health/ # Health checking (79.8% coverage)
β βββ log/ # Logging (96.5% coverage)
β βββ metrics/ # Prometheus metrics (96.9% coverage)
β βββ middleware/ # HTTP middleware (89.8% coverage)
β βββ proxy/ # Reverse proxy (96.0% coverage)
β βββ router/ # Route management (95.2% coverage)
β βββ tls/ # TLS/ACME (90.0% coverage)
β βββ integration/ # Cross-package behaviour tests
βββ examples/ # Example configurations
βββ docs/ # Additional documentation
βββ Dockerfile # Multi-stage Docker build
βββ docker-compose.yml # Quick start compose file
βββ Makefile # Build automation
βββ README.md # This file
Overall test coverage: 92.2% (CI gate: 80%), verified with go test -race ./...
See examples/ directory:
| Example | Description |
|---|---|
| basic | Simple HTTP routing |
| tls-auto | Auto TLS with Let's Encrypt |
| multi-app | Multiple applications with path-based routing |
| microservices | Full microservices architecture with middleware |
| websocket | WebSocket proxying with sticky sessions |
| rate-limiting | Rate limiting and circuit breaker examples |
| loadbalancing | Round-robin, weighted, IP hash, least connections |
| Feature | DockRouter | Traefik | Caddy | Nginx |
|---|---|---|---|---|
| Zero dependencies | β | β | β | β |
| Single binary | β | β | β | β |
| Docker-native | β | β | β | β |
| Auto TLS | β | β | β | β |
| Built-in dashboard | β | β | β | β |
| No config files | β | β | β | β |
| Label-based config | β | β | β | β |
| <10MB binary | β | β | β | β |
Contributions are welcome!
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please read CONTRIBUTING.md for details.
MIT License - see LICENSE file.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Built with β€οΈ using Go
- Inspired by Traefik and Nginx Proxy Manager
- Powered by Docker
