Skip to content

Docker Stack Deployment

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 ----------+ |
+-------------------------------------------------------------+
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).

  1. Clone the repository

    Terminal window
    git clone https://gitlab.satware.com/satware/mcp/onlyoffice.git
    cd onlyoffice
  2. Configure environment

    Terminal window
    cp docker/.env.example docker/.env
    # Edit docker/.env with your production values

    Key variables to set:

    Variable Description Example
    JWT_SECRET OnlyOffice JWT secret openssl rand -hex 32
    ADMIN_PASSWORD Admin UI password (required) secure password
    CORS_ORIGINS Allowed origins for /mcp https://your-domain.com
    ARTIFACT_BASE_URL Base URL for artifact downloads https://your-domain.com/artifacts
    CREDENTIAL_MASTER_PASSWORD Enables credential tools (optional) secure password
  3. Build and start services

    Terminal window
    docker compose -f docker/docker-compose.yml build
    docker compose -f docker/docker-compose.yml up -d
  4. Verify deployment

    Terminal window
    # Health check
    curl -f http://localhost:3847/health
    # Expected: {"status":"healthy","version":"1.3.0",...}
    # Admin login
    curl -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 templates
    curl -b /tmp/cookies.txt http://localhost:3847/admin/templates | jq '.templates | length'
    # Expected: 5
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 *)
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
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
Port Service Access
3847 MCP Server (all endpoints) Inbound - AI clients, operators
9980 OnlyOffice Internal only (Docker backend network)
Terminal window
# Allow MCP server access from internal network
ufw allow from 10.0.0.0/8 to any port 3847
# Block external access to OnlyOffice
ufw deny 9980

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.

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 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.

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

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.

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 configs
CONF=$(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 reload
fi

Enable WebSocket support in the reverse proxy settings. Not required for SSE, but needed if the admin UI is served through the same proxy.

Endpoint Method Expected
/health GET {"status":"healthy"}
/instances GET Instance list with health
/artifacts GET Artifact list
Terminal window
# Cron health check (every 5 min)
*/5 * * * * curl -sf http://localhost:3847/health > /dev/null || echo "MCP DOWN" | logger -t onlyoffice-mcp
Terminal window
cd /opt/onlyoffice-mcp
git pull origin main
docker compose -f docker/docker-compose.yml build
docker compose -f docker/docker-compose.yml up -d