Native Go, standard-library-first¶
HOG v1 was a fork of KrakenD, built on KrakenD's core framework, Lura. HOG v2
is a clean-room rewrite: no v1 code was reused, and the dependency on Lura was
dropped. v2 is built directly on Go 1.26 and net/http.
Why not a framework¶
The traditional case for adopting a framework is that it owns the security-sensitive plumbing — routing, traversal-safe file serving, CSRF protection, cookie crypto — so an application doesn't have to get it right itself. Recent Go releases moved most of that plumbing into the audited standard library, which removes the main reason to pay for a framework:
| HOG job | Standard-library capability |
|---|---|
| Routing | http.ServeMux method-and-pattern routing, r.PathValue() |
| Traversal-safe static serving | os.Root + http.FileServerFS, embed.FS |
| Reverse proxying | httputil.ReverseProxy with Rewrite |
| CSRF protection | http.CrossOriginProtection (token-less, Fetch-metadata based) |
| Session encryption | crypto/aes with GCM |
| Structured logging | log/slog |
| Container-aware scheduling | GOMAXPROCS that reads cgroup limits |
Where the standard library doesn't reach, HOG adds a small, deliberate set of
dependencies: coreos/go-oidc and golang-jwt for OIDC and JWT handling, the
embedded Open Policy Agent engine for Rego-based authorization, and the
OpenTelemetry Go SDK for tracing and metrics. Each is scoped to the package
that needs it rather than pulled into a shared core.
Alternatives considered¶
Three paths were evaluated before settling on native Go:
- Lura (KrakenD's core) was rejected because it's shaped for API
composition rather than serving a web application, pulls in a web framework
(gin) HOG doesn't need, and relies on Go's
plugin/.somechanism for extensibility — the same fragile, version-locked distribution model v2 exists to move away from. Building on Lura would have meant keeping most of what v1's fork left behind. - Caddy was a strong functional fit, but adopting it as a module host would make HOG a Caddy distribution — tied to Caddy's release cadence, dependency weight, and module conventions, with less control over the request path than owning it directly.
- Traefik extends through interpreted or WASM plugins rather than
compiled Go, so it can't be embedded as a Go framework the way HOG's
import-and-call-
Main()mode requires.
The trade-off¶
Going native means HOG owns and maintains more of its own machinery — the middleware chain, the module registry, the configuration loader — instead of inheriting it from a framework. In exchange, every request path is transparent and auditable in HOG's own code, there's no external framework release cadence to track or absorb breaking changes from, and the dependency graph stays small and legible. This is a genuine cost: features a framework would have provided for free (session middleware, plugin hosting, templating) are built and tested in-house. HOG accepts that cost to keep the binary lean and its behavior fully owned.
Compile-time composition, not dynamic plugins¶
Every feature in HOG — including HOG's own first-party features like static
serving, the reverse proxy, and authorization — is a module: a Go package
that registers a factory into a shared registry, keyed by kind and name, at
init(). Configuration selects which modules to instantiate and how.
Composition happens once, at build time; nothing is loaded at runtime.
This rules out Go's plugin/.so loading mechanism, which requires an exact
toolchain and dependency match between the host binary and the plugin,
degrades in practice across Go versions, and imposes per-call indirection.
Compile-time composition instead gives every plugin the full Go language,
pins exact dependency versions in go.sum, and adds zero per-request
overhead — the trade-off is that adding or changing a plugin requires a
rebuild rather than a hot-swap.
One binary, two ways to build it¶
The same compile-time model produces a single, statically linked binary,
distributed two ways: import HOG as a Go framework and call hog.Main(), or
declare plugins in a Gateway resource's manifest and let a build tool
compose the binary for you. Both converge on the same artifact — see
delivery for how the manifest drives that build, and
developer: building a custom binary for
the how-to.