Here’s a full mini project folder for Kong that you can copy as-is.
It uses Kong Gateway in DB-less mode, so all config lives in one declarative kong.yml file. That mode is a good fit for CI/CD and Git-managed config, but the Admin API is effectively read-only for config changes in this setup. (Kong Docs)
Folder structure
kong-mini-project/├── app/│ ├── package.json│ └── server.js├── kong/│ └── kong.yml├── .dockerignore├── Dockerfile└── compose.yml
1) app/package.json
{ "name": "kong-mini-project", "version": "1.0.0", "description": "Node app behind Kong Gateway", "main": "server.js", "scripts": { "start": "node server.js" }, "license": "MIT"}
2) app/server.js
const http = require("http");const PORT = process.env.PORT || 3000;const server = http.createServer((req, res) => { if (req.url === "/healthz") { res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } const body = { ok: true, message: "Hello from app behind Kong", method: req.method, url: req.url, host: req.headers.host, time: new Date().toISOString() }; res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(body, null, 2));});server.listen(PORT, () => { console.log(`Server listening on ${PORT}`);});
3) Dockerfile
FROM node:20-alpineWORKDIR /appCOPY app/package.json ./RUN npm install --omit=devCOPY app/server.js ./ENV PORT=3000EXPOSE 3000CMD ["npm", "start"]
4) .dockerignore
node_modulesnpm-debug.log.git.github
5) kong/kong.yml
This is the heart of the project. It defines:
- one upstream Service
- one public Route
- a key-auth plugin
- a rate-limiting plugin
- one Consumer with an API key
Kong’s declarative config format supports entities like Services, Routes, Consumers, and Plugins in DB-less mode. The Key Auth plugin can require API keys, and the Rate Limiting plugin can throttle requests by time window such as per minute. When authentication is present, rate limiting uses the authenticated Consumer identity. (Kong Docs)
_format_version: "3.0"services: - name: app-service url: http://app:3000 routes: - name: app-route paths: - /api protocols: - http - httpsplugins: - name: key-auth service: app-service config: key_names: - apikey - name: rate-limiting service: app-service config: minute: 5 policy: localconsumers: - username: demo-client keyauth_credentials: - key: super-secret-demo-key
A note on policy: local: that works well for a single local node, but Kong notes that plugins needing shared database state do not fully function in DB-less mode, so this is best for learning or single-node setups rather than clustered distributed quotas. (Kong Docs)
6) compose.yml
Kong’s Docker docs support running Kong with Docker Compose, and the read-only Docker Compose guide for DB-less mode uses KONG_DATABASE=off plus KONG_DECLARATIVE_CONFIG pointing to the config file. (Kong Docs)
services: kong: image: kong:3.10 environment: KONG_DATABASE: "off" KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yml KONG_PROXY_ACCESS_LOG: /dev/stdout KONG_ADMIN_ACCESS_LOG: /dev/stdout KONG_PROXY_ERROR_LOG: /dev/stderr KONG_ADMIN_ERROR_LOG: /dev/stderr KONG_ADMIN_LISTEN: 0.0.0.0:8001 ports: - "8000:8000" # public proxy - "8001:8001" # admin api (read-only for config in DB-less mode) volumes: - ./kong/kong.yml:/kong/declarative/kong.yml:ro app: build: context: . dockerfile: Dockerfile
7) Run it
docker compose up -d --build
Then test it.
Without an API key, access should fail because the route is protected by the Key Auth plugin. (Kong Docs)
curl -i http://localhost:8000/api
With the API key in a header, it should succeed. Kong’s Key Auth plugin supports reading keys from headers, query parameters, or request body, depending on config. (Kong Docs)
curl -i \ -H "apikey: super-secret-demo-key" \ http://localhost:8000/api
You can also use a query string:
curl -i "http://localhost:8000/api?apikey=super-secret-demo-key"
8) Test rate limiting
The plugin is set to 5 requests per minute, so the sixth quick request should return 429. Kong’s rate-limiting plugin supports time windows including seconds, minutes, hours, days, months, and years. (Kong Docs)
for i in {1..6}; do curl -s -o /dev/null -w "%{http_code}\n" \ -H "apikey: super-secret-demo-key" \ http://localhost:8000/apidone
9) Useful checks
See running containers:
docker compose ps
Follow Kong logs:
docker compose logs -f kong
Follow app logs:
docker compose logs -f app
Read the service list from the Admin API:
curl http://localhost:8001/services
In DB-less mode, that Admin API is useful for inspection, but Kong’s docs say you cannot use it for normal write-based configuration management because the declarative file is the source of truth. (Kong Docs)
10) What makes this different from Traefik
With Traefik, the main workflow was “discover containers and route traffic to them.” With Kong, the model is “define Services and Routes, then attach policy plugins like auth and rate limiting.” Kong’s docs emphasize entities such as Services, Routes, Consumers, Upstreams, and Plugins as the core gateway model. (Kong Docs)
So in practice:
- Traefik is great for app routing and reverse proxying.
- Kong is better when you want API-specific control like identity, quotas, and policy.
11) Resume line
Built a containerized API behind Kong Gateway in DB-less mode using declarative configuration, API key authentication, and per-consumer rate limiting.
12) Best next upgrade
The strongest next step is to add JWT auth or request transformation, because those show off Kong as an API gateway rather than just a reverse proxy. Kong’s plugin ecosystem is one of its main strengths. (Kong Docs)