fabiolb/fabio
Consul Load-Balancing made simple
Healthy across the board
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 2d ago
- ✓11 active contributors
- ✓MIT licensed
Show all 6 evidence items →Show less
- ✓CI configured
- ✓Tests present
- ⚠Concentrated ownership — top contributor handles 72% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/fabiolb/fabio)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/fabiolb/fabio on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: fabiolb/fabio
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/fabiolb/fabio shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 2d ago
- 11 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Concentrated ownership — top contributor handles 72% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live fabiolb/fabio
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/fabiolb/fabio.
What it runs against: a local clone of fabiolb/fabio — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in fabiolb/fabio | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 32 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of fabiolb/fabio. If you don't
# have one yet, run these first:
#
# git clone https://github.com/fabiolb/fabio.git
# cd fabio
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of fabiolb/fabio and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "fabiolb/fabio(\\.git)?\\b" \\
&& ok "origin remote is fabiolb/fabio" \\
|| miss "origin remote is not fabiolb/fabio (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "config/config.go" \\
&& ok "config/config.go" \\
|| miss "missing critical file: config/config.go"
test -f "admin/api/routes.go" \\
&& ok "admin/api/routes.go" \\
|| miss "missing critical file: admin/api/routes.go"
test -f "admin/server.go" \\
&& ok "admin/server.go" \\
|| miss "missing critical file: admin/server.go"
test -f "cert/load.go" \\
&& ok "cert/load.go" \\
|| miss "missing critical file: cert/load.go"
test -f "admin/api/config.go" \\
&& ok "admin/api/config.go" \\
|| miss "missing critical file: admin/api/config.go"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 32 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~2d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/fabiolb/fabio"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Fabio is a fast, zero-config load balancer written in Go that automatically discovers and routes traffic to services registered in Consul. It reads service metadata from Consul's service catalog and dynamically updates routing rules without requiring restart, enabling simple service-to-service load balancing in microservices architectures. Single-binary monolithic application: core routing logic in root directory, admin UI server in admin/ (with REST API in admin/api/, static assets in admin/ui/), authentication in auth/, assertion utilities in assert/. Static HTML/CSS/JS assets bundled at build time in admin/ui/assets (Materialize 1.0.0, jQuery).
👥Who it's for
DevOps engineers and platform teams running services on Consul who need lightweight, dynamic load balancing without complex configuration management. Developers building microservices that rely on Consul service discovery and need transparent traffic distribution across instances.
🌱Maturity & risk
Actively maintained production-ready project with 588KB of Go code, CI/CD via GitHub Actions (build.yml, go-releaser.yml), and regular releases. Minimum Go 1.16+ enforced, with recent dependency updates (Consul API v1.34.2, gRPC v1.80.0). Multiple organizations in production (eBay, eBay Classifieds Group, MyTaxi) indicate proven stability.
Moderate risk from large dependency surface (55+ direct/transitive deps including Consul, gRPC, Prometheus, Vault SDKs) requiring security monitoring. Single primary maintainer with irregular commit frequency visible in structure. Minimal test coverage mentioned in files (only admin/server_test.go visible), suggesting gaps in test suite. Breaking changes noted in CHANGELOG (statsd removed in 1.6.0, GOGC default changed in 1.5.15).
Active areas of work
Active maintenance with Dependabot integration (.github/dependabot.yml) for dependency updates. Prometheus support added natively (replacing statsd in recent versions). gRPC proxy functionality added (mwitkow/grpc-proxy dependency). GitHub Pages documentation deployed via workflows. Recent Go version bump to 1.26.2 in go.mod.
🚀Get running
git clone https://github.com/fabiolb/fabio.git && cd fabio && make build (uses Makefile with Go build targets). Binary will be output to build/ directory. Run with: ./fabio -config=config.cfg (requires Consul connectivity and config file).
Daily commands: make build produces ./fabio binary. Start with: ./fabio or set CONSUL_HTTP_ADDR=localhost:8500 ./fabio -loglevel=info. Access admin UI at http://localhost:9998 (default). Requires running Consul agent for service discovery.
🗺️Map of the codebase
config/config.go— Core configuration loading and parsing; defines how Fabio reads Consul tags, routes, and all runtime settingsadmin/api/routes.go— API handler for route management; central interface for viewing and modifying load-balancer route tableadmin/server.go— Admin UI and API server; entry point for web dashboard and REST API that manages Fabio statecert/load.go— Certificate loading from multiple sources (Consul, Vault, files); critical for TLS terminationadmin/api/config.go— Dynamic configuration API endpoints; allows runtime changes to routes and settings without restartbgp/bgp_nonwindows.go— BGP integration for Consul-aware load-balancing; enables failover and multi-datacenter support
🧩Components & responsibilities
- Config Loader (config/) (Go, Consul API, TOML (magiconair/properties)) — Parses TOML/CLI flags, reads Consul tags, merges into unified Config struct
- Failure mode: Invalid config → startup error; missing Consul → delayed start until available
- Route Cache (admin/api/) (Go map/slice, in-process state) — In-memory route table; updated on Consul changes; served to proxy and admin UI
- Failure mode: Stale routes until next poll (~1–5s); no persistence across restart
- Certificate Manager (cert/) (Vault API, Consul KV, file I/O, X.509) — Loads TLS certs from Vault, Consul, or files; watches for updates; supplies to proxy
- Failure mode: Missing cert → TLS handshake fails; expired cert → client connections drop
- HTTP/HTTPS Proxy (proxy layer, implicit in main) (Go net/http, net.Dialer, TLS) — Core forwarding engine; intercepts client requests, matches against route table, forwards to backend
- Failure mode: Backend unavailable → 502 Bad Gateway; TLS cert missing → TLS alert
- BGP Agent (bgp/) (GoBGP (OSRG/gobgp), socket-level networking) — Announces Fabio's IP to network as a route target; enables multi-datacenter failover
- Failure mode: BGP session down → no route announcement; network cannot find Fabio
- Admin API (admin/api/) (Go net/http, auth middleware) — REST endpoints for querying/modifying routes, config, and viewing metrics
- Failure mode: Admin server crash → UI/API unavailable; no impact on proxy traffic
🔀Data flow
Consul service catalog→Config Loader— Service registrations and tags → route definitions (e.g., 'urlprefix-/api lb=random')Config Loader→Route Cache— Parsed routes stored in memory for O(1) lookup on each requestRoute Cache→HTTP Proxy— undefined
🛠️How to make changes
Add a new Admin API endpoint
- Define handler function in admin/api/*.go (e.g., admin/api/custom.go) (
admin/api/custom.go) - Register route in admin/api/routes.go using route.HandleFunc or route.Handle (
admin/api/routes.go) - Write tests for the handler in admin/api/custom_test.go (
admin/api/custom_test.go)
Add a new certificate source
- Create cert/custom_source.go implementing the Source interface from cert/source.go (
cert/custom_source.go) - Register source in cert/load.go in the source-loading logic (
cert/load.go) - Add config keys in config/config.go to enable/configure the new source (
config/config.go)
Add a new configuration option
- Add field to Config struct in config/config.go (
config/config.go) - Update config/load.go to parse the new option from files or flags (
config/load.go) - Expose via admin/api/config.go if it should be queryable/modifiable at runtime (
admin/api/config.go)
Add authentication to a new endpoint
- Import and call auth.Validate() in your handler in admin/api/routes.go (
admin/api/routes.go) - Check auth.Validate() error and return 401 if auth fails (
auth/auth.go)
🔧Why these technologies
- Consul — Central service registry; Fabio watches Consul for service registrations and tags that define routes, enabling zero-config load-balancing
- Go — Single binary, high concurrency, minimal overhead; ideal for a performance-critical network proxy/load-balancer
- Vault — Secure certificate and secret management; supports PKI role generation for dynamic TLS without manual cert handling
- BGP — Announces Fabio as a layer-3 route target to network infrastructure; enables failover and multi-datacenter topology awareness
- Prometheus metrics — Observability into request counts, latencies, and error rates; standard monitoring integration
⚖️Trade-offs already made
-
Reliance on Consul for service discovery
- Why: Simplifies configuration—services only need tags; no static route files required
- Consequence: Fabio cannot function without Consul; introduces Consul as a critical dependency
-
Dynamic route loading with in-memory cache
- Why: Fast routing decisions at request time; minimal latency overhead
- Consequence: Route changes may take seconds to propagate (polling interval); no real-time consistency guarantees
-
Single admin server (not clustered)
- Why: Simpler deployment; admin UI is read-mostly and can be replicated
- Consequence: Admin endpoint is a single point of contact; requires DNS or load-balancing in front for HA
🚫Non-goals (don't propose these)
- Fabio does not implement layer-7 request routing (no URL path-based rules in core); tag-based routing is service-level
- Not a service mesh; no sidecar integration, no mTLS enforcement between services
- Does not cache request payloads; stateless forwarding only
- No built-in request/response mutation beyond header injection; transformations must be done by upstream services
🪤Traps & gotchas
Requires running Consul agent (not embedded) — CONSUL_HTTP_ADDR and token-based auth configurable but mandatory for discovery. Admin UI listens on separate port (9998 by default) from proxy port (9999) — must open both in firewall. Config file required at runtime; no CLI flag defaults exist for production paths. GOGC garbage collection tuning disabled (reverted to Go default 100 in 1.5.15) — may need manual tuning for high-throughput deployments. TLS certificate validation requires SAN extensions (Go 1.15+ behavior); self-signed certs may be rejected. BGP support (gobgp/v3 dependency) is optional but changes route propagation if enabled.
🏗️Architecture
💡Concepts to learn
- Service Mesh / Load Balancing via Service Discovery — Fabio's entire value proposition — understanding how it translates Consul catalog entries into live routing rules is critical to operating it
- Zero-Config / Self-Healing Infrastructure — Fabio requires no manual route definition; changes are read on-the-fly from Consul, reducing deployment friction — a core design principle visible in routes.go dynamic updates
- Health Check Based Routing — Fabio respects Consul health checks to avoid routing to failing instances; understanding check states is essential for troubleshooting traffic patterns
- Tag-Based Routing (prefix/path matching) — Routes are determined by Consul service tags and URL patterns; the routing DSL (visible in admin/api/routes.go) is Fabio's primary configuration mechanism
- Embedded Static Assets / Binary Bundling — Admin UI assets are baked into the binary at compile-time (admin/ui/static.go) using Go's embed package — enables true zero-config distribution without external files
- BGP Route Propagation — Optional BGP support (osrg/gobgp/v3 dependency) allows Fabio to advertise itself as an anycast endpoint, enabling external load balancing at network layer
- Metrics Instrumentation (Prometheus, Graphite, Circonus) — Fabio exports detailed request/response metrics and latency histograms; understanding the metrics backend choice (native Prometheus vs dogstatsd) is critical for observability
🔗Related repos
hashicorp/consul-template— Alternative Consul integration that generates config files instead of dynamic routing; complementary tool for static routing needsenvoyproxy/envoy— Modern alternative load balancer with richer features (mTLS, observability) but higher complexity; direct competitor in Consul ecosystemtraefik/traefik— Cloud-native load balancer with Consul backend provider; shares zero-config philosophy but targets cloud/container deploymentshashicorp/consul— Primary dependency — service catalog source of truth; Fabio reads from Consul's /v1/catalog APIsprometheus/prometheus— Metrics backend Fabio exports to natively; scrapes /metrics endpoint for observability
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add integration tests for gRPC proxy functionality in mwitkow/grpc-proxy integration
The repo uses github.com/mwitkow/grpc-proxy for gRPC load balancing but there are no visible gRPC-specific test files in the admin/api or main routing logic. Given fabio's core purpose (load balancing), gRPC routing deserves dedicated integration tests to ensure proper stream handling, multiplexing, and failover.
- [ ] Create admin/api/grpc_test.go with tests for gRPC route registration and proto message handling
- [ ] Add test cases for gRPC service discovery via Consul tags
- [ ] Test gRPC stream handling under load balancer failover scenarios
- [ ] Verify compatibility with the existing TCP proxy logic in cert/consul_source.go
Add CI workflow for security vulnerability scanning (Trivy/Snyk) for Docker images
The repo has Dockerfile and Dockerfile-goreleaser but no container image scanning in .github/workflows/. Given fabio's role in production infrastructure, scanning for vulnerabilities in the published Docker images is critical. Currently only build.yml and go-releaser.yml exist.
- [ ] Create .github/workflows/container-scan.yml workflow file
- [ ] Add Trivy scanning step to scan Dockerfile and generated images for CVEs
- [ ] Upload results to GitHub Security tab using sarif format
- [ ] Add step to fail the workflow if critical/high vulnerabilities are found
- [ ] Reference this in README.md under Security section
Add comprehensive unit tests for BGP route announcement (bgp/bgp_nonwindows.go)
The BGP module handles route announcements via github.com/osrg/gobgp/v3 but bgp/bgp_nonwindows_test.go appears minimal. This is critical for users leveraging fabio's BGP integration for external route advertisement. There's test data in bgp/test_data/bgp.toml but insufficient test coverage.
- [ ] Expand bgp/bgp_nonwindows_test.go with tests for route add/remove/update scenarios
- [ ] Add test cases for BGP session establishment and graceful shutdown
- [ ] Test route filtering and redistribution logic based on Consul service tags
- [ ] Add tests for handling malformed bgp.toml configurations (reference bgp/test_data/bgp.toml)
- [ ] Test concurrent route updates to ensure thread safety with the gobgp library
🌿Good first issues
- Add comprehensive unit test coverage for admin/api/config.go and admin/api/manual.go — currently only admin/server_test.go exists; config validation and manual route injection need tests
- Document the Consul tag format and routing rule DSL in a dedicated doc file (admin/routing-rules.md) — no spec visible in file list, only inferred from routes.go
- Add Prometheus metrics exporter example and explain dogstatsd vs statsd_raw in docs/ — CHANGELOG mentions metrics migration in 1.6.0 but users need migration guide
⭐Top contributors
Click to expand
Top contributors
- @tristanmorgan — 72 commits
- @dependabot[bot] — 9 commits
- @dcarbone — 5 commits
- @maciej-lech — 3 commits
- @evkuzin — 3 commits
📝Recent commits
Click to expand
Recent commits
6cdb368— Changelog 1.7.1 (tristanmorgan)7110c84— Version 1.7.1 release. (tristanmorgan)214b934— Merge pull request #1032 from fabiolb/dependabot/go_modules/golang.org/x/net-0.53.0 (tristanmorgan)783b507— Bump golang.org/x/net from 0.52.0 to 0.53.0 (dependabot[bot])1013235— Merge pull request #1033 from fabiolb/dependabot/go_modules/github.com/hashicorp/vault/sdk-0.25.1 (tristanmorgan)41f2d00— Bump github.com/hashicorp/vault/sdk from 0.25.0 to 0.25.1 (dependabot[bot])85c5148— Merge pull request #1031 from fabiolb/dependabot/go_modules/github.com/hashicorp/consul/api-1.34.1 (tristanmorgan)fbe29ec— Bump github.com/hashicorp/consul/api from 1.34.0 to 1.34.2 (dependabot[bot])7b4eec5— Merge pull request #1034 from maciej-lech/feature/ui-base-path (tristanmorgan)850363f— fix: update tests for /health staying at root path (maciej-lech)
🔒Security observations
- High · Outdated Go Version —
go.mod. The go.mod file specifies 'go 1.26.2', which is a future/non-existent version. This appears to be a configuration error. The actual Go version should be verified and pinned to a stable, currently supported release. Using an invalid or extremely new version can lead to unexpected behavior and missing security patches. Fix: Update go.mod to use a stable, currently supported Go version (e.g., go 1.22 or 1.23). Verify the actual Go version used in CI/CD pipelines and local development. - High · Insecure Download of External Binaries in Docker —
Dockerfile (lines 4-9). The Dockerfile downloads Consul and Vault binaries from the internet using HTTP ADD commands without verification of checksums or signatures. An attacker performing MITM attacks could potentially inject malicious binaries. Fix: Verify downloaded binaries using SHA256 checksums or GPG signatures. Use HTTPS where available. Example: Download checksums file, verify with 'sha256sum -c', then use the binary. Reference HashiCorp's official checksum files. - Medium · Missing Hash Verification for Go Dependencies —
go.mod and missing go.sum verification. The go.mod file is present but no go.sum file content is shown. Without go.sum verification, there's a risk of dependency tampering. Additionally, some dependencies have known potential issues or are outdated. Fix: Ensure go.sum is properly maintained and checked into version control. Run 'go mod verify' in CI/CD. Consider using 'go mod tidy' and 'go mod audit' to identify vulnerable dependencies. - Medium · Potential Outdated or Vulnerable Dependencies —
go.mod - multiple dependencies. Several dependencies may have known vulnerabilities or are from older releases: github.com/armon/go-proxyproto (2018), github.com/inetaf/tcpproto (2020), and others. No explicit version pinning strategy is evident beyond semantic versioning. Fix: Run 'go list -json -m all | nancy sleuth' or use 'govulncheck ./...' to identify known vulnerabilities. Update to latest stable versions. Implement automated dependency scanning in CI/CD. - Medium · Scratch Container Without Security Context —
Dockerfile (final stage). The final Docker image is based on 'FROM scratch' with no security restrictions defined (no USER directive, no securityContext). The binary runs as root by default, increasing blast radius if compromised. Fix: Add a non-root USER directive (e.g., 'USER nobody' or create dedicated user). Define resource limits. Consider using distroless or minimal base images instead of scratch for better maintainability. - Medium · Hardcoded Binary Paths in Docker —
Dockerfile (lines 11-14). The Dockerfile hardcodes paths and relies on /usr as the final destination without explicit verification. The setcap command grants network binding capabilities which could be exploited if the binary is compromised. Fix: Use explicit, well-documented paths. Consider using capabilities sparingly and document why cap_net_bind_service is required. Document all security-relevant operations in comments. - Medium · Missing Security Headers and TLS Configuration —
admin/ui/assets/. The admin UI (admin/ui/assets) includes jQuery and Materialize from CDN pinned to old versions (jQuery 3.6.0, Materialize 1.0.0). These may contain known vulnerabilities. No evidence of security headers (HSTS, CSP, X-Frame-Options) in the codebase structure. Fix: Update frontend dependencies to latest stable versions. Implement security headers in admin/server.go. Consider bundling dependencies rather than loading from CDN to avoid supply chain attacks. - Medium · Potential Certificate Management Vulnerabilities —
cert/vault_pki_source.go, cert/consul_source.go. The cert/ package handles Vault PKI, Consul, and file-based certificates. No obvious validation of certificate chains or revocation checking is evident from file names alone. Fix: Review certificate validation logic for proper chain verification, expiration checks, and revocation handling. Implement certificate
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.