Modern applications often start as a single monolithic codebase. As they grow, teams break them into smaller microservices to improve maintainability, scalability, and deployment agility. However, microservices introduce new challenges:
- Proliferation of Endpoints: Each service has its own URL (e.g.,
auth.company.com,users.company.com, etc.), making it hard for clients to know where to send requests. - Security Complexity: You must secure each endpoint individually, issuing certificates, configuring CORS, rate-limiting, authentication plugins, and more.
- Routing Logic: Versioning APIs, path rewriting, canary releases, and traffic splitting require custom code or a homegrown gateway.
Enter Kong, an open-source API gateway that sits in front of your services to:
- Unify Endpoints: Expose a single public URL (e.g.
api.company.com) and route internally to the correct microservice. - Centralize Security & Policies: Apply authentication, rate-limiting, CORS, and logging in one place.
- Enable Declarative Management: Define your services and routes in YAML, version them in Git, and push updates without writing custom proxy code.
Background & Motivation
Our application began as a monolith, one codebase handling authentication, user profiles, memberships, and chat features. As traffic grew and teams expanded, we decided to split the monolith into four independent microservices:
- Auth Service for login, tokens, and user sessions
- Users Service for profile CRUD operations
- Members Service for group/membership logic
- Chats Service for messaging
While microservices unlocked faster deployments and team autonomy, we ran into:
- Client Confusion: Frontend teams had to remember four different base URLs.
- Certificate Sprawl: Managing SSL certificates for each service became a DevOps burden.
- Security Drift: Applying consistent rate-limits, auth checks, and CORS settings across services was error-prone.
Kong solved these pain points by providing:
- Single Entry Point:
api.company.comforwards to the correct service based on path. - Unified Security Layer: Enable plugins (JWT, ACL, rate-limiting) once, for all routes.
- Declarative Config: Store service definitions in Git, sync them automatically with Deck.
In this guide, we'll deploy Kong as an API gateway using Docker Compose, then define our services and routes declaratively with Deck. By the end, you'll have a reusable, containerized API-proxy setup that can route requests to your microservices with zero-downtime updates.
The Kong setup given here is a basic setup without any plugins. It acts as a proxy only. You can extend it in any way you need.
1. Docker Compose Configuration
Create a file named docker-compose.yml at the root of your project:
version: "3.8"
services:
api-gateway-database:
image: postgres:13
container_name: api-gateway-database
restart: always
environment:
POSTGRES_USER: ${API_GW_PG_USER}
POSTGRES_DB: api_gateway_db
POSTGRES_PASSWORD: ${API_GW_PG_PASSWORD}
networks:
- api-network
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${API_GW_PG_USER} -d api_gateway_db" ]
interval: 5s
timeout: 5s
retries: 5
api-gateway-migrations:
image: kong:latest
container_name: api-gateway-migrations
depends_on:
api-gateway-database:
condition: service_healthy
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: ${API_GW_PG_HOST}
KONG_PG_USER: ${API_GW_PG_USER}
KONG_PG_PASSWORD: ${API_GW_PG_PASSWORD}
command: kong migrations bootstrap
networks:
- api-network
restart: no
api-gateway:
build:
context: .
dockerfile: Dockerfile.kong
container_name: api-gateway
restart: always
depends_on:
api-gateway-database:
condition: service_healthy
api-gateway-migrations:
condition: service_completed_successfully
environment:
- KONG_DATABASE=${API_GW_DATABASE}
- KONG_PG_HOST=${API_GW_PG_HOST}
- KONG_PG_USER=${API_GW_PG_USER}
- KONG_PG_PASSWORD=${API_GW_PG_PASSWORD}
- KONG_DB_CACHE_REDIS_PASSWORD=${CACHE_PASSWORD}
- 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_PROXY_LISTEN=0.0.0.0:8000
- KONG_ADMIN_LISTEN=0.0.0.0:8001
- KONG_GUI_LISTEN=0.0.0.0:8002
- KONG_ADMIN_GUI_URL=http://api-gateway-gui:8002
- KONG_ADMIN_GUI_SESSION_CONF={"secret":"YourSecretHere"}
- KONG_PORT_MAPS=8100:8000,8101:8001,8102:8002,3101:3000
ports:
- "8000:8000"
- "8443:8443"
networks:
- api-network
healthcheck:
test: [ "CMD", "curl", "-f", "http://api-gateway:8001" ]
interval: 5s
timeout: 5s
retries: 5
auth-backend-service:
build:
context: ./auth.backend.service
dockerfile: Dockerfile.local
image: auth-backend-001
env_file:
- ${AUTH_ENV_FILE}
container_name: auth-backend-service
environment:
- NODE_ENV=development
- PORT=3000
networks:
- api-network
users-backend-service:
build:
context: ./users.backend.service
dockerfile: Dockerfile.local
image: users-backend-001
env_file:
- ${USERS_ENV_FILE}
container_name: users-backend-service
environment:
- NODE_ENV=development
- PORT=3000
networks:
- api-network
members-backend-service:
build:
context: ./members.backend.service
container_name: members-backend-service
environment:
- NODE_ENV=development
- PORT=3000
networks:
- api-network
chats-backend-service:
build:
context: ./chats.backend.service
container_name: chats-backend-service
environment:
- NODE_ENV=development
- PORT=3000
networks:
- api-network
redis:
image: redis:latest
container_name: redis
restart: always
command: redis-server --requirepass ${CACHE_PASSWORD}
ports:
- "6379:6379"
networks:
- api-network
api-gateway-configurator:
image: kong/deck:latest
container_name: api-gateway-configurator
depends_on:
api-gateway:
condition: service_healthy
api-gateway-database:
condition: service_healthy
api-gateway-migrations:
condition: service_started
redis:
condition: service_started
auth-backend-service:
condition: service_started
users-backend-service:
condition: service_started
members-backend-service:
condition: service_started
chats-backend-service:
condition: service_started
volumes:
- ./kong-config/kong-declarative-config.yml:/config/kong-declarative-config.yml:ro
command: gateway sync /config/kong-declarative-config.yml --kong-addr=http://api-gateway:8001
networks:
- api-network
restart: always
api-gateway-gui-proxy:
image: nginx:latest
container_name: api-gateway-gui-proxy
restart: always
ports:
- "8001:8001"
- "8002:8002"
volumes:
- ./nginx/api-gateway-proxy.conf:/etc/nginx/conf.d/default.conf
- ./nginx/.htpasswd:/etc/nginx/.htpasswd
depends_on:
- api-gateway
networks:
- api-network
networks:
api-network:
driver: bridge
2. Declarative Configuration
Place this file under kong-config/kong-declarative-config.yml. Deck will read it and create/update your Kong services and routes automatically.
_format_version: "3.0"
services:
- name: auth-backend
url: http://auth-backend-service:3000
routes:
- name: auth-route
paths:
- /api/v1/auth
strip_path: false
preserve_host: false
- name: auth-swagger-route
paths:
- /api/v1/auth/docs
strip_path: false
preserve_host: false
- name: users-backend
url: http://users-backend-service:3000
routes:
- name: users-route
paths:
- /api/v1/users
strip_path: false
preserve_host: false
- name: users-swagger-route
paths:
- /api/v1/users/docs
strip_path: false
preserve_host: false
- name: members-backend
url: http://members-backend-service:3000
routes:
- name: members-route
paths:
- /api/v1/members
strip_path: false
preserve_host: false
- name: members-swagger-route
paths:
- /api/v1/members/docs
strip_path: false
preserve_host: false
- name: chats-backend
url: http://chats-backend-service:3000
routes:
- name: chats-route
paths:
- /api/v1/chats
strip_path: false
preserve_host: false
- name: chats-swagger-route
paths:
- /api/v1/chats/docs
strip_path: false
preserve_host: false
3. How It All Fits Together
Orchestration with Docker Compose:
- Brings up PostgreSQL, Kong (with migrations), Redis, your microservices, Deck, and an Nginx proxy, all on a single network (
api-network). - Health checks and
depends_onensure proper startup order.
Declarative API Definitions:
- Each service maps to an internal container URL.
- Routes define URI prefixes, path-stripping behavior, and host preservation.
Configuration Sync via Deck:
The api-gateway-configurator container mounts your YAML and runs:
deck gateway sync /config/kong-declarative-config.yml --kong-addr=http://api-gateway:8001
Any changes to kong-declarative-config.yml are applied on restart, giving you declarative, version-controlled API definitions.
Secure Admin & GUI Access:
api-gateway-gui-proxyuses Nginx (with basic auth) to expose Kong's Admin API and GUI on ports8001and8002without exposing them directly.
To run the docker compose use the following commands:
- Run the following command where the
docker-compose.ymlfile is, in the terminal:
docker-compose up -d
- If there are changes in the microservices code and you want to build the Docker images again, use:
docker-compose up -d --build
Conclusion
By combining Docker Compose and Kong's declarative configuration, you get:
- Rapid, reproducible deployments of your API gateway and microservices.
- Version-controlled, human-readable definitions of services and routes.
- Automated schema migrations and health-checked startup.
- Secure admin access via a reverse proxy.
- Quick test environment setup for local testing without incurring costs for cloud and added overheads for deployment time.
Notes: Kong DB-less Mode for Testing
What is it?
Kong's DB-less (or "off-db") mode allows you to run Kong purely from a declarative config file, no Postgres required. Ideal for quick local tests or CI pipelines.
Key Environment Variables
KONG_DATABASE=off
KONG_DECLARATIVE_CONFIG=/path/to/kong-declarative-config.yml
KONG_PROXY_LISTEN=0.0.0.0:8000
KONG_ADMIN_LISTEN=0.0.0.0:8001
Example Docker Compose Service
kong:
image: kong:latest
environment:
- KONG_DATABASE=off
- KONG_DECLARATIVE_CONFIG=/config/kong-declarative-config.yml
- KONG_PROXY_LISTEN=0.0.0.0:8000
- KONG_ADMIN_LISTEN=0.0.0.0:8001
volumes:
- ./kong-config/kong-declarative-config.yml:/config/kong-declarative-config.yml:ro
ports:
- "8000:8000"
- "8001:8001"
Workflow
- Edit your declarative YAML.
docker-compose up kongwill spin up Kong with routes/services defined in the YAML.- No migrations or database container needed, Kong reads directly from the file.
Bonus Tip: GitHub Workflow to Deploy Kong to Google Cloud Run
name: Production - Deploy Kong Service
on:
push:
branches:
- dev
workflow_dispatch:
env:
ARTIFACTORY_PATH: kong-gateway
IMAGE_NAME: kong-gateway-${{ github.run_number }}
IMAGE_URL: asia-south1-docker.pkg.dev/kovaad/kong-gateway/kong-gateway-${{ github.run_number }}:latest
jobs:
build_tag_push_deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Check Google Cloud authentication
run: |
gcloud auth list
- name: Configure Docker to use the gcloud command-line tool as a credential helper
run: |
gcloud auth configure-docker asia-south1-docker.pkg.dev
- name: Build Docker image
run: |
docker build -t ${{ env.IMAGE_NAME }} -f kong/Dockerfile.prod .
- name: Tag Docker image
run: |
docker tag ${{ env.IMAGE_NAME }} ${{ env.IMAGE_URL }}
- name: Push Docker image
run: |
docker push ${{ env.IMAGE_URL }}
- name: Deploy Kong Production to Google Cloud Run
run: |
gcloud run deploy ${{ env.ARTIFACTORY_PATH }} \
--image ${{ env.IMAGE_URL }} \
--platform managed \
--region asia-south1 \
--allow-unauthenticated \
--cpu=4 \
--memory=8Gi \
--set-env-vars KONG_PROXY_LISTEN=0.0.0.0:8080,KONG_ADMIN_LISTEN=off
This workflow automates your CI/CD pipeline: it builds and pushes a versioned Docker image of Kong, then deploys it to Google Cloud Run with the specified resources and runtime configuration. Feel free to change it according to your environment.
Note: Google Cloud Run only allows one port (8080) to be exposed, hence we set the Kong proxy port to 8080 in the Docker image and workflow.
FROM kong:latest
USER root
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
USER kong
COPY kong/kong-config/prod/kong-declarative-config.yml /config/kong-declarative-config.yml
ENV KONG_DATABASE=off \
KONG_PROXY_ACCESS_LOG=/dev/stdout \
KONG_PROXY_ERROR_LOG=/dev/stderr \
KONG_PROXY_LISTEN=0.0.0.0:8080 \
KONG_DECLARATIVE_CONFIG=/config/kong-declarative-config.yml
EXPOSE 8080
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["kong", "docker-start"]
Feel free to adapt container names, port mappings, and route definitions to match your own architecture. Happy proxying!
Continue the Discussion
If you are designing an API gateway layer and want a practical review of routing, auth, observability, and deployment tradeoffs, book a CTO consultation.
You can also connect with me on LinkedIn for a deeper architecture discussion.