Docker Deploy
FreeNot checkedLightweight PaaS for deploying Docker containers to any VPS with automatic SSL, subdomain routing, and MCP-based management from any AI coding agent.
About
Lightweight PaaS for deploying Docker containers to any VPS with automatic SSL, subdomain routing, and MCP-based management from any AI coding agent.
README
mcp-deploy
The most lightweight PaaS on the planet. Deploy Docker containers to your VPS from any MCP client. No database, no bloat – just deploy.
Website · Quick Start · MCP Tools · REST API
Demo video: Watch the full demo on the project website.
Install
Run this on any Linux VPS — installs everything in under a minute:
curl -fsSL https://raw.githubusercontent.com/ddalcu/mcp-deploy/main/install.sh | bash
Table of Contents
- How It Works
- Quick Start
- Usage
- Server
- Reference
How It Works
┌─────────────────┐ HTTPS ┌─────────────────────────────────────┐
│ Claude Code / │ ────────────────── │ Your VPS │
│ Cursor / any │ MCP Protocol │ │
│ MCP Client │ /mcp │ Traefik ──→ MCP Server (:3000) │
└─────────────────┘ │ │ │
│ ├──→ my-app.deploy.example.com │
┌─────────────────┐ HTTP (direct) │ ├──→ my-api.deploy.example.com │
│ GitHub Actions │ ────────────────── │ └──→ blog.deploy.example.com │
│ / CI/CD / curl │ REST API /api │ │
└─────────────────┘ └─────────────────────────────────────┘
- Traefik handles SSL (automatic per-subdomain Let's Encrypt certs) and routes each subdomain to the right container
- MCP Server receives tool calls over Streamable HTTP and manages Docker containers
- No database — Docker is the source of truth
- No registry required — push images directly or pull from GHCR/DockerHub
Quick Start
You need a Linux VPS with a public IP. Any provider works — if you don't have one yet, Hetzner gives you a $20 credit.
Note: Proxmox/LXC Note: If you want to run this on Proxmox/LXC, make sure its in a VM, not LXC.
1. Set Up DNS
Create two DNS A records pointing to your VPS IP:
| Record | Type | Value | Purpose |
|---|---|---|---|
deploy.example.com |
A | YOUR_VPS_IP |
MCP server (mcp.deploy.example.com) |
*.deploy.example.com |
A | YOUR_VPS_IP |
All deployed apps (myapp.deploy.example.com, etc.) |
Note: The wildcard record (
*) must include the dot and asterisk prefix — enter it exactly as*.deploy.example.comin your DNS provider. This is a separate record from the base domain.
2. Install on Your VPS
Run the installer with curl:
curl -fsSL https://raw.githubusercontent.com/ddalcu/mcp-deploy/main/install.sh | bash
Or with wget:
wget -qO- https://raw.githubusercontent.com/ddalcu/mcp-deploy/main/install.sh | bash
The installer asks for two things: your base domain (e.g. deploy.example.com, without * or leading dot) and email (for Let's Encrypt). That's it. It generates an API key and starts everything.
Alternative: Docker Compose Install
For users who already have Docker and prefer a non-interactive setup:
Create the directory and download the compose file:
mkdir -p /opt/mcp-deploy && cd /opt/mcp-deploy curl -fsSL https://raw.githubusercontent.com/ddalcu/mcp-deploy/main/docker-compose.yml -o docker-compose.ymlCreate a
.envfile:cat > .env <<EOF DOMAIN=deploy.example.com [email protected] API_KEY=$(openssl rand -hex 32) EOF chmod 600 .envStart the stack:
docker network create web 2>/dev/null; docker compose up -d
Optional: Set
MCP_TRAEFIK=falseandDIRECT_PORT=8080in.envto change how the MCP server is exposed. See Exposure Modes for details.
3. Add to Your MCP Client
Copy the config the installer outputs into your MCP client settings.
Claude Code:
claude mcp add deploy https://mcp.deploy.example.com/mcp \
-t http -s user \
-H "Authorization: Bearer YOUR_API_KEY"
The -s user flag makes it available in all projects. Restart Claude Code after adding.
4. Deploy Something
Open any project in Claude Code and say:
"Deploy nginx:alpine as hello"
Visit https://hello.deploy.example.com — it's live with SSL.
Pushing Images
Option A: Push directly (no registry needed)
Build locally and send the image straight to your VPS:
docker build -t my-app:latest .
docker save my-app:latest | curl -X POST -T - \
-H "Authorization: Bearer YOUR_API_KEY" \
https://mcp.deploy.example.com/upload
Then deploy it:
"Deploy my-app:latest as my-app on port 3000"
Option B: Pull from an external registry
Use any registry. Public images just work:
"Deploy nginx:alpine as docs on port 80"
For private registries, pass credentials:
"Deploy ghcr.io/myorg/my-app:latest as my-app on port 3000 with registry auth username myuser password ghp_abc123"
Multiple Servers
Install on as many VPS servers as you want. Add each as a separate MCP server entry:
{
"mcpServers": {
"deploy-prod": {
"type": "streamable-http",
"url": "https://mcp.prod.example.com/mcp",
"headers": { "Authorization": "Bearer PROD_KEY" }
},
"deploy-staging": {
"type": "streamable-http",
"url": "https://mcp.staging.example.com/mcp",
"headers": { "Authorization": "Bearer STAGING_KEY" }
}
}
}
Then specify which server: "Deploy my-app to deploy-staging"
MCP Tools
Nine tools, available to any MCP client — all managed through natural language.
| Tool | Description |
|---|---|
deploy |
Deploy or redeploy a Docker image with automatic SSL and subdomain routing |
deploy-static |
Deploy a static HTML/CSS/JS site — no Docker image needed |
list |
List all managed deployments |
logs |
Get container logs |
status |
Detailed app info — CPU, memory, uptime, restart count |
stop |
Stop a running app (container preserved) |
start |
Start a stopped app |
remove |
Permanently remove an app and its container |
upload-image |
Push a locally-built Docker image to your VPS — no registry needed |
Deploy Parameters
| Param | Type | Required | Description |
|---|---|---|---|
name |
string | yes | App name — becomes the subdomain (name.domain.com) |
image |
string | yes | Docker image (e.g. nginx:alpine, ghcr.io/user/repo:v1) |
port |
number | no | Port the app listens on inside the container. Auto-detected from the image EXPOSE directive if not provided. |
env |
object | no | Environment variables ({ "KEY": "value" }) |
registry_auth |
object | no | { "username": "...", "password": "..." } for private registries |
volumes |
string[] | no | Named volume mounts (["data:/app/data"]) |
command |
string | no | Override the container's default command |
REST API
All MCP tools are also available as standard REST endpoints — no LLM or MCP client required. Use these from GitHub Actions, shell scripts, or any HTTP client.
All endpoints require Authorization: Bearer YOUR_API_KEY.
| Method | Path | Description |
|---|---|---|
POST |
/api/deploy |
Deploy or redeploy a Docker image |
GET |
/api/apps |
List all deployed apps |
GET |
/api/apps/:name |
Get detailed app status |
GET |
/api/apps/:name/logs |
Get app logs |
POST |
/api/apps/:name/stop |
Stop an app |
POST |
/api/apps/:name/start |
Start a stopped app |
DELETE |
/api/apps/:name |
Remove an app |
POST |
/upload |
Upload a Docker image (binary stream) |
POST |
/deploy-static/:name |
Deploy a static site (tar.gz) |
Deploy via REST
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "image": "nginx:alpine", "port": 80}' \
https://mcp.deploy.example.com/api/deploy
Response:
{"name": "my-app", "url": "https://my-app.deploy.example.com", "image": "nginx:alpine", "container": "mcp-deploy-my-app", "status": "running"}
List apps
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://mcp.deploy.example.com/api/apps
# Filter: ?status=running or ?status=stopped
App status, logs, stop, start, remove
# Status
curl -H "Authorization: Bearer $KEY" https://mcp.example.com/api/apps/my-app
# Logs (optional: ?tail=50&since=1h)
curl -H "Authorization: Bearer $KEY" https://mcp.example.com/api/apps/my-app/logs
# Stop
curl -X POST -H "Authorization: Bearer $KEY" https://mcp.example.com/api/apps/my-app/stop
# Start
curl -X POST -H "Authorization: Bearer $KEY" https://mcp.example.com/api/apps/my-app/start
# Remove (optional: ?remove_volumes=true)
curl -X DELETE -H "Authorization: Bearer $KEY" https://mcp.example.com/api/apps/my-app
GitHub Actions
For CI/CD, you'll typically push images from a registry rather than uploading directly. Recommended registries:
- GitHub Container Registry (GHCR) — free for public repos, integrates with GitHub Actions
- Docker Hub — one free private repo, widely supported
- ttl.sh — anonymous, ephemeral registry (images expire after hours) — great for previews and testing
Deploy from a registry (recommended)
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:latest
- name: Deploy to VPS
run: |
curl -fsSL -X POST \
-H "Authorization: Bearer ${{ secrets.DEPLOY_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app",
"image": "ghcr.io/${{ github.repository }}:latest",
"port": 3000,
"registry_auth": {
"username": "${{ github.actor }}",
"password": "${{ secrets.GITHUB_TOKEN }}"
}
}' \
https://mcp.${{ secrets.DEPLOY_DOMAIN }}/api/deploy
Deploy with direct image upload (no registry)
If you prefer not to use a registry, you can upload the image directly. This requires direct port access for best results.
- name: Build and upload image
run: |
docker build -t my-app:latest .
docker save my-app:latest | curl -fsSL -X POST -T - \
-H "Authorization: Bearer ${{ secrets.DEPLOY_KEY }}" \
http://${{ secrets.VPS_IP }}:8080/upload
- name: Deploy
run: |
curl -fsSL -X POST \
-H "Authorization: Bearer ${{ secrets.DEPLOY_KEY }}" \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "image": "my-app:latest", "port": 3000}' \
http://${{ secrets.VPS_IP }}:8080/api/deploy
Deploy a static site
- name: Deploy static site
run: |
tar -czf site.tar.gz -C ./dist .
curl -fsSL -X POST \
-H "Authorization: Bearer ${{ secrets.DEPLOY_KEY }}" \
-H "Content-Type: application/gzip" \
--data-binary @site.tar.gz \
https://mcp.${{ secrets.DEPLOY_DOMAIN }}/deploy-static/my-site
Web Dashboard
The server includes a built-in web dashboard at the root URL (/). It shows all deployed apps with their status, resource usage, and logs — no MCP client required.
- Auth: enter your API key to log in (saved in
localStorage) - App table: name, status (green/red badge), image, URL, CPU, memory
- Logs: click any row to expand and view the last 50 log lines
- Auto-refresh: polls every 30 seconds
- Dark theme, no external dependencies, works on mobile
Access it at https://mcp.deploy.example.com (production) or http://localhost:3000 (local dev).
What Gets Installed on Your VPS
/opt/mcp-deploy/
├── .env ← your config + API key (chmod 600)
└── docker-compose.yml ← the stack definition
Two files. Everything else runs in Docker:
| Container | Image | Purpose |
|---|---|---|
mcp-deploy-traefik |
traefik:v3.3 |
Reverse proxy + automatic SSL |
mcp-deploy-server |
ghcr.io/ddalcu/mcp-deploy |
MCP server |
Plus a traefik-certs volume for Let's Encrypt certificates and a web network shared by all containers.
MCP Server Exposure Modes
The installer asks how the MCP server (dashboard, API, MCP endpoint) should be accessible. This only affects the MCP server — deployed apps always use Traefik for subdomain routing.
| Mode | MCP Server access | Deployed apps |
|---|---|---|
| Domain only (default) | https://mcp.domain.com via Traefik |
Via Traefik |
| Port only | http://IP:PORT direct access |
Via Traefik |
| Both | Domain + direct port | Via Traefik |
Port only is useful when you have your own reverse proxy in front, or only need API/CI access without a subdomain for the MCP server itself.
To change the mode after installation, edit /opt/mcp-deploy/.env:
# Domain only (default) — remove MCP_TRAEFIK and DIRECT_PORT lines
# Port only
MCP_TRAEFIK=false
DIRECT_PORT=8080
# Both
DIRECT_PORT=8080
For port only or both, create /opt/mcp-deploy/docker-compose.override.yml:
services:
mcp-server:
ports:
- "${DIRECT_PORT}:3000"
Then restart:
cd /opt/mcp-deploy && docker compose up -d
Security note: The direct port is HTTP (not HTTPS). Use it only for server-to-server communication (CI/CD) where the VPS IP is known. All endpoints still require the API key.
Configuration
All config is in /opt/mcp-deploy/.env on the VPS:
| Variable | Description | Default |
|---|---|---|
DOMAIN |
Base domain for all services | (required) |
ACME_EMAIL |
Let's Encrypt notification email | (required) |
API_KEY |
Bearer token for MCP server auth | (auto-generated) |
MCP_IMAGE |
Override the MCP server Docker image | ghcr.io/ddalcu/mcp-deploy:latest |
MCP_TRAEFIK |
Route MCP server through Traefik | true |
DIRECT_PORT |
Expose MCP server on this port via IP (no SSL) | (disabled) |
After editing .env, restart the stack:
cd /opt/mcp-deploy && docker compose up -d
Upgrade
cd /opt/mcp-deploy && docker compose pull && docker compose up -d
This pulls the latest MCP server image and recreates the container. Your config (.env), deployed apps, and SSL certificates are not affected.
Uninstall
cd /opt/mcp-deploy && docker compose down -v
rm -rf /opt/mcp-deploy
docker network rm web
Security
- All traffic is HTTPS via Traefik + Let's Encrypt (automatic per-subdomain certs via HTTP-01 challenge)
- MCP and upload endpoints require
Authorization: Bearer <token>(timing-safe comparison) - API key is generated with
openssl rand -hex 32(256-bit) .envfile ischmod 600(root-only readable)- Volume mounts are restricted to named volumes only (no host path mounts)
- Infrastructure container names are reserved and cannot be overwritten via deploy
- Upload endpoint enforces a 2 GB size limit
Architecture
graph LR
subgraph Clients
CC[Claude Code / Cursor]
CI[GitHub Actions / curl]
end
subgraph VPS
T[Traefik]
MCP[MCP Server :3000]
D[(Docker)]
T -- "/mcp" --> MCP
T -- "/api/*" --> MCP
MCP -- "manage containers" --> D
T -- "*.domain.com" --> Apps
Apps[Deployed Apps] --> D
end
CC -- "MCP Protocol (HTTPS)" --> T
CI -- "REST API (HTTPS)" --> T
server/src/
├── index.ts ← Express + MCP Streamable HTTP transport + /upload endpoint
├── api.ts ← REST API router (/api/*) for CI/CD
├── auth.ts ← Bearer token middleware (timing-safe)
├── config.ts ← Environment variable config
├── dashboard.ts ← Web dashboard HTML (served at GET /)
├── docker.ts ← Dockerode singleton + pull/load/list helpers
├── labels.ts ← Traefik label generator
├── validation.ts ← Shared app name validation + reserved names
└── tools/
├── deploy.ts ← Pull or use local image, create container with Traefik labels
├── list.ts ← Query Docker for containers labeled mcp-deploy.managed=true
├── logs.ts ← Container log retrieval
├── status.ts ← Container inspect + live CPU/memory stats
├── stop.ts ← Stop container
├── start.ts ← Start container
└── remove.ts ← Stop + remove container
Design principles:
- Docker IS the database — no SQLite, no JSON state files, no sync issues
- Traefik auto-discovers containers via Docker labels — no config reloads
- Each tool is one file (SRP) — easy to add new tools without touching existing code
- Stateless auth — every request validates independently, no sessions
Development
A local dev compose file runs the full stack (Traefik + MCP server) on your machine with self-signed TLS, source mounting, and live reload. Compatible with Docker, Podman, and OrbStack.
1. Add a hosts entry
# macOS / Linux
echo "127.0.0.1 mcp.mcp.local" | sudo tee -a /etc/hosts
For each app you deploy locally, add its subdomain too (e.g. 127.0.0.1 my-app.mcp.local).
2. Start the stack
git clone https://github.com/ddalcu/mcp-deploy.git
cd mcp-deploy
docker compose -f docker-compose.dev.yml up
The startup output shows the dashboard URL and API key:
Dashboard: http://localhost:3000
API Key: devkey
3. Access
| URL | Description |
|---|---|
http://localhost:3000 |
Direct access (bypasses Traefik) |
http://mcp.mcp.local |
Via Traefik (HTTP) |
https://mcp.mcp.local |
Via Traefik (HTTPS, accept self-signed cert warning) |
Source files are bind-mounted from server/src/ — edit and restart the mcp-deploy-server container to pick up changes:
docker restart mcp-deploy-server
Dev Configuration
Override defaults with environment variables:
DOMAIN=mydev.local API_KEY=mysecret docker compose -f docker-compose.dev.yml up
| Variable | Default | Description |
|---|---|---|
DOMAIN |
mcp.local |
Base domain for routing |
API_KEY |
devkey |
Bearer token for auth |
How It Compares
| MCP Docker servers | Self-hosted PaaS | mcp-deploy | |
|---|---|---|---|
| Remote VPS deployment | No | Yes | Yes |
| MCP protocol (AI-native) | Yes | No | Yes |
| Automatic SSL + subdomains | No | Yes | Yes |
| No database / minimal | N/A | No (most have DBs) | Yes |
| Direct image push | No | Some | Yes |
| REST API for CI/CD | No | Yes | Yes |
MCP Docker servers like mcp-server-docker and docker-mcp let AI manage Docker locally, but don't deploy to remote servers.
Self-hosted PaaS platforms like Coolify, Dokku, CapRover, and Dokploy deploy containers to your VPS with web UIs or CLI, but have no MCP integration.
mcp-deploy combines MCP-native AI control with remote PaaS deployment and zero-database minimalism.
Contributing
Contributions welcome. Please open an issue first to discuss what you'd like to change.
License
Installing Docker Deploy
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/ddalcu/mcp-deployFAQ
Is Docker Deploy MCP free?
Yes, Docker Deploy MCP is free — one-click install via Unyly at no cost.
Does Docker Deploy need an API key?
No, Docker Deploy runs without API keys or environment variables.
Is Docker Deploy hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Docker Deploy in Claude Desktop, Claude Code or Cursor?
Open Docker Deploy on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Docker Deploy with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
