Skip to main content

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.
  • deck CLI (optional — for YAML validation): brew install kong/deck/deck or equivalent.
  • curl / httpie for 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-lookup from KONG_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:

  • 202 from sms-orchestrator with a messageId.
  • Request shows X-Request-Id echoed in the response.
  • X-Api-Key is not visible in sms-orchestrator logs (stripped).

6. Add a route locally

  1. Edit ops/kong/local.kong.yaml — add a Service + Route.
  2. Validate: deck file validate --state ops/kong/local.kong.yaml.
  3. Reload Kong: docker compose kill -s SIGHUP kong (DB-less honours SIGHUP reload).
  4. Curl the new path.

7. Test an auth plugin change

  • JWT: run auth-service locally so Kong can fetch JWKS at http://auth-service:3000/.well-known/jwks.json. Add a jwt plugin to a route and obtain a token via /v1/auth/login.
  • key-auth: rotate the dev-local-key-123 and observe 401 from the old key.
  • rate-limiting: add the plugin with small limits (5/minute) and curl-loop to exceed — watch 429 + Retry-After.

8. Test the custom plugin (optional)

If ghasi-api-key-lookup is installed:

  1. Run auth-service with a seeded API key (gk_test_...).
  2. Enable the plugin on a test route in the local YAML.
  3. Send a request with X-Api-Key: gk_test_....
  4. 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 point KONG_OPENTELEMETRY_ENDPOINT at it.

10. Teardown

docker compose down -v

11. Common issues

SymptomCauseFix
404 from KongPath not matchedCheck paths, strip_path, hosts
401 on every requestAuth plugin misconfiguredVerify key_names, consumer credential
500 with no upstream hitPlugin init faileddocker compose logs kong; often a bad Lua path
Custom plugin not loadedMissing from KONG_PLUGINSAdd 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.