# Location: ./mcpgateway/tools/builder/templates/compose/docker-compose.yaml.j2
# Copyright 2025
# SPDX-License-Identifier: Apache-2.0
# Authors: Teryl Taylor
# Docker Compose manifest for MCP Stack
# Generated from mcp-stack.yaml
version: '3.8'
networks:
mcp-network:
driver: bridge
volumes:
gateway-data:
driver: local
pgdata:
driver: local
{% for plugin in plugins %}
{{ plugin.name | lower }}-data:
driver: local
{% endfor %}
services:
# MCP Gateway
mcpgateway:
image: {{ gateway.image }}
container_name: mcpgateway
hostname: mcpgateway
{% if gateway.env_file is defined %}
env_file:
- {{ gateway.env_file }}
{% endif %}
environment:
{% if gateway.env_vars is defined and gateway.env_vars %}
# User-defined environment variables
{% for key, value in gateway.env_vars.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% endif %}
# Database configuration
- DATABASE_URL=postgresql+psycopg://postgres:$${POSTGRES_PASSWORD:-mysecretpassword}@postgres:5432/mcp
- REDIS_URL=redis://redis:6379/0
{% if gateway.mtls_enabled | default(true) %}
# mTLS client configuration (gateway connects to external plugins)
- PLUGINS_CLIENT_MTLS_CA_BUNDLE=/app/certs/mcp/ca/ca.crt
- PLUGINS_CLIENT_MTLS_CERTFILE=/app/certs/mcp/gateway/client.crt
- PLUGINS_CLIENT_MTLS_KEYFILE=/app/certs/mcp/gateway/client.key
- PLUGINS_CLIENT_MTLS_VERIFY={{ gateway.mtls_verify | default('true') }}
- PLUGINS_CLIENT_MTLS_CHECK_HOSTNAME={{ gateway.mtls_check_hostname | default('false') }}
{% endif %}
ports:
- "{{ gateway.host_port | default(4444) }}:{{ gateway.port | default(4444) }}"
volumes:
- gateway-data:/app/data
{% if gateway.mtls_enabled | default(true) %}
- {{ cert_paths.gateway_cert_dir }}:/app/certs/mcp/gateway:ro
- {{ cert_paths.ca_cert_file }}:/app/certs/mcp/ca/ca.crt:ro
{% endif %}
# Auto-generated plugin configuration
- ./plugins-config.yaml:/app/config/plugins.yaml:ro
networks:
- mcp-network
restart: unless-stopped
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:{{ gateway.port | default(4444) }}/health').read()"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
{% for plugin in plugins %} {{ plugin.name | lower }}:
condition: service_started
{% endfor %}
{% for plugin in plugins %}
# Plugin: {{ plugin.name }}
{{ plugin.name | lower }}:
image: {{ plugin.image | default('mcpgateway-' + plugin.name | lower + ':latest') }}
container_name: mcp-plugin-{{ plugin.name | lower }}
hostname: {{ plugin.name | lower }}
{% if plugin.env_file is defined %}
env_file:
- {{ plugin.env_file }}
{% endif %}
environment:
{% if plugin.env_vars is defined and plugin.env_vars %}
# User-defined environment variables
{% for key, value in plugin.env_vars.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% endif %}
{% if plugin.mtls_enabled | default(true) %}
# mTLS server configuration (plugin accepts gateway connections)
- PLUGINS_TRANSPORT=http
- PLUGINS_SERVER_HOST=0.0.0.0
- PLUGINS_SERVER_PORT={{ plugin.port | default(8000) }}
- PLUGINS_SERVER_SSL_ENABLED=true
- PLUGINS_SERVER_SSL_KEYFILE=/app/certs/mcp/server.key
- PLUGINS_SERVER_SSL_CERTFILE=/app/certs/mcp/server.crt
- PLUGINS_SERVER_SSL_CA_CERTS=/app/certs/mcp/ca.crt
- PLUGINS_SERVER_SSL_CERT_REQS=2 # CERT_REQUIRED - enforce client certificates
{% endif %}
{% if plugin.expose_port | default(false) %}
ports:
- "{{ plugin.host_port }}:{{ plugin.port | default(8000) }}"
{% endif %}
volumes:
- {{ plugin.name | lower }}-data:/app/data
{% if plugin.mtls_enabled | default(true) %}
- {{ cert_paths.plugins_cert_base }}/{{ plugin.name }}:/app/certs/mcp:ro
{% endif %}
networks:
- mcp-network
restart: unless-stopped
healthcheck:
{% if plugin.mtls_enabled | default(true) %}
# When mTLS is enabled, health check uses separate HTTP server on port+1000
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:{{ (plugin.port | default(8000)) + 1000 }}/health').read()"]
{% else %}
# When mTLS is disabled, health check uses main server
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:{{ plugin.port | default(8000) }}/health').read()"]
{% endif %}
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
{% if plugin.depends_on is defined %}
depends_on:
{% for dep in plugin.depends_on %}
- {{ dep }}
{% endfor %}
{% endif %}
{% endfor %}
# PostgreSQL Database
postgres:
image: postgres:17
container_name: mcp-postgres
hostname: postgres
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=$${POSTGRES_PASSWORD:-mysecretpassword}
- POSTGRES_DB=mcp
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- mcp-network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s
# Redis Cache
redis:
image: redis:latest
container_name: mcp-redis
hostname: redis
ports:
- "6379:6379"
networks:
- mcp-network
restart: unless-stopped