The whole API gateway is four files.
g3 puts auth, rate limits, request and response rewrites, retries and hot reload in a 9.6 MB container image. It's one static binary built only on the Go standard library, with no shell, no libc and no third-party code.
g3 runs with Docker or Podman on a computer or server. Open this page there to read the details and download the image.
| /g3 | the gateway | 9.40 MB |
| /etc/ssl/certs/ca-certificates.crt | to verify upstream TLS | 179 KB |
| /etc/passwd | the non-root user | 57 B |
| /etc/group | its group | 17 B |
| image total | 9.6 MB |
Built on an empty base image and run as user 65532. There's nothing else to exec into, and almost nothing for a scanner to flag.
What happens to a request
Every request takes the same path through the gateway. Each stage is set in one JSON file, globally, per service or per route, and a route's setting overrides its service's.
- 1Clean
Dot segments, encoded slashes, control bytes and odd methods are refused before routing.
- 2Match
Paths with parameters such as
/users/{id}, methods and hosts. - 3Filter
IP allow and deny lists, and CORS answered at the edge.
- 4Limit
Rate limits per second, minute and hour, plus body size caps.
- 5Authenticate
JWT, OIDC login, token introspection, API keys or mTLS.
- 6Rewrite
Path, headers and query string, using values from the path.
- 7Upstream
Load balanced, circuit broken, and retried only when that's safe.
- 8Respond
Response headers and top-level JSON fields renamed, removed or added.
Everything a gateway needs, nothing it doesn't
No plugins to install and no database to run. Everything below ships in the one binary.
Identity
- OAuth 2 and OpenID Connect. Works with Keycloak, Auth0, Entra ID, Okta, Google and Dex, with keys from JWKS that rotate without a restart.
- Browser login. Authorization code flow with PKCE, sessions in encrypted
__Host-cookies, automatic token refresh. - Opaque tokens. RFC 7662 introspection with a bounded cache keyed by token hash.
- Per-client API keys. Grouped by consumer, scoped to routes, rotated by config edit. Only hashes are kept in memory.
- Mutual TLS. From its own handshake or from your load balancer's header, with CRLs, SPIFFE IDs and key pinning.
Traffic
- Rewrites both ways. Rename, remove or add headers and query parameters going in; headers and JSON fields coming back.
- Rate limiting. GCRA with burst, per client IP (IPv6 grouped by /64) or global.
- Load balancing. Power of two choices on in-flight requests, with a circuit breaker per target and optional health checks.
- Safe retries. Only when a retry can't cause a duplicate, sent to a different target, within a retry budget.
- Load shedding. Concurrency limits overall and per service, with
Retry-Afteron the way out.
Security
- Modern TLS. TLS 1.2+ with forward-secret AEAD suites, post-quantum X25519MLKEM768 key exchange preferred, and HTTP/2.
- Forging blocked. Identity headers are stripped from every inbound request, and forwarding headers are trusted only from your proxies.
- Brute-force lockout. 20 wrong credentials in a minute locks a client out for a minute.
- Quiet errors. Minimal JSON with a request ID. Upstream
Serverheaders are stripped.
Operations
- Hot reload. Config, certificates, keys and CA files are watched. A bad config is rejected and the running one stays.
- Validate before you deploy.
g3 checkreports every problem in a config at once, without serving traffic. - Observable. Prometheus metrics, health and readiness endpoints, W3C
traceparentpropagation. - Graceful. Drains on SIGTERM, stops stalled clients without cutting off large uploads, and sizes its memory to the container limit.
One JSON file
Unknown fields, missing secrets and conflicting routes are rejected when the config loads, so a typo never reaches traffic.
- Secrets stay out of the file
- Write
${NAME}and the value comes from the environment. An unset variable fails the load instead of becoming an empty key. - This example
- Lets partners with an API key call
/v1/users/42/orders, sends it to/internal/orders?user=42, and removesinternal_notesfrom the reply.
{
"plugins": [
{ "name": "rate-limiting",
"config": { "second": 20, "minute": 600 } }
],
"consumers": [
{ "name": "acme", "groups": ["partners"],
"api_keys": ["${ACME_API_KEY}"] }
],
"services": [{
"name": "orders",
"url": "http://orders.internal:8080",
"plugins": [
{ "name": "auth", "config": { "auth": {
"type": "apikey",
"consumer_groups": ["partners"] } } }
],
"routes": [{
"name": "user-orders",
"paths": ["/v1/users/{id}/orders"],
"methods": ["GET"],
"plugins": [
{ "name": "request-transformer", "config": {
"replace_uri": "/internal/orders",
"add_query": { "user": "{id}" } } },
{ "name": "response-transformer", "config": {
"remove_json": ["internal_notes"] } }
]
}]
}]
}
Run it in a minute
You need Docker or Podman on Linux, macOS or Windows. The image is built for linux/amd64.
-
Load the image
Download the file below, then load it. It's tagged
g3:1.0.0-alpha.4.docker load -i g3-1.0.0-alpha.4-linux-amd64.tar.gz -
Write a config
This is a complete config. It sends every request to a public echo service.
cat > gateway.json <<'EOF' { "services": [{ "name": "echo", "url": "https://postman-echo.com", "routes": [{ "name": "all", "paths": ["/"] }] }] } EOF -
Check it, then run it
Traffic is served on 8080. Health, readiness and metrics are on 9901; keep that port private.
docker run --rm -v "$PWD/gateway.json:/etc/gateway/gateway.json:ro" g3:1.0.0-alpha.4 check docker run --rm --read-only -p 8080:8080 -p 9901:9901 \ -v "$PWD/gateway.json:/etc/gateway/gateway.json:ro" g3:1.0.0-alpha.4 -
Send a request
The echo service replies with what it received, including the headers the gateway added.
curl "localhost:8080/get?hello=world"
Download g3
The container image as a compressed archive for docker load or podman load.
- Version
- 1.0.0-alpha.4
- Platform
- linux/amd64
- Download
- 4.0 MB
- Image
- 9.6 MB
The image is for Docker or Podman on Linux, macOS or Windows. Open this page on that computer to download it.
39b56b56d5e181efa60342161b9a9badeb9b3d16cc4daa718fee2a97ceaad1b0