Run the Full Stack Locally with Docker Compose
Run the complete STOA Platform stack on your local machine using Docker Compose. This tutorial shows you how to spin up the Control Plane API, Console UI, Developer Portal, MCP Gateway, Keycloak, and PostgreSQL β all configured and ready to use in under 10 minutes.
What You'll Runβ
STOA Platform is designed as a cloud-native, Kubernetes-first API management solution. But for local development, testing, and learning, you can run the entire stack using Docker Compose without needing a Kubernetes cluster.
The local stack includes:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Developer Portal (React) :8080 β
β Console UI (React) :3000 β
β Control Plane API (FastAPI) :8000 β
β MCP Gateway (Rust) :8080 β
β Keycloak (Auth) :8443 β
β PostgreSQL (DB) :5432 β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Architecture overview:
- Control Plane API β manages APIs, policies, applications, and gateway configuration
- Console UI β admin interface for platform operators (RBAC-enabled)
- Developer Portal β self-service portal for API consumers to discover and subscribe to APIs
- MCP Gateway β Rust-based gateway that proxies API requests and exposes MCP tools for AI agents
- Keycloak β OpenID Connect provider for authentication (pre-configured with 4 RBAC roles)
- PostgreSQL β database for Control Plane API and Keycloak
This is a fully functional environment. You can create APIs, configure rate limiting, test MCP tools with Claude Desktop, and experiment with GitOps workflows β all without deploying to a cloud provider.
For a broader overview of STOA's architecture and design principles, see our Open Source API Gateway Guide.
Prerequisitesβ
Before you begin, ensure you have:
- Docker 20.10+ installed (Get Docker)
- Docker Compose v2.0+ (included with Docker Desktop, or install via
docker composeplugin) - 8GB RAM minimum (12GB recommended for smooth operation)
- 10GB free disk space (for images and volumes)
- Git to clone the repository
Verify your setup:
docker --version
# Docker version 24.0.0 or higher
docker compose version
# Docker Compose version v2.20.0 or higher
Note: This guide uses docker compose (v2 syntax, space not hyphen). If you're on an older Docker installation, replace docker compose with docker-compose throughout.
Step 1: Clone & Configureβ
Clone the STOA Quickstart repository:
git clone https://github.com/stoa-platform/stoa-quickstart.git
cd stoa-quickstart
The repository includes:
docker-compose.ymlβ service definitions for all components.env.exampleβ configuration templatekeycloak/realm-export.jsonβ pre-configured STOA realm with 4 roles and test usersinit-scripts/β database initialization scripts
Copy the environment template:
cp .env.example .env
Open .env and review the settings. For local development, the defaults work out of the box:
# Control Plane API
API_BASE_URL=http://localhost:8000
DATABASE_URL=postgresql://stoa:stoa@postgres:5432/stoa
# Keycloak
KEYCLOAK_BASE_URL=https://localhost:8443
KEYCLOAK_REALM=stoa
KEYCLOAK_CLIENT_ID=control-plane-api
KEYCLOAK_CLIENT_SECRET=your-secret-here
# Gateway
GATEWAY_PORT=8080
STOA_CONTROL_PLANE_API_KEY=demo-gateway-key
# Console UI
VITE_API_URL=http://localhost:8000
VITE_KEYCLOAK_URL=https://localhost:8443
VITE_KEYCLOAK_REALM=stoa
VITE_KEYCLOAK_CLIENT_ID=control-plane-ui
# Portal
VITE_PORTAL_API_URL=http://localhost:8000
VITE_PORTAL_KEYCLOAK_CLIENT_ID=stoa-portal
Important: The KEYCLOAK_CLIENT_SECRET is auto-generated during Keycloak startup. Leave it as-is for the first run β the init script will populate it.
Step 2: Start the Stackβ
Start all services with a single command:
docker compose up -d
Docker Compose will:
- Pull images for all services (~2GB total, first run only)
- Create a Docker network (
stoa-network) - Start PostgreSQL and wait for it to become healthy
- Start Keycloak and run the realm import
- Start the Control Plane API, Console UI, Portal, and Gateway
Monitor the startup logs:
docker compose logs -f
Startup time: 2-3 minutes. You'll see:
- PostgreSQL:
database system is ready to accept connections - Keycloak:
Keycloak 23.0.0 started in XXXXms - Control Plane API:
Uvicorn running on http://0.0.0.0:8000 - Gateway:
stoa-gateway listening on 0.0.0.0:8080
Press Ctrl+C to stop following logs (services continue running).
Verify all services are up:
docker compose ps
Expected output:
NAME STATUS PORTS
stoa-control-plane-api Up 0.0.0.0:8000->8000/tcp
stoa-control-plane-ui Up 0.0.0.0:3000->3000/tcp
stoa-portal Up 0.0.0.0:8080->8080/tcp
stoa-gateway Up 0.0.0.0:8081->8080/tcp
keycloak Up 0.0.0.0:8443->8443/tcp
postgres Up 0.0.0.0:5432->5432/tcp
Port mapping note: The Gateway is mapped to 8081 on the host (not 8080) to avoid conflicts with the Portal. Inside the Docker network, it listens on 8080.
Step 3: Access the Servicesβ
All services are now accessible on your local machine.
Console UI (Admin Interface)β
Pre-configured users (from Keycloak realm export):
| Username | Password | Role | Permissions |
|---|---|---|---|
cpi-admin | admin | CPI Admin | Full platform access (stoa:admin) |
tenant-admin | admin | Tenant Admin | Own tenant only (stoa:write, stoa:read) |
devops | admin | DevOps | Deploy/promote (stoa:write, stoa:read) |
viewer | admin | Viewer | Read-only (stoa:read) |
Log in as cpi-admin / admin to access all features.
Developer Portal (Self-Service)β
The Portal is where API consumers discover available APIs, view documentation, create applications, and generate API keys. No login required for browsing; authentication required for subscriptions.
Control Plane API (REST API)β
Base URL: http://localhost:8000
Interactive docs:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
Health check:
curl http://localhost:8000/health
# {"status":"healthy","version":"0.1.0"}
MCP Gatewayβ
Base URL: http://localhost:8081
Health check:
curl http://localhost:8081/health
# {"status":"healthy","mode":"edge-mcp"}
The Gateway runs in edge-mcp mode by default, which means it's optimized for AI agent integrations via the MCP protocol. For more on MCP Gateway setup, see our MCP Gateway Quickstart.
Keycloak (Auth Provider)β
Admin Console: https://localhost:8443/admin
Credentials: admin / admin (pre-configured in realm export)
The STOA realm is pre-imported with:
- 4 roles:
cpi-admin,tenant-admin,devops,viewer - 4 test users (passwords all
admin) - 3 clients:
control-plane-api,control-plane-ui,stoa-portal - Scopes:
stoa:read,stoa:write,stoa:admin
Note: Keycloak uses a self-signed certificate for HTTPS. Your browser will show a security warning. Click "Advanced" and proceed anyway for local development.
PostgreSQLβ
Connection string:
postgresql://stoa:stoa@localhost:5432/stoa
Connect with psql:
docker compose exec postgres psql -U stoa -d stoa
Step 4: Create Your First APIβ
Let's create an API using the Console UI and test it through the Gateway.
Via Console UI (Recommended)β
- Open http://localhost:3000
- Log in as
cpi-admin/admin - Navigate to APIs β Create API
- Fill in the form:
- Name:
httpbin-test - Display Name: HTTPBin Test API
- Description: Test API using httpbin.org
- Base Path:
/httpbin - Backend URL:
https://httpbin.org - Gateway Instance:
local-gateway(auto-created on startup)
- Name:
- Click Create
The API is now synced to the Gateway. Test it:
curl http://localhost:8081/httpbin/get
You should see a JSON response from httpbin.org, proxied through the STOA Gateway.
Via REST APIβ
If you prefer the command line, use the Control Plane API directly:
# Get an access token
ACCESS_TOKEN=$(curl -s -X POST "https://localhost:8443/realms/stoa/protocol/openid-connect/token" \
--insecure \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=cpi-admin" \
-d "password=admin" \
-d "grant_type=password" \
-d "client_id=control-plane-api" \
| jq -r '.access_token')
# Create the API
curl -X POST "http://localhost:8000/v1/apis" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "httpbin-test",
"display_name": "HTTPBin Test API",
"description": "Test API using httpbin.org",
"base_path": "/httpbin",
"backend_url": "https://httpbin.org",
"gateway_instance_id": "local-gateway"
}'
For a more detailed walkthrough of API creation and configuration, see our 5-Minute First API Guide.
Step 5: Test MCP Toolsβ
STOA Gateway exposes APIs as MCP tools that AI agents can discover and invoke. Let's verify the MCP integration.
List Available Toolsβ
curl http://localhost:8081/mcp/tools
Response:
{
"tools": [
{
"name": "httpbin_test_get",
"description": "HTTPBin Test API - GET /httpbin/get",
"inputSchema": {
"type": "object",
"properties": {}
}
}
]
}
Each API endpoint you create is automatically exposed as an MCP tool with a generated name pattern: {api_name}_{method}_{path}.
Invoke a Toolβ
curl -X POST "http://localhost:8081/mcp/tools/httpbin_test_get/call" \
-H "Content-Type: application/json" \
-d '{
"arguments": {}
}'
The Gateway will proxy the request to https://httpbin.org/get and return the response.
Connect to Claude Desktopβ
To use STOA Gateway with Claude Desktop (or any MCP-compatible client):
- Add this configuration to your MCP settings:
{
"mcpServers": {
"stoa-local": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"],
"env": {
"MCP_SERVER_URL": "http://localhost:8081/mcp"
}
}
}
}
- Restart Claude Desktop
- You'll now see STOA tools in the tool palette
For a complete guide to MCP integration, including OAuth 2.1 authentication for production deployments, see our MCP Gateway documentation.
Customizationβ
Environment Variablesβ
Edit .env to customize the stack. Common changes:
Change ports (if defaults conflict with other services):
API_PORT=9000
CONSOLE_PORT=4000
PORTAL_PORT=9080
GATEWAY_PORT=9081
Enable debug logging:
LOG_LEVEL=DEBUG
RUST_LOG=debug
Configure CORS (for external frontend development):
CORS_ORIGINS=http://localhost:5173,http://localhost:5174
Database connection pool:
DB_POOL_SIZE=20
DB_MAX_OVERFLOW=10
After changing .env, restart the stack:
docker compose down
docker compose up -d
Persistent Dataβ
By default, PostgreSQL data is stored in a Docker volume (stoa-postgres-data). This persists across container restarts.
To reset the database to a clean state:
docker compose down -v # -v removes volumes
docker compose up -d
Warning: This deletes all APIs, policies, and applications you've created.
Custom Keycloak Realmβ
The default realm (keycloak/realm-export.json) includes test users with the password admin. For production-like local testing, you can:
- Import a custom realm: place your
realm.jsoninkeycloak/and updatedocker-compose.yml:
keycloak:
environment:
KEYCLOAK_IMPORT: /opt/keycloak/data/import/my-realm.json
volumes:
- ./keycloak/my-realm.json:/opt/keycloak/data/import/my-realm.json
- Or configure Keycloak manually via the Admin Console and export the realm for reuse.
Multiple Gateway Instancesβ
To simulate a multi-gateway deployment (e.g., edge + sidecar modes), add another gateway service:
stoa-gateway-sidecar:
image: ghcr.io/stoa-platform/stoa-gateway:latest
container_name: stoa-gateway-sidecar
ports:
- "8082:8080"
environment:
MODE: sidecar
CONTROL_PLANE_API_URL: http://control-plane-api:8000
GATEWAY_API_KEY: ${STOA_CONTROL_PLANE_API_KEY}
networks:
- stoa-network
Register the sidecar gateway in the Console UI under Gateway Instances β Register Gateway.
Troubleshootingβ
Services Won't Startβ
Symptom: docker compose up -d exits with errors.
Causes:
- Port conflicts β another service is using 3000, 8000, 8080, or 8443
- Insufficient memory β Docker Desktop allocates less than 8 GB RAM
- Disk space β Docker has less than 10 GB free
Fix:
# Check port usage
lsof -i :3000
lsof -i :8000
# Free up Docker disk space
docker system prune -a --volumes
# Increase Docker Desktop memory (Settings β Resources β Memory β 12GB)
Keycloak Certificate Warningβ
Symptom: Browser shows "Your connection is not private" when accessing https://localhost:8443.
Cause: Keycloak uses a self-signed certificate for local development.
Fix: Click "Advanced" β "Proceed to localhost (unsafe)". This is safe for local development. For production, configure a real certificate.
Control Plane API Returns 500β
Symptom: API requests return {"detail": "Internal server error"}.
Cause: Database connection failed or migration not applied.
Debug:
# Check API logs
docker compose logs control-plane-api
# Common errors:
# - "connection refused" β PostgreSQL not ready (wait 30s and retry)
# - "relation does not exist" β migrations not applied
# Manually run migrations
docker compose exec control-plane-api alembic upgrade head
Gateway Returns 404 for API Routesβ
Symptom: curl http://localhost:8081/httpbin/get returns 404.
Cause: API not synced to Gateway.
Fix:
- Check API exists:
curl http://localhost:8000/v1/apis - Force re-sync: Edit the API in Console UI and save (no changes needed)
- Verify Gateway config:
docker compose logs stoa-gateway | grep "Synced"
MCP Tools Not Appearingβ
Symptom: curl http://localhost:8081/mcp/tools returns empty tools array.
Cause: No APIs created yet, or APIs have enabled: false.
Fix:
- Create an API via Console UI (Step 4)
- Ensure the API status is "Active"
- Restart Gateway:
docker compose restart stoa-gateway
Docker Compose Syntax Errorβ
Symptom: services.xxx Additional property yyy is not allowed.
Cause: Using docker-compose (v1) syntax with a v2 compose file.
Fix: Upgrade to Docker Compose v2 (docker compose with space, not hyphen). Or edit docker-compose.yml to be v1-compatible (not recommended).
Next Stepsβ
Now that you have STOA running locally, explore these guides:
- GitOps in 10 Minutes β manage APIs as code using GitOps workflows
- stoactl CLI Guide β manage APIs from the terminal with kubectl-style commands
- Open Source API Gateway 2026 β architecture deep-dive and design principles
- Quick Start Guide β comprehensive documentation for production deployments
- Configuration Reference β all environment variables and settings explained
For production deployment to Kubernetes, see our Installation Guide with Helm charts and ArgoCD examples.
FAQβ
Can I use this setup for production?β
No. This Docker Compose stack is designed for local development and testing only. It uses:
- Self-signed certificates
- Default credentials (
admin/admin) - Single-replica services (no high availability)
- Embedded PostgreSQL (not production-grade)
For production, deploy to Kubernetes using our Helm charts. This gives you:
- Multi-replica deployments with auto-scaling
- Managed databases (RDS, Cloud SQL, etc.)
- Let's Encrypt certificates via cert-manager
- ArgoCD for GitOps-based continuous deployment
How do I update to the latest version?β
Pull the latest images and restart:
docker compose pull
docker compose up -d
Docker Compose will recreate containers with the new images while preserving your data volumes (PostgreSQL, Keycloak).
Note: Major version upgrades may require database migrations. Check the CHANGELOG before upgrading.
Can I connect external services to this stack?β
Yes. All services are accessible from the host machine:
- External frontend (React, Vue, etc.) β set
VITE_API_URL=http://localhost:8000in your app's.env - External CLI β
stoactl configure --api-url http://localhost:8000 - Postman/Insomnia β import the OpenAPI spec from
http://localhost:8000/openapi.json
For AI agents (Claude Desktop, ChatGPT plugins), configure the MCP Gateway URL as http://localhost:8081/mcp.
Limitation: Services inside other Docker Compose stacks cannot reach this stack by hostname (control-plane-api won't resolve). Use host.docker.internal as a bridge on Mac/Windows, or join the same Docker network.