Skip to main content

Self-hosting an enterprise API gateway with Docker Compose: the complete production guide

A complete guide to self-hosting an enterprise API gateway on Docker Compose: stack setup, TLS, env configuration, and production hardening for regulated teams.

  • deployment
  • architecture
  • docker
  • on-premises
  • enterprise
Zerq team

A self-hosted API gateway Docker Compose deployment is the model that regulated industries keep returning to. Not because it is the most exotic option, but because it is the most controllable one. Every piece of infrastructure sits in your environment, every request is processed on your servers, and your audit data never touches a vendor's cloud. This guide walks through deploying the full Zerq stack on Docker Compose: seven services, no external dependencies, production-ready from the first up -d.

The case for keeping your API gateway entirely on-premises starts with what an API gateway actually does. It processes every authenticated request from every application, partner, and AI agent that calls your APIs. In a cloud-managed gateway, that processing happens on the vendor's infrastructure. For organizations operating under HIPAA, PCI-DSS, GDPR data residency requirements, or national security classifications, that is not a tradeoff most compliance teams will accept.

The alternative is a genuine on-premises deployment where the gateway binary, the config store, the rate limit state, and the audit trail all live in your environment, on hardware you control, with no runtime outbound dependency to any vendor system.

Why most "self-hosted" options still depend on the vendor

Several established API gateways offer self-hosted deployment modes. The friction point is that "self-hosted" does not always mean what it sounds like.

Kong's full enterprise feature set (including RBAC, audit logging, and advanced plugins) requires the Konnect control plane, which runs on Kong's cloud infrastructure. The self-hosted data plane handles traffic, but configuration and management flow through Konnect. For teams operating in air-gapped or data-residency-constrained environments, this creates a compliance problem: your management operations and configuration state depend on an external service you do not control.

Apigee is a Google Cloud product. Even when the runtime proxies are deployed on-premises via Apigee hybrid, the management plane (where you configure products, proxies, and developer access) runs in Google Cloud. AWS API Gateway is a regional AWS service with no self-hosted option. MuleSoft requires the Anypoint Platform for management. Azure API Management supports a self-hosted gateway agent, but the management plane remains on Azure. These are honest product architectures, not deceptions. They reflect what those vendors optimized for. But for a team that needs to answer "where does my configuration data live?" with "in our own MongoDB instance," those architectures do not clear the bar.

How Zerq solves it: the architecture

Zerq's stack is seven services, all running in your environment, coordinated by Docker Compose. There is no Zerq control plane, no license check endpoint, no telemetry agent calling home. The gateway binary is a compiled Go binary with no JVM and no proprietary runtime; startup is fast. MongoDB stores all configuration and audit data locally. KeyDB handles rate limit state and response caching. Keycloak provides OIDC identity for management authentication. Nginx Proxy Manager handles TLS termination and reverse proxy routing at the edge.

The architecture covers five distinct surfaces: the management platform where operators define API products, proxies, clients, and policies; the gateway runtime that processes live API traffic; the developer portal where API consumers discover and obtain credentials; the MCP surface for AI client connectivity; and the observability surface (request logs, audit trails, and metrics) stored entirely in your MongoDB instance. For a complete architecture walkthrough, see the Zerq architecture overview. Nothing in this stack makes outbound calls to Zerq infrastructure during normal operation. Once the images are pulled, the gateway runs indefinitely without any network path to the vendor.

Step-by-step deployment guide

Step 1: prerequisites

You need:

  • A Linux host with Docker and Docker Compose installed (Docker Engine 24+ recommended)
  • A domain name with DNS control; you will create four subdomains
  • Ports 80, 443, and 81 reachable from the internet or your internal network for Nginx Proxy Manager

For production, size your host with at least 4 vCPUs and 8 GB RAM. MongoDB and the Go backend are not memory-hungry, but headroom matters under traffic load.

Step 2: configure your environment file

Create a .env file in the same directory as the compose file. These are the variables that matter for production:

# === Database ===
MONGODB_URI=mongodb://admin:strongpassword@mongodb:27017
DB_NAME=api_gateway
DB_MAX_POOL_SIZE=100
DB_MIN_POOL_SIZE=10
DB_RETRY_WRITES=true

# === Security: generate strong random values ===
JWT_SECRET=<generate-with-openssl-rand-hex-32>
ENCRYPTION_KEY=<generate-with-openssl-rand-hex-32>

# === Keycloak ===
KC_HOSTNAME_URL=https://identity.yourdomain.com
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=<strong-password>
KEYCLOAK_DB_PASSWORD=<strong-password>

