Multi-database MCP server — MySQL, PostgreSQL, SQLite, Oracle, TimescaleDB
10K+
This image packages the FreePeak/db-mcp-server project, built and maintained by FreePeak and contributors. All application code, functionality, and documentation originate from their repository and are licensed under the MIT License.
This repository only provides:
linux/amd64, linux/arm64, linux/arm/v7)For feature requests, bug reports, and source code contributions, please visit the upstream repository.
Images are published to both registries via manual workflow dispatch (Docker Build & Publish) and are intended to remain identical in content.
Stable promotion is available through workflow actions:
auto-check promotes to stable after 5 days of latest age (when eligible)mark-stable performs explicit/manual stable promotion| Registry | Image |
|---|---|
| Docker Hub | mekayelanik/db-mcp-server |
| GHCR | ghcr.io/mekayelanik/db-mcp-server |
| Tag | Description |
|---|---|
latest | Latest manually published build from upstream main |
stable | Promoted from a published version (auto-eligible after 5 days or manual mark-stable) |
YYYYMMDD-<sha> | Immutable tag — exact upstream commit and build date, e.g. 20250312-19b7975 |
YYYYMMDD | Floating date tag — latest build of that day |
| Platform | Architecture |
|---|---|
linux/amd64 | x86-64 (most servers, desktop Linux, WSL2) |
linux/arm64 | ARM 64-bit (Apple Silicon via Rosetta, AWS Graviton, Raspberry Pi 4/5) |
linux/arm/v7 | ARM 32-bit (Raspberry Pi 2/3, older ARM boards) |
Your support encourages me to keep creating/supporting my open-source projects. If you found value in this project, you can buy me a coffee to keep me inspired.
# Create a config file
cat > config.json <<'EOF'
{
"connections": [
{
"id": "mydb",
"type": "postgres",
"host": "localhost",
"port": 5432,
"name": "mydb",
"user": "myuser",
"password": "mypassword"
}
]
}
EOF
# Run
docker run -d \
--name db-mcp-server \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json \
mekayelanik/db-mcp-server:latest
The MCP server is now available at https://localhost:9092/sse by default.
For local HTTP-only testing, set ENABLE_HTTPS=false.
docker run -d \
--name db-mcp-server \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json \
-e TRANSPORT_MODE=sse \
-e SERVER_PORT=9092 \
mekayelanik/db-mcp-server:latest
docker run -d \
--name db-mcp-server \
-p 8080:8080 \
-v $(pwd)/config.json:/app/config.json \
-e SERVER_PORT=8080 \
mekayelanik/db-mcp-server:latest
docker run --rm -i \
-v $(pwd)/config.json:/app/config.json \
-e TRANSPORT_MODE=stdio \
mekayelanik/db-mcp-server:latest
docker run -d \
--name db-mcp-server \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json \
-v $(pwd)/logs:/app/logs \
mekayelanik/db-mcp-server:latest
docker run -d \
--name db-mcp-server \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json \
mekayelanik/db-mcp-server:20250312-19b7975
docker pull ghcr.io/mekayelanik/db-mcp-server:latest
docker run -d \
--name db-mcp-server \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json \
ghcr.io/mekayelanik/db-mcp-server:latest
services:
db-mcp-server:
image: mekayelanik/db-mcp-server:latest
container_name: db-mcp-server
restart: unless-stopped
ports:
- "9092:9092"
volumes:
- ./config.json:/app/config.json:ro
- logs:/app/logs
environment:
TRANSPORT_MODE: sse
SERVER_PORT: "9092"
volumes:
logs:
services:
db-mcp-server:
image: mekayelanik/db-mcp-server:latest
container_name: db-mcp-server
restart: unless-stopped
ports:
- "9092:9092"
volumes:
- ./config.json:/app/config.json:ro
- logs:/app/logs
environment:
TRANSPORT_MODE: sse
SERVER_PORT: "9092"
depends_on:
postgres:
condition: service_healthy
mysql:
condition: service_healthy
networks:
- mcp-net
postgres:
image: postgres:16-alpine
container_name: mcp-postgres
restart: unless-stopped
environment:
POSTGRES_DB: mydb
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
networks:
- mcp-net
mysql:
image: mysql:8.0
container_name: mcp-mysql
restart: unless-stopped
environment:
MYSQL_DATABASE: mydb
MYSQL_USER: myuser
MYSQL_PASSWORD: mypassword
MYSQL_ROOT_PASSWORD: rootpassword
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "myuser", "-pmypassword"]
interval: 10s
timeout: 5s
retries: 5
networks:
- mcp-net
volumes:
postgres-data:
mysql-data:
logs:
networks:
mcp-net:
driver: bridge
config.json for the above stack:
{
"connections": [
{
"id": "postgres1",
"type": "postgres",
"host": "postgres",
"port": 5432,
"name": "mydb",
"user": "myuser",
"password": "mypassword"
},
{
"id": "mysql1",
"type": "mysql",
"host": "mysql",
"port": 3306,
"name": "mydb",
"user": "myuser",
"password": "mypassword"
}
]
}
Note: Use the Docker Compose service name as the
hostvalue — e.g.postgres,mysql— notlocalhost.
For additional variants, reuse this pattern and adjust service image + config.json connection type/host.
When your databases run on the host (not in Docker), use host.docker.internal instead of localhost:
{
"connections": [
{
"id": "host_postgres",
"type": "postgres",
"host": "host.docker.internal",
"port": 5432,
"name": "mydb",
"user": "myuser",
"password": "mypassword"
}
]
}
On Linux, host.docker.internal requires adding --add-host:
docker run -d \
--name db-mcp-server \
-p 9092:9092 \
--add-host host.docker.internal:host-gateway \
-v $(pwd)/config.json:/app/config.json \
mekayelanik/db-mcp-server:latest
Or in Docker Compose:
services:
db-mcp-server:
image: mekayelanik/db-mcp-server:latest
extra_hosts:
- "host.docker.internal:host-gateway"
If your databases are already running on a named Docker network:
docker run -d \
--name db-mcp-server \
-p 9092:9092 \
--network my-existing-network \
-v $(pwd)/config.json:/app/config.json \
mekayelanik/db-mcp-server:latest
In Docker Compose, attach to an external network:
services:
db-mcp-server:
image: mekayelanik/db-mcp-server:latest
networks:
- my-existing-network
networks:
my-existing-network:
external: true
By default the container binds to all interfaces (0.0.0.0). To restrict to a specific host IP:
docker run -d \
--name db-mcp-server \
-p 192.168.1.100:9092:9092 \
-v $(pwd)/config.json:/app/config.json \
mekayelanik/db-mcp-server:latest
upstream db_mcp {
server 127.0.0.1:9092;
}
server {
listen 443 ssl;
server_name mcp.example.com;
location / {
proxy_pass http://db_mcp;
proxy_http_version 1.1;
# Required for SSE (Server-Sent Events)
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Current default is ENABLE_HTTPS=true.
Recommended practice:
ENABLE_HTTPS=false for tools that only support HTTP.Application transport modes:
sse (default) via HTTP(S)stdio (for local command-based integrations)When running in container server mode (TRANSPORT_MODE=sse):
SERVER_PORT (default 9092)INTERNAL_SERVER_PORT (default 38011, local-only in container)Common MCP HTTP endpoints exposed by upstream server include:
/sse/jsonrpc/message/events/statusSet API_KEY to enable Bearer token protection in HAProxy.
Expected status behavior:
401 Unauthorized403 Forbidden200, 204, 405, etc., depending on endpoint/method)Notes:
Bearer scheme is matched case-insensitively (Bearer/bearer both work)./healthz has a localhost-only bypass for internal health checks.If ENABLE_HTTPS=true, HAProxy serves TLS on SERVER_PORT.
Default minimum TLS version is TLSv1.3.
Set HTTP_VERSION_MODE to control protocol negotiation on the public listener:
auto (default): try h3 first, then fall back to h2, then h1 (http/1.1)all: same behavior as autoh1: force HTTP/1.1 onlyh2: force HTTP/2 over TLS onlyh1+h2: allow both HTTP/1.1 and HTTP/2h3: request HTTP/3 support (QUIC) plus standard TLS fallback (h1+h2)Notes:
h2 and h3 require TLS (ENABLE_HTTPS=true).ENABLE_HTTPS=false, the listener falls back to HTTP/1.1.h3 is requested but QUIC is unavailable in the HAProxy build, startup logs a warning and continues without h3.HTTP_VERSION_MODE=autoauto / all: try HTTP/3 first, then HTTP/2, then HTTP/1.1 fallbackh1: strict compatibility modeh2: force HTTP/2-only over TLSh3: request HTTP/3 with safe TCP fallbackAuto chain (h3 -> h2 -> h1) (default):
docker run -d \
--name db-mcp-auto \
-p 9092:9092 \
-p 9092:9092/udp \
-v $(pwd)/config.json:/app/config.json:ro \
-e ENABLE_HTTPS=true \
-e HTTP_VERSION_MODE=auto \
-e API_KEY='replace_with_a_strong_key' \
mekayelanik/db-mcp-server:latest
HTTP/1.1 only:
docker run -d \
--name db-mcp-h1 \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json:ro \
-e ENABLE_HTTPS=true \
-e HTTP_VERSION_MODE=h1 \
-e API_KEY='replace_with_a_strong_key' \
mekayelanik/db-mcp-server:latest
HTTP/2 only:
docker run -d \
--name db-mcp-h2 \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json:ro \
-e ENABLE_HTTPS=true \
-e HTTP_VERSION_MODE=h2 \
-e API_KEY='replace_with_a_strong_key' \
mekayelanik/db-mcp-server:latest
HTTP/3 requested (plus fallback):
docker run -d \
--name db-mcp-h3 \
-p 9092:9092 \
-p 9092:9092/udp \
-v $(pwd)/config.json:/app/config.json:ro \
-e ENABLE_HTTPS=true \
-e HTTP_VERSION_MODE=h3 \
-e API_KEY='replace_with_a_strong_key' \
mekayelanik/db-mcp-server:latest
services:
db-mcp-server:
image: mekayelanik/db-mcp-server:latest
ports:
- "9092:9092/tcp"
- "9092:9092/udp"
environment:
ENABLE_HTTPS: "true"
HTTP_VERSION_MODE: "auto"
API_KEY: "replace_with_a_strong_key"
volumes:
- ./config.json:/app/config.json:ro
Check startup logs:
docker logs db-mcp-auto | grep -E 'HTTP versions enabled|HTTPS enabled|falling back'
Inspect generated HAProxy binds:
docker exec db-mcp-auto sh -lc "grep -n 'bind ' /tmp/haproxy.cfg"
Expected for auto/all when QUIC is available:
h2,http/1.1h3Expected for h1:
http/1.1Certificate precedence:
TLS_PEM_PATH exists, it is used directly.TLS_CERT_PATH and TLS_KEY_PATH exist, they are combined into PEM.TLS_CN and TLS_SAN.If your traffic is proxied through Cloudflare, use a Cloudflare Origin Certificate instead of the auto-generated self-signed cert.
mcp.example.com.mkdir -p certs
# Paste certificate PEM from Cloudflare
cat > certs/cloudflare-origin.crt <<'EOF'
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
EOF
# Paste private key PEM from Cloudflare
cat > certs/cloudflare-origin.key <<'EOF'
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
EOF
docker run -d \
--name db-mcp-server \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json:ro \
-v $(pwd)/certs/cloudflare-origin.crt:/etc/haproxy/certs/server.crt:ro \
-v $(pwd)/certs/cloudflare-origin.key:/etc/haproxy/certs/server.key:ro \
-e ENABLE_HTTPS=true \
-e TLS_CERT_PATH=/etc/haproxy/certs/server.crt \
-e TLS_KEY_PATH=/etc/haproxy/certs/server.key \
-e TLS_MIN_VERSION=TLSv1.3 \
-e API_KEY='replace_with_a_strong_key' \
mekayelanik/db-mcp-server:latest
Notes:
TLS_CERT_PATH + TLS_KEY_PATH into TLS_PEM_PATH automatically.chmod 600).Full (strict).docker run -d \
--name db-mcp-server \
-p 9092:9092 \
-v $(pwd)/config.json:/app/config.json:ro \
-e TRANSPORT_MODE=sse \
-e ENABLE_HTTPS=true \
-e TLS_CN=localhost \
-e TLS_SAN=DNS:localhost,IP:127.0.0.1 \
-e API_KEY='replace_with_a_strong_key' \
mekayelanik/db-mcp-server:latest
Then connect using:
https://localhost:9092/sseCreate a config.json and mount it to /app/config.json inside the container.
{
"connections": [
{
"id": "pg_main",
"type": "postgres",
"host": "postgres",
"port": 5432,
"name": "maindb",
"user": "appuser",
"password": "apppassword",
"query_timeout": 60,
"max_open_conns": 20,
"max_idle_conns": 5,
"conn_max_lifetime_seconds": 300
},
{
"id": "mysql_legacy",
"type": "mysql",
"host": "mysql",
"port": 3306,
"name": "legacydb",
"user": "appuser",
"password": "apppassword"
},
{
"id": "sqlite_cache",
"type": "sqlite",
"database_path": "/app/data/cache.db",
"journal_mode": "WAL",
"use_modernc_driver": true
},
{
"id": "oracle_erp",
"type": "oracle",
"host": "oracle.internal",
"port": 1521,
"service_name": "PROD",
"user": "appuser",
"password": "apppassword"
}
]
}
| Database | Status | Notes |
|---|---|---|
| PostgreSQL | ✅ Full support (v9.6–18) | Queries, transactions, schema, performance |
| MySQL | ✅ Full support | Queries, transactions, schema, performance |
| SQLite | ✅ Full support | File-based, in-memory, SQLCipher encryption |
| Oracle | ✅ Full support (10g–23c) | Standard, RAC, Cloud Wallet, TNS |
| TimescaleDB | ✅ Full support | Hypertables, continuous aggregates, retention policies |
| Variable | Default | Description |
|---|---|---|
SERVER_PORT | 9092 | Public HAProxy listener port |
INTERNAL_SERVER_PORT | 38011 | Internal MCP server port (inside container) |
TRANSPORT_MODE | sse | Transport mode: sse or stdio |
CONFIG_PATH | /app/config.json | Path to the database config file inside the container |
API_KEY | (empty) | Enables Bearer auth when set (validated at startup) |
ENABLE_HTTPS | true | Enables TLS termination in HAProxy |
TLS_CERT_PATH | /etc/haproxy/certs/server.crt | Certificate file path |
TLS_KEY_PATH | /etc/haproxy/certs/server.key | Private key file path |
TLS_PEM_PATH | /etc/haproxy/certs/server.pem | PEM bundle path used by HAProxy |
TLS_CN | localhost | CN used for auto-generated self-signed cert |
TLS_SAN | DNS:localhost | SAN used for auto-generated self-signed cert |
TLS_DAYS | 365 | Validity period for auto-generated self-signed cert |
TLS_MIN_VERSION | TLSv1.3 | Minimum accepted TLS version for HTTPS listener (TLSv1.2 or TLSv1.3) |
HTTP_VERSION_MODE | auto | Public listener HTTP version mode: auto, all, h1, h2, h1+h2, h3 |
Most MCP clients (Claude Code, Codex-style clients, Cursor, VS Code extensions, and custom agents) accept one of these patterns:
The exact config file path differs per client, but the JSON object under mcpServers is usually similar.
Use this for local trusted development only.
{
"mcpServers": {
"db-mcp-http": {
"url": "http://localhost:9092/sse"
}
}
}
Recommended for LAN/remote use.
{
"mcpServers": {
"db-mcp-https": {
"url": "https://localhost:9092/sse",
"headers": {
"Authorization": "Bearer REPLACE_WITH_API_KEY"
}
}
}
}
If your client validates certificates strictly, use a trusted cert (or configure trust for your self-signed cert).
Works well for local command-based clients (including Claude Desktop/CLI style integrations).
{
"mcpServers": {
"db-mcp-stdio": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-v", "/path/to/config.json:/app/config.json:ro",
"-e", "TRANSPORT_MODE=stdio",
"mekayelanik/db-mcp-server:latest"
]
}
}
}
{
"mcpServers": {
"db-mcp-server": {
"url": "https://localhost:9092/sse",
"headers": {
"Authorization": "Bearer REPLACE_WITH_API_KEY"
}
}
}
}
{
"mcpServers": {
"db-mcp-server": {
"url": "https://localhost:9092/sse",
"headers": {
"Authorization": "Bearer REPLACE_WITH_API_KEY"
}
}
}
}
HTTP + SSE:
docker run -d \
--name db-mcp-http \
-p 9092:9092 \
-e TRANSPORT_MODE=sse \
-e ENABLE_HTTPS=false \
-v $(pwd)/config.json:/app/config.json:ro \
mekayelanik/db-mcp-server:latest
HTTPS + SSE + API key:
docker run -d \
--name db-mcp-https \
-p 9092:9092 \
-e TRANSPORT_MODE=sse \
-e ENABLE_HTTP
Content type
Image
Digest
sha256:457696a37…
Size
51.9 MB
Last updated
5 months ago
docker pull mekayelanik/db-mcp-server