Note: This is an unofficial community-maintained Docker image. It is not affiliated with or endorsed by Oraios GmbH, the creators of Serena.
Serena is an LSP-backed MCP providing 30+ tools for semantic code intelligence across 30+ languages (Python, TypeScript, Rust, Go, Java, C/C++, Ruby, PHP, Kotlin, and more). Fills gaps where tree-sitter-based code-graph tools (like CodeGraphContext, GitNexus, Narsil) stop: real symbol resolution via language servers, cross-file rename, symbol-aware edits.
This Docker image wraps serena-agent with mcp-proxy for HTTP/SSE transport and HAProxy for TLS, rate limiting, API key auth, and CORS — matching the transport layer used by all other MCP images in this ecosystem.
Stdio bridge update (May 2026): the image now ships with
mcp-proxyinstead ofsupergateway. The new bridge is stateful by default and multiplexes all client sessions through a single backend stdio child via JSON-RPC IDs (no spawn-per-session), which eliminates a class of memory leaks observed withsupergatewayin statelessstreamableHttpmode (supercorp-ai/supergateway#108). Empirical measurement on this image: 4.6× lower total RSS (1247 MiB → 268 MiB) under bursty unreused-session traffic. The Node.js runtime is no longer required.
mcp-proxy (stateful by default, single-backend multiplex)prlimit per-child cap and HAProxy concurrency caps prevent runaway LSPs from OOM-ing the host| Architecture | Tag |
|---|---|
| x86-64 | amd64 |
| ARM64 | arm64 |
Multi-architecture images are available — Docker automatically selects the correct platform.
| Tag | Description |
|---|---|
stable | Production-ready, tested release |
latest | Most recent release |
1.1.2 | Specific version |
beta | Pre-release testing |
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 core | 2+ cores |
| RAM | 512 MB | 1 GB+ |
| Disk | 500 MB | 1 GB+ |
| Docker | 23.0+ | Latest |
| Docker Compose | 2.0+ | Latest |
services:
serena-mcp:
image: mekayelanik/serena-mcp:latest
container_name: serena-mcp
restart: unless-stopped
ports:
- "9121:9121"
volumes:
- /path/to/your/project:/data:rw
- serena-config:/config
- serena-cache:/home/serena/.cache
environment:
- PORT=9121
- PUID=1000
- PGID=1000
- TZ=UTC
- PROTOCOL=SHTTP
- SERENA_PROJECT=/data
- SERENA_CONTEXT=desktop-app
- SERENA_TRANSPORT=stdio
- SERENA_LOG_LEVEL=INFO
- ENABLE_HTTPS=false
- HTTP_VERSION_MODE=auto
# mcp-proxy session model. Stateful by default — one stdio child shared
# across all sessions (multiplexed via JSON-RPC ids). Set to "true" only
# if full per-request isolation is required (memory-hostile).
- MCP_PROXY_STATELESS=false
# Cap virtual memory of the Serena stdio child (MiB; 0 disables)
- SERENA_MAX_MEM_MB=4096
# HAProxy concurrency caps (0 disables) — bound burst-spawn risk
- HAPROXY_FRONTEND_MAXCONN=64
- HAPROXY_SERVER_MAXCONN=16
# Optional: require Bearer token auth at HAProxy layer
# - API_KEY=replace-with-strong-secret
# Optional: CORS origins
# - CORS=*
volumes:
serena-config:
driver: local
serena-cache:
driver: local
docker run --rm -i \
-v /path/to/your/project:/data:rw \
-e SERENA_PROJECT=/data \
mekayelanik/serena-mcp:latest
| Protocol | Endpoint | Description |
|---|---|---|
| SHTTP | http://host-ip:9121/mcp | Streamable HTTP (default; exposed simultaneously) |
| SSE | http://host-ip:9121/sse | Server-Sent Events (exposed simultaneously) |
| Health | http://host-ip:9121/healthz | Health check (answered by HAProxy, sub-millisecond) |
WebSocket transport was dropped in the migration to
mcp-proxy. SettingPROTOCOL=WSwill now fail at startup with a clear message. UseSHTTPorSSEinstead.
| Variable | Default | Description |
|---|---|---|
PORT | 9121 | External server port |
PUID | 1000 | User ID for file permissions |
PGID | 1000 | Group ID for file permissions |
TZ | UTC | Container timezone (TZ database) |
PROTOCOL | SHTTP | Transport protocol (SHTTP, SSE, STDIO). WS is no longer supported. |
SERENA_PROJECT | /data | Project path (mapped to --project) |
SERENA_CONTEXT | desktop-app | Client context (see Context Selection) |
SERENA_TRANSPORT | stdio | Serena transport (stdio, sse, streamable-http) |
SERENA_PORT | 9121 | Serena port when transport != stdio |
SERENA_MODES | (empty) | Comma-separated modes fed to --mode |
SERENA_LOG_LEVEL | INFO | Log level (DEBUG, INFO, WARNING, ERROR) |
MCP_PROXY_STATELESS | false | When true, mcp-proxy disables Mcp-Session-Id issuance on /mcp (per-request isolation). Default false = stateful, single backend reused across all sessions. |
SERENA_MAX_MEM_MB | 0 | Cap virtual memory of the Serena stdio child via prlimit --as (MiB). 0 disables the cap. |
HAPROXY_FRONTEND_MAXCONN | 0 | HAProxy frontend maxconn. Bounds total concurrent connections accepted. 0 disables. |
HAPROXY_SERVER_MAXCONN | 0 | HAProxy backend server maxconn. Bounds in-flight requests to the upstream mcp-proxy. 0 disables. |
API_KEY | (empty) | Enables Bearer token auth (Authorization: Bearer <API_KEY>) |
CORS | (empty) | Comma-separated CORS origins, supports * |
ENABLE_HTTPS | false | Enables TLS termination in HAProxy |
TLS_CERT_PATH | /etc/haproxy/certs/server.crt | TLS cert path |
TLS_KEY_PATH | /etc/haproxy/certs/server.key | TLS private key path |
TLS_PEM_PATH | /etc/haproxy/certs/server.pem | Combined PEM file used by HAProxy |
TLS_CN | localhost | CN for auto-generated certificate |
TLS_SAN | DNS:<TLS_CN> | SAN for auto-generated certificate |
TLS_DAYS | 365 | Auto-generated cert validity period |
TLS_MIN_VERSION | TLSv1.3 | Minimum TLS protocol (TLSv1.2 or TLSv1.3) |
HTTP_VERSION_MODE | auto | auto, all, h1, h2, h3, h1+h2 |
RATE_LIMIT | 0 | Max requests per RATE_LIMIT_PERIOD per IP (0 = disabled) |
RATE_LIMIT_PERIOD | 10s | Sliding window for rate limiting (e.g., 10s, 1m, 1h) |
MAX_CONNECTIONS_PER_IP | 0 | Max concurrent connections per IP (0 = disabled) |
IP_ALLOWLIST | (empty) | Comma-separated IPs/CIDRs to allow (all others blocked) |
IP_BLOCKLIST | (empty) | Comma-separated IPs/CIDRs to block |
ENABLE_HTTPS=false, only HTTP/1.1 is available regardless of HTTP_VERSION_MODEENABLE_HTTPS=true, the server auto-generates a self-signed certificate if none is providedENABLE_HTTPS=true and a HAProxy build with QUIC supportHTTP_VERSION_MODE values: auto (h1+h2+h3 if available), h1, h2, h3, h1+h2, allENABLE_HTTPS=true for security/healthz) always bypasses authenticationRATE_LIMIT and RATE_LIMIT_PERIOD work together (e.g., RATE_LIMIT=100 + RATE_LIMIT_PERIOD=1m = 100 req/min)MAX_CONNECTIONS_PER_IP limits concurrent connections (useful for preventing abuse)IP_ALLOWLIST and IP_BLOCKLIST accept comma-separated IPs or CIDR ranges.serena/config.toml)Serena indexes and caches per-project. Default LSP settings can grow the cache to GiB scale on TypeScript / JavaScript projects, because the bundled language server indexes node_modules (see upstream oraios/serena#944). Drop the following file at <project>/.serena/config.toml before first run to keep the footprint bounded:
[indexing]
exclude_patterns = [
"**/node_modules/**",
"**/.next/**",
"**/dist/**",
"**/build/**",
"**/.venv/**",
"**/__pycache__/**",
"**/.git/**",
"**/coverage/**",
"**/target/**",
"**/.serena/cache/**",
]
[cache]
max_size_mb = 200
prune_after_days = 3
max_memory_mb = 1024
[language_servers.typescript]
# Single most impactful line for TS/JS repos — keeps the TS LSP from
# indexing node_modules and library declaration files.
exclude_libs = true
max_memory_mb = 1024
worker_threads = 2
[language_servers.python]
max_memory_mb = 512
This file is per-project (lives inside the mounted volume), so it cannot be baked into the image. Clear any stale cache with rm -rf <project>/.serena/cache/ after changing the limits.
mcp-proxy runs the Serena backend as a single long-lived stdio child and multiplexes all client sessions through it via JSON-RPC ids. This caps the expected memory footprint; the knobs below cap the worst case:
MCP_PROXY_STATELESS=false (default) — share one backend child across all sessions. Recommended for almost every deployment. Flip to true only when you genuinely need per-request isolation (and accept the per-request transport-instance cost).SERENA_MAX_MEM_MB=4096 — caps the virtual-memory size of the Serena child via prlimit --as. A runaway LSP gets OOM-killed by the kernel before it exhausts the host. The recommended starting value is 4 GiB; raise if you index very large monorepos.HAPROXY_FRONTEND_MAXCONN=64 + HAPROXY_SERVER_MAXCONN=16 — bound concurrent connections at the HAProxy layer so a burst cannot saturate the upstream stdio bridge./healthz is answered directly by HAProxy with a local 200 — Docker's container healthcheck no longer depends on upstream MCP readiness, so a slow LSP startup will not mark the container unhealthy.Set PUID and PGID to match your host user to avoid permission issues with mounted volumes:
-e PUID=$(id -u) -e PGID=$(id -g)
-e TZ=America/New_York
-e TZ=Europe/London
-e TZ=Asia/Dhaka
The SERENA_CONTEXT environment variable controls which tools Serena exposes, tailored per MCP client:
| Client | SERENA_CONTEXT | What it does |
|---|---|---|
| Claude Code CLI | claude-code | Excludes tools Claude Code already has (create/read file, shell) — tighter token budget |
| Cursor / Windsurf | ide | Generic IDE; excludes create_text_file |
| VS Code + Copilot | vscode | Assumes internal file/shell tools; activate_project required |
| ChatGPT Desktop | chatgpt | 30-tool cap + short descriptions |
| Codex | codex | Excludes shell + non-symbolic edits |
| GitHub Copilot CLI | copilot-cli | Tuned for gh copilot |
| JetBrains AI Assistant | jb-ai-assistant | JetBrains-specific tool set |
| JetBrains Copilot plugin | jb-copilot-plugin | JetBrains Copilot variant |
| Junie | junie | JetBrains Junie context |
| OpenAI-compat agent | oaicompat-agent | Generic OpenAI-compatible agent |
| Google Antigravity | antigravity | Google Antigravity context |
| Any desktop / chat app | desktop-app (default) | Full toolset — broadest compatibility |
| Transport | Protocol | Use Case |
|---|---|---|
SHTTP | Streamable HTTP | Best for remote/multi-client setups (default). /mcp endpoint. |
SSE | Server-Sent Events | Compatible with older MCP clients. /sse endpoint exposed simultaneously when PROTOCOL=SHTTP or SSE. |
STDIO | Standard I/O | Single local client, lightest mode. Bypasses HAProxy and mcp-proxy. |
WebSocket transport was removed when the image migrated from
supergatewaytomcp-proxy.mcp-proxydoes not expose a WS output transport.
Configure in ~/.config/claude-code/mcp.json or project-level .mcp.json:
{
"mcpServers": {
"serena": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-v", "${PWD}:/data:rw",
"-e", "SERENA_PROJECT=/data",
"-e", "SERENA_CONTEXT=claude-code",
"-e", "PROTOCOL=STDIO",
"mekayelanik/serena-mcp:latest"
]
}
}
}
{
"mcpServers": {
"serena": {
"transport": "http",
"url": "http://host-ip:9121/mcp"
}
}
}
{
"mcpServers": {
"serena": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-v", "/path/to/project:/data:rw",
"-e", "SERENA_PROJECT=/data",
"-e", "PROTOCOL=STDIO",
"mekayelanik/serena-mcp:latest"
]
}
}
}
Configure in ~/.codex/config.json:
{
"mcpServers": {
"serena": {
"transport": "http",
"url": "http://host-ip:9121/mcp"
}
}
}
{
"mcpServers": {
"serena": {
"transport": "http",
"url": "http://host-ip:9121/mcp"
}
}
}
{
"mcpServers": {
"serena": {
"transport": "http",
"url": "http://host-ip:9121/mcp"
}
}
}
Test the SHTTP endpoint:
curl -s http://localhost:9121/healthz
| Transport | SERENA_TRANSPORT | PROTOCOL | Port exposed | Use when |
|---|---|---|---|---|
| Pure stdio (lightest) | stdio | STDIO | none | Single local client; docker run -i only. Bypasses HAProxy and mcp-proxy. |
| SHTTP + SSE via HAProxy + mcp-proxy | stdio | SHTTP (default) | $PORT (9121) | Multiple remote clients, need TLS/QUIC. /mcp and /sse are exposed simultaneously. |
| Direct SSE from serena (no HAProxy, no mcp-proxy) | sse | (any) | $SERENA_PORT (9121) | Dev / debug, single client |
| Direct Streamable HTTP from serena (MCP spec) | streamable-http | (any) | $SERENA_PORT | MCP spec-compliant HTTP client direct, no bridging |
# Minimal (most common)
-e SERENA_PROJECT=/data
# Tight for Claude Code
-e SERENA_PROJECT=/data -e SERENA_CONTEXT=claude-code
# SSE mode exposed
-e SERENA_TRANSPORT=sse -e SERENA_PORT=9121 -p 9121:9121
# Enable dashboard (bind 127.0.0.1 inside; publish only if you want remote access)
-p 127.0.0.1:24282:24282
# With API key auth at HAProxy layer
-e API_KEY=your-strong-secret -p 9121:9121
# Non-root UID matching host
-e PUID=$(id -u) -e PGID=$(id -g)
services:
serena-hfe:
image: mekayelanik/serena-mcp:latest
environment: [SERENA_PROJECT=/data]
volumes: [/host/hfe:/data:rw]
serena-loinc:
image: mekayelanik/serena-mcp:latest
environment: [SERENA_PROJECT=/data]
volumes: [/host/loinc:/data:rw]
Pros: crash isolation, per-project resource limits, matches other MCP containers.
services:
serena:
image: mekayelanik/serena-mcp:latest
environment:
- SERENA_PROJECT=/projects/hfe
volumes:
- /host/hfe:/projects/hfe:rw
- /host/loinc:/projects/loinc:rw
- serena-config:/config
volumes:
serena-config:
Then in your MCP client, call Serena's activate_project tool with /projects/loinc to switch — no container restart. Shared LSP cache, fast switching, but crashes kill all project sessions.
| Mode | Use Case | Isolation | Complexity |
|---|---|---|---|
| Bridge (default) | Most deployments | Container-level | Low |
| Host | Maximum performance | None | Medium |
| MACVLAN | Dedicated IP | Network-level | High |
services:
serena-mcp:
ports:
- "9121:9121"
services:
serena-mcp:
network_mode: host
environment:
- PORT=9121
networks:
mcp_net:
driver: macvlan
driver_opts:
parent: eth0
ipam:
config:
- subnet: 192.168.1.0/24
gateway: 192.168.1.1
services:
serena-mcp:
networks:
mcp_net:
ipv4_address: 192.168.1.100
docker compose pull
docker compose down && docker compose up -d
docker pull mekayelanik/serena-mcp:latest
docker stop serena-mcp && docker rm serena-mcp
# Re-run your docker run command
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower --run-once serena-mcp
Check tag stable for production; <version> (e.g. 1.1.2) for pinning.
docker logs serena-mcpdocker images | grep serena-mcpss -tlnp | grep 9121-e PUID=$(id -u) -e PGID=$(id -g)ls -la /path/to/your/projectdocker ps | grep serena-mcpcurl http://localhost:9121/healthzfind_references Returns No ResultsSERENA_PROJECT points at repo root/home/serena/.cache volumeContext name 'ide-assistant' is deprecated WarningSERENA_CONTEXT to claude-code0.0.0.0 inside container but not published-p 127.0.0.1:24282:24282 (keep dashboard local; never expose publicly)--memory=2g# Container logs
docker logs serena-mcp
# Enter container shell
docker exec -it serena-mcp bash
# Check serena version
docker exec serena-mcp serena --version
# Check running processes
docker exec serena-mcp ps aux
24282 — never expose publicly. Default compose does NOT publish it. If needed, bind to 127.0.0.1 on host.API_KEY env — optional HAProxy Bearer auth. Generate with openssl rand -base64 32. Rotate quarterly.:ro when Serena only needs read access; :rw is required for symbol-editing tools.ENABLE_HTTPS + API_KEY.GET /healthzdocker logs serena-mcpPull requests are welcome! Please open an issue first to discuss proposed changes.
This Docker image packaging is licensed under GPL-3.0.
The upstream serena-agent is licensed under the MIT License by Oraios GmbH.
Content type
Image
Digest
sha256:277bc1d11…
Size
257.7 MB
Last updated
about 1 month ago
docker pull mekayelanik/serena-mcp