π§© Docker ComposeΒΆ
Running MCP Gateway with Compose spins up a full stack (Gateway, Postgres, Redis, optional MPC servers) behind a single YAML file. The Makefile detects Podman or Docker automatically, and you can override it with COMPOSE_CMD=. Health-checks (service_healthy) gate the Gateway until the database is ready, preventing race conditions. If dependencies become temporarily unavailable, the Gateway uses exponential backoff with jitter for connection retriesβsee Startup Resilience for details.
Configure the compose command to useΒΆ
For example, install and use Docker Compose v2:
π³/π¦ Build the imagesΒΆ
π³/π¦ Build the images (when doing local development)ΒΆ
Using Make (preferred)ΒΆ
| Target | Image | Dockerfile | Notes |
|---|---|---|---|
make podman | mcpgateway:latest | Containerfile | Rootless Podman, dev-oriented |
make podman-prod | mcpgateway:latest | Containerfile.lite | Ultra-slim UBI 9-micro build |
make docker | mcpgateway:latest | Containerfile | Docker Desktop / CI runners |
make docker-prod | mcpgateway:latest | Containerfile.lite | Same multi-stage "lite" build |
Remember to tag the image or configure the correct image in docker-compose.yml
Manual equivalentsΒΆ
# Podman (dev image)
podman build -t mcpgateway-dev:latest -f Containerfile .
# Podman (prod image, AMD64, squash layers)
podman build --platform=linux/amd64 --squash \
-t mcpgateway:latest -f Containerfile.lite .
# Docker (dev image)
docker build -t mcpgateway-dev:latest -f Containerfile .
# Docker (prod image)
docker build -t mcpgateway:latest -f Containerfile.lite .
Apple Silicon caveat
Containerfile.litederives from ubi9-micro. Running it via QEMU emulation on M-series Macs often fails with aglibc x86-64-v2error. Use the regular image or build a nativelinux/arm64variant on Mac.
π Start the Compose stackΒΆ
With MakeΒΆ
make compose-up # auto-detects engine
COMPOSE_ENGINE=docker make compose-up # force Docker
COMPOSE_ENGINE=podman make compose-up # force Podman
Without MakeΒΆ
| Make target | Docker CLI | Podman built-in | podman-compose |
|---|---|---|---|
compose-up | docker compose -f docker-compose.yml up -d | podman compose -f docker-compose.yml up -d | podman-compose -f docker-compose.yml up -d |
compose-restart | docker compose up -d --pull=missing --build | idem | idem |
compose-logs | docker compose logs -f | podman compose logs -f | podman-compose logs -f |
compose-ps | docker compose ps | podman compose ps | podman-compose ps |
compose-stop | docker compose stop | podman compose stop | podman-compose stop |
compose-down | docker compose down | podman compose down | podman-compose down |
compose-clean | docker compose down -v (removes volumes) | podman compose down -v | podman-compose down -v |
π Access and verifyΒΆ
- Gateway URL: http://localhost:4444 (Bound to
0.0.0.0inside the container so port-forwarding works.)
- Logs:
make compose-logsor rawdocker compose logs -f gateway.
π Selecting a databaseΒΆ
Uncomment one service block in docker-compose.yml and align DATABASE_URL:
| Service block | Connection string | Notes |
|---|---|---|
postgres: (default) | postgresql+psycopg://postgres:...@postgres:5432/mcp | Recommended for production |
mariadb: | mysql+pymysql://mysql:...@mariadb:3306/mcp | Fully supported - MariaDB 10.6+ |
mysql: | mysql+pymysql://admin:...@mysql:3306/mcp | Alternative MySQL variant |
Named volumes (pgdata, mariadbdata, mysqldata, mongodata) isolate persistent data.
MariaDB & MySQL Full Support
MariaDB and MySQL are fully supported alongside SQLite and PostgreSQL:
- 36+ database tables work perfectly with MariaDB 10.6+ and MySQL 8.0+
- All VARCHAR length issues have been resolved for MariaDB/MySQL compatibility
- The
mariadb:service block is available indocker-compose.yml - Use connection string:
mysql+pymysql://mysql:changeme@mariadb:3306/mcp
π PgBouncer Connection PoolingΒΆ
PgBouncer is a lightweight connection pooler for PostgreSQL that reduces connection overhead and improves throughput under high concurrency. PgBouncer is enabled by default in the Docker Compose configuration.
Default ArchitectureΒΆ
Benefits of PgBouncer (enabled by default):
- Connection multiplexing: Many app connections share fewer database connections
- Reduced PostgreSQL overhead: Lower
max_connectionsreduces memory per connection - Connection reuse: PgBouncer maintains persistent connections to PostgreSQL
- Graceful handling of connection storms: Queues requests instead of rejecting
Disabling PgBouncer (Direct PostgreSQL)ΒΆ
If you need to bypass PgBouncer for debugging or specific workloads:
- Update gateway
DATABASE_URLto connect directly to PostgreSQL:
# In gateway environment section, change:
- DATABASE_URL=postgresql+psycopg://postgres:mysecretpassword@pgbouncer:6432/mcp
# To:
- DATABASE_URL=postgresql+psycopg://postgres:mysecretpassword@postgres:5432/mcp
- Increase gateway pool settings:
- Increase PostgreSQL max_connections:
- Update gateway depends_on to wait for PostgreSQL directly:
Pool ModesΒΆ
PgBouncer supports three pool modes:
| Mode | Description | Best For |
|---|---|---|
| transaction (default) | Connection returned after transaction commit | Web applications, APIs |
| session | Connection held for entire session | Legacy apps requiring session state |
| statement | Connection returned after each statement | Simple read-heavy workloads |
Transaction Mode Limitations
Transaction mode (the default) returns connections to the pool after each transaction. This means:
- Prepared statements may not work as expected across transactions
- Session-level settings (like
SETcommands) are not preserved - LISTEN/NOTIFY requires session mode
- Advisory locks (used during migrations/bootstrap) are session-level; ensure
server_reset_queryclears them (useDISCARD ALLor addSELECT pg_advisory_unlock_all()), or run migrations against direct PostgreSQL.
MCP Gateway is designed to work with transaction mode.
Configuration ReferenceΒΆ
| Parameter | Default | Description |
|---|---|---|
MAX_CLIENT_CONN | 2000 | Maximum connections from applications |
DEFAULT_POOL_SIZE | 100 | Connections per user/database pair |
MIN_POOL_SIZE | 10 | Minimum connections to keep open |
RESERVE_POOL_SIZE | 25 | Extra connections for burst traffic |
MAX_DB_CONNECTIONS | 200 | Maximum connections to PostgreSQL |
SERVER_LIFETIME | 3600 | Max age of server connection (seconds) |
SERVER_IDLE_TIMEOUT | 600 | Close idle connections after (seconds) |
Monitoring PgBouncerΒΆ
Connect to PgBouncer's admin console:
# Connect to PgBouncer admin
docker compose exec pgbouncer psql -p 6432 -U postgres pgbouncer
# View pool statistics
SHOW STATS;
# View current pools
SHOW POOLS;
# View active clients
SHOW CLIENTS;
# View server connections
SHOW SERVERS;
TroubleshootingΒΆ
Connection timeouts: - Increase RESERVE_POOL_SIZE for burst handling - Check if MAX_DB_CONNECTIONS is sufficient
Slow queries with PgBouncer: - Verify pool mode is appropriate for your workload - Check for long-running transactions holding connections
Authentication failures: - Ensure AUTH_TYPE matches PostgreSQL's pg_hba.conf - Verify password is correct in DATABASE_URL
π Lifecycle cheatsheetΒΆ
| Task | Make | Manual (engine-agnostic) |
|---|---|---|
| Start / create | make compose-up | <engine> compose up -d |
| Re-create changed | make compose-restart | <engine> compose up -d --pull=missing --build |
| Tail logs | make compose-logs | <engine> compose logs -f |
| Shell into gateway | make compose-shell | <engine> compose exec gateway /bin/sh |
| Stop | make compose-stop | <engine> compose stop |
| Remove containers | make compose-down | <engine> compose down |
| Nuke volumes | make compose-clean | <engine> compose down -v |
<engine> = docker, podman, or podman-compose as shown earlier.
π Troubleshooting port publishing on WSL2 (rootless Podman)ΒΆ
# Verify the port is listening (dual-stack)
ss -tlnp | grep 4444 # modern tool
netstat -anp | grep 4444 # legacy fallback
A line like
LISTEN rootlessportis normal - the IPv6 wildcard socket (::) also accepts IPv4 whennet.ipv6.bindv6only=0(the default on Linux).
WSL2 quirk
WSL's NAT maps only the IPv6 side, so http://127.0.0.1:4444 fails from Windows. Tell Podman you are inside WSL and restart your containers:
ss should now show an explicit 0.0.0.0:4444 listener, making the service reachable from Windows and the LAN.
π ReferencesΒΆ
- Docker Compose CLI (
up,logs,down) - official docs - Podman's integrated compose wrapper - man page
podman-composerootless implementation - GitHub project- Health-check gating with
depends_on: condition: service_healthy - UBI9 runtime on Apple Silicon limitations (
x86_64-v2glibc) - General Containerfile build guidance (Fedora/Red Hat)