# === Backend public URLs: replace with your actual domain ===
BACKEND_PUBLIC_URL=https://api.yourdomain.com
MCP_GATEWAY_BASE_URL=https://api.yourdomain.com
OIDC_ISSUER_URL=https://identity.yourdomain.com/realms/api-platform

# === Authentication methods ===
AUTH_METHODS_ENABLED=token,jwt,oidc,mtls

# === Audit ===
AUDIT_ENABLED=true

# === Rate limiting ===
GATEWAY_ENABLE_LIMITER=true
GATEWAY_LIMITER_MAX=100
GATEWAY_LIMITER_EXPIRATION=1m

# === Token expiry ===
TOKEN_EXPIRY_DEFAULT=720h
JWT_EXPIRY_DEFAULT=24h

# === Log level ===
LOG_LEVEL=INFO

# === RBAC role mappings ===
OIDC_VIEWER_ROLES=viewer,editor,admin
OIDC_MODIFIER_ROLES=editor,admin
OIDC_AUDITOR_ROLES=auditor,admin
OIDC_ADMIN_ROLES=admin

Generate JWT_SECRET and ENCRYPTION_KEY with a command like openssl rand -hex 32. These must be set before the first startup; they protect credential storage and token signing. Set BACKEND_PUBLIC_URL and MCP_GATEWAY_BASE_URL to your actual API domain. The backend uses these values when constructing callback URLs and MCP discovery responses, so they must match the public URL your clients will reach.

Step 3: pull and start the stack

docker compose -f docker-compose.platform.images.yml pull
docker compose -f docker-compose.platform.images.yml up -d

The pull step downloads all images from Docker Hub. If you are deploying into an air-gapped environment, mirror the images to your internal registry first and set DOCKER_IMAGE_BASE to point at it before running this command.

The up -d starts all seven services. The backend waits for MongoDB, KeyDB, and Keycloak to report healthy before it starts. That dependency chain is defined in the compose file with condition: service_healthy.

Step 4: verify services are healthy

docker compose -f docker-compose.platform.images.yml ps
docker compose -f docker-compose.platform.images.yml logs backend

The three infrastructure services run health checks on startup:

  • MongoDB: mongosh --eval "db.adminCommand('ping')" confirms the database is accepting connections
  • Keycloak DB (PostgreSQL): pg_isready confirms the Postgres instance is ready
  • KeyDB: keydb-cli ping confirms the cache is reachable

Watch the backend logs. A healthy startup shows the backend connecting to MongoDB and KeyDB, loading its configuration, and beginning to accept requests on port 8080. If you see connection errors, check that the service names in MONGODB_URI and REDIS_URL match the compose service names (mongodb and keydb).

Step 5: configure Nginx Proxy Manager

Open http://your-host:81 to access the NPM admin interface. The default credentials are [email protected] / changeme; change these immediately on first login.

Add proxy hosts for each service. Services communicate over Docker's internal network using service names as hostnames — you do not need IP addresses:

Public domainBackend servicePort
api.yourdomain.combackend8080
platform.yourdomain.comfrontend3000
devportal.yourdomain.comdeveloper-portal3001
identity.yourdomain.comkeycloak8080

For each proxy host, enable SSL and use Let's Encrypt for certificate issuance. NPM handles automatic renewal. If you are deploying in an environment without public DNS, use your internal CA and configure NPM with the certificate files directly.

Enable "Websockets Support" on the platform.yourdomain.com proxy host. The management UI uses WebSocket connections for real-time updates.

Step 6: initialize Keycloak

Navigate to https://identity.yourdomain.com and sign in with the admin credentials you set in .env.

  1. Create a new realm named api-platform. This name must match the realm path in OIDC_ISSUER_URL. If you use a different name, update the env var accordingly.

  2. Create an OIDC client named api-gateway-management. Set the redirect URI to https://platform.yourdomain.com/api/auth/callback/keycloak.

  3. Create the four management roles in the realm: viewer, editor, admin, and auditor. These role names must match what you configured in the OIDC_*_ROLES env vars.

Each role grants a specific level of management access:

RolePermissions
viewerRead-only access to all resources: collections, proxies, clients, policies, request logs
editorCreate and modify API products, proxies, clients, credentials, and workflow configurations
adminFull platform access including privileged and emergency operations
auditorRead-only access to request logs and audit trails only; no configuration visibility

The auditor role is the one your compliance team gets. They can query every request log and audit trail entry in the system, but they cannot touch gateway configuration. For more on how Zerq structures observability and audit data, see the observability overview.

  1. Create management users and assign them roles. For teams already running an existing IdP, Keycloak can federate to it via LDAP or OIDC. Configure federation under "User Federation" or "Identity Providers" in the realm settings.

