Docker Stack Deployment
Architecture
Section titled “Architecture”Single-port model: one Express app inside the mcp-server container listens on :3847 and serves /mcp, /admin, /artifacts, /health, /instances. TLS termination is handled by the host reverse proxy (Caddy or Nginx).
+-------------------------------------------------------------+| Docker Compose Stack |+-------------------------------------------------------------+| +------------------+ +------------------+ +-------------+ || | MCP Server | | OnlyOffice | | Artifact | || | Port: 3847 |--| Port: 9980 | | Cleanup | || | /mcp /admin | | (Document | | (Cron) | || | /artifacts | | Builder) | | | || | /health | | | | | || +--------+---------+ +------------------+ +------+------+| | | || +----------- /var/artifacts ----------+ |+-------------------------------------------------------------+Services
Section titled “Services”| Service | Base Image | Port | Purpose |
|---|---|---|---|
mcp-server |
node:25-slim (multi-stage) |
3847 | MCP server + admin UI + artifact downloads |
onlyoffice |
onlyoffice/documentserver |
9980 (internal) | Document generation engine |
artifact-cleanup |
alpine:3.20 + supercronic |
- | Removes expired artifacts (configurable TTL) |
Optional services via Docker Compose profiles: minio (--profile s3), samba (--profile smb).
Deployment
Section titled “Deployment”-
Clone the repository
Terminal window git clone https://gitlab.satware.com/satware/mcp/onlyoffice.gitcd onlyoffice -
Configure environment
Terminal window cp docker/.env.example docker/.env# Edit docker/.env with your production valuesKey variables to set:
Variable Description Example JWT_SECRETOnlyOffice JWT secret openssl rand -hex 32ADMIN_PASSWORDAdmin UI password (required) secure password CORS_ORIGINSAllowed origins for /mcphttps://your-domain.comARTIFACT_BASE_URLBase URL for artifact downloads https://your-domain.com/artifactsCREDENTIAL_MASTER_PASSWORDEnables credential tools (optional) secure password -
Build and start services
Terminal window docker compose -f docker/docker-compose.yml builddocker compose -f docker/docker-compose.yml up -d -
Verify deployment
Terminal window # Health checkcurl -f http://localhost:3847/health# Expected: {"status":"healthy","version":"1.3.0",...}# Admin logincurl -c /tmp/cookies.txt -X POST http://localhost:3847/admin/login \-H "Content-Type: application/json" \-d '{"password":"<your-password>"}'# Expected: {"success":true,"authenticated":true}# List templatescurl -b /tmp/cookies.txt http://localhost:3847/admin/templates | jq '.templates | length'# Expected: 5
Environment Variables
Section titled “Environment Variables”Required
Section titled “Required”| Variable | Description |
|---|---|
JWT_SECRET |
JWT secret for OnlyOffice Document Server authentication |
ADMIN_PASSWORD |
Password for /admin UI. If unset, /admin returns 503 |
CORS_ORIGINS |
Comma-separated allowed origins (production: restrict, don’t use *) |
Artifact Management
Section titled “Artifact Management”| Variable | Default | Description |
|---|---|---|
ARTIFACT_BASE_URL |
(unset) | Base URL for downloads. Unset = base64 delivery |
ARTIFACT_STORAGE_DIR |
/var/artifacts |
Directory for saved artifacts |
ARTIFACT_EXPIRY_MS |
86400000 (24h) |
Milliseconds before artifacts expire |
ARTIFACT_CLEANUP_INTERVAL_MS |
3600000 (1h) |
Cleanup sweep interval |
Optional
Section titled “Optional”| Variable | Description |
|---|---|
CREDENTIAL_MASTER_PASSWORD |
Enables 5 credential management tools |
CREDENTIAL_STORE_PATH |
Path to encrypted credential store |
DOCKER_GID |
Host Docker GID for socket access |
HOST_UID / HOST_GID |
UID/GID for bind-mount permissions |
Network / Firewall
Section titled “Network / Firewall”| Port | Service | Access |
|---|---|---|
| 3847 | MCP Server (all endpoints) | Inbound - AI clients, operators |
| 9980 | OnlyOffice | Internal only (Docker backend network) |
# Allow MCP server access from internal networkufw allow from 10.0.0.0/8 to any port 3847
# Block external access to OnlyOfficeufw deny 9980Reverse Proxy
Section titled “Reverse Proxy”The MCP server uses the Streamable HTTP transport with SSE (Server-Sent Events) for server-to-client notifications. Reverse proxies must not buffer these responses.
Caddy (recommended)
Section titled “Caddy (recommended)”https://mcp.example.com { reverse_proxy localhost:3847 { flush_interval -1 stream_timeout 24h }}Caddy auto-detects Content-Type: text/event-stream and flushes
immediately. The flush_interval -1 and stream_timeout 24h directives
provide additional protection for long-lived streaming connections.
Caddy automatically provisions TLS certificates via Let’s Encrypt.
upstream mcp_server { server localhost:3847;}
server { listen 443 ssl; server_name mcp.example.com;
ssl_certificate /etc/ssl/certs/mcp.crt; ssl_certificate_key /etc/ssl/private/mcp.key;
location / { proxy_pass http://mcp_server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_buffering off; proxy_read_timeout 3600s; }}The proxy_buffering off directive disables response buffering for SSE
streaming. The proxy_read_timeout 3600s prevents nginx from closing
idle SSE connections after the default 60-second timeout.
Synology DSM
Section titled “Synology DSM”Synology NAS devices running DSM 7.x include a built-in reverse proxy based on nginx. Configure it via Control Panel > Login Portal > Advanced > Reverse Proxy.
Basic Setup
Section titled “Basic Setup”Create a new reverse proxy rule:
| Setting | Value |
|---|---|
| Source Protocol | HTTPS |
| Source Hostname | mcp.example.com |
| Source Port | 443 |
| Destination Protocol | HTTP |
| Destination Hostname | localhost |
| Destination Port | 3847 |
Response Buffering
Section titled “Response Buffering”No manual configuration required. The MCP server sends the
X-Accel-Buffering: no response header on all /mcp responses. DSM’s
nginx honors this header and disables buffering per-response
automatically.
Idle Connection Timeout
Section titled “Idle Connection Timeout”DSM’s default proxy_read_timeout is 60 seconds. After 60 seconds of
no data on the SSE stream, nginx closes the connection and the MCP
client reconnects automatically. This is normal SSE behavior and does
not affect tool calls (which use short-lived POST requests).
If longer idle periods are desired, create a Task Scheduler script (Control Panel > Task Scheduler > Create > Triggered Task > User-defined script) that runs on boot:
#!/bin/bash# Inject proxy_read_timeout into DSM's reverse proxy config# Runs on boot to survive DSM regenerating nginx configsCONF=$(grep -rl "proxy_pass.*:3847" /etc/nginx/app.d/ 2>/dev/null)if [ -n "$CONF" ] && ! grep -q "proxy_read_timeout 3600s" "$CONF"; then sed -i '/proxy_pass.*:3847/a\ proxy_read_timeout 3600s;' "$CONF" nginx -s reloadfiWebSocket
Section titled “WebSocket”Enable WebSocket support in the reverse proxy settings. Not required for SSE, but needed if the admin UI is served through the same proxy.
Health Monitoring
Section titled “Health Monitoring”| Endpoint | Method | Expected |
|---|---|---|
/health |
GET | {"status":"healthy"} |
/instances |
GET | Instance list with health |
/artifacts |
GET | Artifact list |
# Cron health check (every 5 min)*/5 * * * * curl -sf http://localhost:3847/health > /dev/null || echo "MCP DOWN" | logger -t onlyoffice-mcpUpdates
Section titled “Updates”cd /opt/onlyoffice-mcpgit pull origin maindocker compose -f docker/docker-compose.yml builddocker compose -f docker/docker-compose.yml up -dRelated
Section titled “Related”- Proxmox VE Deployment - Production VM hosting
- Architecture - System overview
- HTTP Setup - Client configuration