api-gateway (Kong) — Local Dev Setup
Status: populated Owner: TBD (Platform / SRE) Last updated: 2026-04-17 Companion: SERVICE_OVERVIEW · DEPLOYMENT_TOPOLOGY · Service Template
1. Purpose
Run Kong locally in DB-less mode so developers can exercise the edge plugin chain against locally-running upstream services.
2. Prerequisites
- Docker (24+) + Docker Compose v2.
- Local clones of the application monorepo (for upstream services) and optionally
ops/kong/for the decK YAML. deckCLI (optional — for YAML validation):brew install kong/deck/deckor equivalent.curl/httpiefor probing.
3. Compose snippet
Add to the application monorepo's docker-compose.dev.yml (or run standalone):
services:
kong:
image: kong:3.7-alpine
restart: unless-stopped
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /kong/kong.local.yaml
KONG_PROXY_LISTEN: "0.0.0.0:8000"
KONG_ADMIN_LISTEN: "0.0.0.0:8001"
KONG_LOG_LEVEL: info
KONG_PLUGINS: "bundled,ghasi-api-key-lookup" # omit custom plugin if not installed
KONG_LUA_PACKAGE_PATH: "/opt/kong/plugins/?.lua;;"
volumes:
- ../../ops/kong/local.kong.yaml:/kong/kong.local.yaml:ro
- ../../ops/kong/plugins:/opt/kong/plugins:ro
ports:
- "8000:8000" # proxy
- "8001:8001" # admin (local only!)
depends_on:
- redis
- sms-orchestrator
- auth-service
redis:
image: redis:7-alpine
ports: ["6379:6379"]
4. Minimal ops/kong/local.kong.yaml
_format_version: "3.0"
_transform: true
services:
- name: svc-sms-orchestrator
host: sms-orchestrator
port: 3000
protocol: http
routes:
- name: rt-sms-v1-send
methods: [POST]
paths: ["/v1/sms/send"]
strip_path: false
plugins:
- name: key-auth
config:
key_names: [X-Api-Key]
hide_credentials: true
- name: request-size-limiting
config:
allowed_payload_size: 64
- name: correlation-id
config:
header_name: X-Request-Id
generator: uuid
echo_downstream: true
consumers:
- username: csm-dev-local
keyauth_credentials:
- key: dev-local-key-123
plugins:
- name: prometheus
In DB-less mode consumers and credentials are declared in YAML; customer-style dynamic key resolution uses the custom plugin, which you can disable locally by omitting
ghasi-api-key-lookupfromKONG_PLUGINS.
5. Start & smoke
docker compose up -d kong redis sms-orchestrator auth-service
# proxy health
curl -i http://localhost:8000/health
# auth via local dev key
curl -i -X POST http://localhost:8000/v1/sms/send \
-H 'X-Api-Key: dev-local-key-123' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: dev-1' \
-d '{"to":"+15551234567","from":"GHASI","body":"hello"}'
Expected:
202fromsms-orchestratorwith amessageId.- Request shows
X-Request-Idechoed in the response. X-Api-Keyis not visible insms-orchestratorlogs (stripped).
6. Add a route locally
- Edit
ops/kong/local.kong.yaml— add a Service + Route. - Validate:
deck file validate --state ops/kong/local.kong.yaml. - Reload Kong:
docker compose kill -s SIGHUP kong(DB-less honours SIGHUP reload). - Curl the new path.
7. Test an auth plugin change
- JWT: run
auth-servicelocally so Kong can fetch JWKS athttp://auth-service:3000/.well-known/jwks.json. Add ajwtplugin to a route and obtain a token via/v1/auth/login. - key-auth: rotate the
dev-local-key-123and observe 401 from the old key. - rate-limiting: add the plugin with small limits (
5/minute) and curl-loop to exceed — watch429+Retry-After.
8. Test the custom plugin (optional)
If ghasi-api-key-lookup is installed:
- Run
auth-servicewith a seeded API key (gk_test_...). - Enable the plugin on a test route in the local YAML.
- Send a request with
X-Api-Key: gk_test_.... - Inspect Kong logs: cache miss on first request → upstream call → hit on subsequent.
9. Observability locally
- Prometheus scrape:
curl http://localhost:8001/metrics(admin port). - Logs:
docker compose logs -f kong. - Traces: optional — run a local OTel collector (e.g.
otel/opentelemetry-collector-contrib) and pointKONG_OPENTELEMETRY_ENDPOINTat it.
10. Teardown
docker compose down -v
11. Common issues
| Symptom | Cause | Fix |
|---|---|---|
404 from Kong | Path not matched | Check paths, strip_path, hosts |
401 on every request | Auth plugin misconfigured | Verify key_names, consumer credential |
500 with no upstream hit | Plugin init failed | docker compose logs kong; often a bad Lua path |
| Custom plugin not loaded | Missing from KONG_PLUGINS | Add to env var; ensure KONG_LUA_PACKAGE_PATH resolves |
12. Open questions
- Shared compose file across services vs one per service.
- Offer a Tilt / devspace recipe for hot-reload of plugin code.