Step 7: verify the deployment

Check the gateway health endpoint:

curl -i "https://api.yourdomain.com/healthz"

A healthy gateway returns 200 OK. If you get a connection error, check the Nginx Proxy Manager routing for api.yourdomain.com and confirm the backend container is running.

Sign in to the management UI at https://platform.yourdomain.com with a Keycloak user. You should land on the dashboard. From there, create your first API collection, add a proxy, and issue a test client credential to confirm the full flow is working end to end.

Production hardening

Before you put real traffic through the gateway, review these configuration decisions.

External MongoDB. The compose file includes a local MongoDB service, which is fine for getting started. In production, point MONGODB_URI at a managed MongoDB instance: Atlas running in your own cloud account, a self-managed replica set, or a cloud provider's managed Mongo service. Set DB_MAX_POOL_SIZE=100, DB_MIN_POOL_SIZE=10, and DB_RETRY_WRITES=true. The pool settings match the default config and handle concurrent request bursts without connection contention.

Encryption key. Set ENCRYPTION_KEY to a randomly generated 32-byte hex value before first startup. This key protects sensitive stored values. If you start without it and add it later, you will need to re-encrypt existing data. Generate it before the stack starts, not after.

Rate limiting. The global rate limiter (GATEWAY_ENABLE_LIMITER=true, GATEWAY_LIMITER_MAX=100, GATEWAY_LIMITER_EXPIRATION=1m) applies to all gateway traffic as a circuit breaker. For fine-grained per-client rate limiting and quotas, configure Policy objects on each client profile. Rate limit policies enforce a maximum number of requests over 1m, 5m, or 1h windows. Quota policies enforce daily (1d), weekly (7d), or monthly (30d) caps. Clients that exceed their limit receive a 429 response with {"error": "rate_limit_exceeded"}. For more on authentication methods and access policies, see the Zerq security documentation.

Idempotency for payment APIs. If your upstream services process payments or other non-idempotent operations, set GATEWAY_ENABLE_IDEMPOTENCY=true. This enables idempotency key tracking via KeyDB; duplicate requests with the same idempotency key return the cached response without re-processing.

Log level. Keep LOG_LEVEL=INFO in production. DEBUG logs every request detail including headers and significantly increases log volume. Use INFO unless you are actively diagnosing a problem.

Auth methods. AUTH_METHODS_ENABLED=token,jwt,oidc,mtls enables all four authentication methods. If your organization only uses one or two, restrict this list to exactly what you need. Fewer enabled methods means a smaller auth surface. For B2B integrations where partners have client certificates, mtls is the strongest option: clients only need X-Client-ID and X-Profile-ID headers, and the certificate does the authentication.

What this looks like in practice

A regional healthcare organization deploying FHIR APIs is a typical example. Before Zerq, they were running a cloud-managed gateway where audit logs lived on the vendor's infrastructure. A HIPAA business associate agreement existed, but the compliance team had no direct access to query audit records. Generating evidence for an OCR audit required a support ticket to the vendor and took days.

After migrating to Zerq on Docker Compose, MONGODB_URI points to their managed MongoDB Atlas cluster running in their own Azure tenant. All request logs, audit trail entries, and configuration history are in that instance. The compliance team's Keycloak user carries the auditor role; they can query every API request from the past 12 months directly from the management UI, filtered by endpoint, client, or time range. No vendor involvement, no support tickets. When regulators ask for access logs, the answer is a database query that returns in seconds.

Keycloak in this deployment is federated to their existing Azure AD instance. Management sign-in uses the organization's existing SSO credentials. Access revocation is automatic: remove someone from the Azure AD group and their gateway management access disappears on the next token refresh. For other industries with similar compliance requirements, see how regulated industries use Zerq.

The bottom line

Zerq on Docker Compose gives you a complete enterprise API gateway (API management, AI agent access control, compliance audit trail, and developer portal) running entirely in your own infrastructure. Every configuration change, every request log, every client credential sits in your MongoDB instance. The compliance team has a dedicated auditor role with direct log access. Nothing in the stack calls home to Zerq infrastructure once the images are pulled.

For regulated teams who have accepted the operational overhead of self-hosting in exchange for complete data control, this is the architecture that delivers on that trade.


Zerq is an enterprise API gateway built for regulated industries — one platform for API management, AI agent access, compliance audit, and developer portal, running entirely in your own infrastructure. See how it works or request a demo to walk through your specific requirements.