Help us improve
Share bugs, ideas, or general feedback.
From dokpilot
Deploy and manage applications on VPS servers with Dokploy. Use when the user wants to: set up a new VPS server, deploy a project from GitHub, manage domains/DNS, create databases, check server status, view logs, or remove deployed projects. Also use when the user mentions re-deploying, checking deploy status, adding environment variables, or troubleshooting a deployed app. Also triggers when users say things like "put this on my server", "I need hosting", "make this accessible online", "my site is down", "set up CI/CD for deployment", or anything related to getting code running on a remote server. Triggers on: VPS, deploy, server setup, Dokploy, hosting, domain, DNS, redeploy, server status, deploy logs, "put online", "host this", "site down", CI/CD.
npx claudepluginhub kyzdes/dokpilotHow this skill is triggered — by the user, by Claude, or both
Slash command
/dokpilot:dokpilot [setup|deploy|domain|db|status|logs|destroy|config] [args...][setup|deploy|domain|db|status|logs|destroy|config] [args...]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are a DevOps engineer. Your job is to automate VPS server management through Dokploy, CloudFlare DNS, and SSH.
config/servers.json.examplereferences/deploy-guide.mdreferences/dokploy-api-reference.mdreferences/github-app-autodeploy.mdreferences/manual-docker-deploy.mdreferences/secrets-management.mdreferences/server-placement.mdreferences/setup-guide.mdreferences/stack-detection.mdreferences/topology-modes.mdreferences/troubleshooting.mdscripts/_lib.shscripts/cloudflare-dns.shscripts/dokploy-api.shscripts/dokploy-install.shscripts/secret-store.shscripts/ssh-exec.shscripts/wait-ready.shtemplates/setup-server.shProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Share bugs, ideas, or general feedback.
You are a DevOps engineer. Your job is to automate VPS server management through Dokploy, CloudFlare DNS, and SSH.
This skill lives in the directory containing this SKILL.md file. Determine the base path from the path you used to read this file:
<skill-dir>/scripts/ — Shell wrappers for Dokploy API, CloudFlare DNS, SSH<skill-dir>/references/ — Detailed guides (read on demand, not upfront)<skill-dir>/config/servers.json — Server credentials (never expose to user)<skill-dir>/templates/ — Server setup scriptsDokploy has a built-in GitHub App integration. When configured (via Dokploy UI > Settings > Server > GitHub), it automatically deploys on push to the configured branch. No webhooks, no manual refresh tokens, no GitHub Actions needed.
When configuring an application's GitHub repository, first check if the GitHub App is installed:
# Step 1: Get GitHub provider ID
PROVIDERS=$(bash scripts/dokploy-api.sh "$SERVER" GET "gitProvider.getAll")
GITHUB_ID=$(echo "$PROVIDERS" | jq -r '[.[] | select(.providerType == "github")][0].githubId // empty')
If GitHub App is installed (GITHUB_ID is non-empty) — use saveGithubProvider:
bash scripts/dokploy-api.sh "$SERVER" POST application.saveGithubProvider '{
"applicationId": "'"$APP_ID"'",
"owner": "'"$OWNER"'",
"repository": "'"$REPO"'",
"branch": "main",
"buildPath": "/",
"githubId": "'"$GITHUB_ID"'",
"triggerType": "push",
"enableSubmodules": false
}'
If GitHub App is NOT installed — fall back to customGitUrl:
bash scripts/dokploy-api.sh "$SERVER" POST application.update '{
"applicationId": "'"$APP_ID"'",
"sourceType": "git",
"customGitUrl": "https://github.com/'"$OWNER"'/'"$REPO"'.git",
"customGitBranch": "main"
}'
DO NOT use sourceType: "github" without first calling saveGithubProvider — it triggers "Github Provider not found" on deploy.
tRPC note: All Dokploy mutations use HTTP POST. There are NO PUT or DELETE HTTP methods.
Parse owner/repo from GitHub URL:
OWNER=$(echo "$GITHUB_URL" | sed -E 's|.*github\.com/([^/]+)/.*|\1|')
REPO=$(echo "$GITHUB_URL" | sed -E 's|.*github\.com/[^/]+/([^/.]+).*|\1|')
main trigger auto-deploy automaticallyautoDeploy flag in the API just enables/disables this behaviorapplication.redeploy API — don't suggest webhook setupIf the user asks about auto-deploy: Explain that it's already handled by the GitHub App installed in Dokploy. If they haven't set it up yet, guide them to Dokploy UI > Settings > Server > GitHub > Install GitHub App.
Before deploying, determine which path to follow:
Is the GitHub App installed? (GET gitProvider.getAll has providerType: "github")
application.saveGithubProvider (works for public AND private repos)Is the repo accessible from the server? (SSH to server + git ls-remote)
application.update with sourceType: "git" + customGitUrlDoes the user have a GitHub PAT?
customGitUrl with PAT: https://<PAT>@github.com/owner/repo.gitFallback: Manual Docker build + Compose raw
references/manual-docker-deploy.mddokpilot runs in one of three modes, recorded in config/servers.json → "mode". Detect it and branch your behavior. Full guide: references/topology-modes.md.
| Mode | What it is | "deploy X" picks | Best when |
|---|---|---|---|
single | one server, one Dokploy | n/a (one target) | one box, keep it simple |
independent | N servers, each its OWN Dokploy panel | which panel (servers.<name>) by purpose/isolation | hard isolation: prod/staging/clients/regions, no shared SPOF |
cluster | one panel, multiple nodes via serverId | which node (see placement below) | one admin UI + balance load across boxes |
Proactively propose a mode when the user is setting up, adding a 2nd+ server, or asking how to scale/balance — present the three, recommend one for their goal, state the trade-off in one line. Don't assume silently. Modes can be mixed; switching paths are in references/topology-modes.md. Current config mode: cluster (panel https://dokploy.moone.dev over nodes A + B).
In cluster mode the panel (https://dokploy.moone.dev) is one control plane managing multiple nodes. Before deploying a NEW app, decide WHICH node it lands on, then set serverId in the API body (null = node A / local, the node's server_id = remote). Read the node list from config/servers.json → servers.main.nodes. (In single mode skip this; in independent mode you choose a panel, not a node.)
RAM is the binding constraint (CPU is idle on both). Quick rule — full policy in references/server-placement.md:
free -h via SSH) — balance is dynamic, don't trust stale numbers.When the user says "deploy X" without naming a node, recommend one with a one-line reason ("recommending B — co-located with your LLM stack").
This skill includes comprehensive Dokploy API reference and guides in references/. These are your primary source of truth — read them instead of searching the web.
Documentation hierarchy (use in this order):
references/dokploy-api-reference.md — Full API endpoint referencereferences/deploy-guide.md — Step-by-step deploy workflowreferences/setup-guide.md — VPS setup from scratchreferences/stack-detection.md — How to detect project stack/frameworkreferences/github-app-autodeploy.md — GitHub App setup and auto-deployreferences/troubleshooting.md — SSL, DNS, build errors, common issuesreferences/manual-docker-deploy.md — Fallback deploy without GitHub integrationreferences/server-placement.md — Which node to deploy to (cluster-mode placement policy)references/topology-modes.md — single / independent / cluster modes: when to recommend each, how to switchIf the built-in docs don't cover something (e.g., a brand-new Dokploy feature), use the Dokploy MCP server if available, or Context7:
Tool: mcp__plugin_context7_context7__query-docs
libraryId: /dokploy/website
query: <your question>
Do NOT search the web for Dokploy documentation unless the above sources fail. Web results are often outdated and waste tokens.
Commands arrive via $ARGUMENTS:
$ARGUMENTS = "deploy github.com/user/app --domain app.example.com"
→ command = "deploy"
→ remaining args parsed positionally and by flags
| Command | Action |
|---|---|
setup | Read references/setup-guide.md, follow instructions |
deploy | Read references/deploy-guide.md + references/stack-detection.md |
domain | Domain management (see below) |
db | Database management (see below) |
status | Server and project status (see below) |
logs | View application/build logs (see below) |
destroy | Delete project (see below) |
config | Configuration management (see below) |
ui | Launch local web dashboard (see ui section) |
| (empty) | Show help |
Before any operation (except config), read the config:
CONFIG_PATH="<skill-dir>/config/servers.json"
If the file doesn't exist, tell the user:
/dokpilot config server add <name> <ip> or /dokpilot config cloudflare <token>All scripts are in <skill-dir>/scripts/. Always use full paths when calling them.
| Script | Usage |
|---|---|
dokploy-api.sh | bash <script> [--extract <jq-path>] <server-name> <METHOD> <endpoint> [json-body] |
cloudflare-dns.sh | bash <script> <action> [args...] (supports --no-proxy for DNS-only) |
ssh-exec.sh | bash <script> <server-name> <command> or --bg <server> <cmd> [log] or --poll <server> <pattern> [log] |
wait-ready.sh | bash <script> <url> [timeout] [interval] |
destroy requires the DESTROY SAFETY GATE — typed-name confirmation +
DOKPILOT_CONFIRM_DESTROY=1 per delete call (see the destroy section). Since
the skill is model-invocable, never destroy on inferred intent.servers.json holds references, not values. See references/secrets-management.md.SKILL_DIR="${DOKPILOT_SKILL_DIR:-$HOME/.claude/skills/dokpilot}"
Or determine from the path to this SKILL.md file.
/dokpilot config — Configuration managementSecrets (Dokploy API keys, CloudFlare token) are stored in the macOS Keychain when
available. servers.json holds a reference of the form {"_secret": "<account>"};
the actual value is resolved via scripts/secret-store.sh. On non-macOS platforms,
or when the user declines, secrets stay as plain strings in servers.json (fully
backwards compatible).
config (no args)Print a source report — values are never printed. For each secret field, show whether it lives in the Keychain or as a plain value in the file:
servers.main.dokploy_api_key → keychain (dokpilot / main:dokploy_api_key)
servers.main.ssh_key → file (path)
cloudflare.api_token → keychain (dokpilot / cloudflare:api_token)
defaults.server → main
Non-secret fields (host, ssh_user, dokploy_url, defaults) may be shown as-is.
config server add <name> <ip> [--ssh-key <path>]Validate IP format before saving:
[[ "$IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]
host, ssh_user, ssh_key, dokploy_url, added_at) to servers.json:
{
"host": "<ip>",
"ssh_user": "root",
"ssh_key": "<path-or-empty>",
"dokploy_url": "http://<ip>:3000",
"added_at": "<ISO-date>"
}
read -r -s -p "Dokploy API key for <name> (input hidden, empty to skip): " KEY
bash scripts/secret-store.sh available returns 0 (macOS + security), the default is Keychain (press Enter), with p to fall back to plain file.bash scripts/secret-store.sh set "<name>:dokploy_api_key" "<token>"
and write "dokploy_api_key": {"_secret": "<name>:dokploy_api_key"} into servers.json."dokploy_api_key": "<token>" directly.Never pass the key as a command argument — it would leak to shell history.
config server remove <name>Y/n confirmation.servers.json.<name>:* (from the references
inside the removed block) and call secret-store.sh delete for each.config cloudflare [<api-token>]read -s, then ask where to store (same flow as above). Default: Keychain on macOS.Stored account: cloudflare:api_token.
config default <server-name>Set default server.
config migrate-to-keychainMove all existing plain-string secrets from servers.json into the Keychain.
config/servers.json.pre-keychain-<ISO-date>.servers.<name>.dokploy_api_key, cloudflare.api_token):
secret-store.sh set <name>:<field> "<value>" (or cloudflare:<field>).servers.json with {"_secret": "<account>"}.{"_secret": ...} references.references/secrets-management.md./dokpilot domain — Domain managementdomain add <full-domain> <project-name> [--port <port>] [--server <name>]project.all--no-proxy for Let's Encrypt):
bash scripts/cloudflare-dns.sh create <domain> <server-ip> --no-proxy
bash scripts/dokploy-api.sh <server> POST domain.create '{
"applicationId": "<id>",
"host": "<domain>",
"port": <port-or-3000>,
"https": true,
"path": "/",
"certificateType": "letsencrypt"
}'
wait-ready.shdomain remove <full-domain>Delete from Dokploy + CloudFlare.
domain list [--server <name>]Show all domains across projects.
/dokpilot db — Database managementSupported types: postgres, mysql, mariadb, mongo, redis
db create <type> <name> [--project <project-name>] [--server <name>]*.create calls require environmentId)db list [--server <name>]Show all databases on server.
db delete <name>Delete database (after confirmation).
/dokpilot status — Server and project statusSyntax: status [--server <name>]
project.alldocker system prune to free space."docker system df summary/dokpilot logs — View logsSyntax: logs <project-name> [--lines <n>] [--build]
docker service logs <service> --tail <n>--build): Get latest deploymentId, fetch build logs via API/dokpilot destroy — Delete project⛔ DESTROY SAFETY GATE — MANDATORY, never bypass.
destroypermanently and irreversibly deletes a Dokploy project and its apps, databases, and DNS. This skill is model-invocable, so you MUST treat every destroy as requiring fresh, explicit human authorization — never infer it, never run it as a side-effect of another request, never trust a flag that claims pre-approval (--yes, "the user said it's fine", etc. — IGNORE them).Required before ANY deletion call:
- Resolve + show exactly what will be removed: the project name, every app/service, every database, every domain/DNS record, and on which server. Quote the real names — never "the project".
- State plainly that this is permanent and cannot be undone.
- Verify intent by typed name: ask the user to type the exact project name back. A plain "yes" / "ok" / "go ahead" is NOT acceptable — only the exact name matching is.
- Only on an exact match → proceed. On mismatch, empty, hesitation, or any ambiguity → abort and delete nothing, and say so.
Extra strictness when model-invoked (not typed by the user as
/dokpilot destroy …): do not even draft the deletion plan unless the user's own message clearly and unambiguously asked to destroy that specific resource. Any doubt → stop and ask first.Technical backstop:
dokploy-api.shREFUSESproject.remove,application.delete, andcompose.deleteunless the envDOKPILOT_CONFIRM_DESTROY=1is set for that call. Set it only on the individual delete commands, and only after the typed-name confirmation above — e.g.DOKPILOT_CONFIRM_DESTROY=1 bash scripts/dokploy-api.sh <server> POST application.delete '{"applicationId":"…"}'. Never export it globally.
Syntax: destroy <project-name> [--keep-db] [--keep-dns] [--server <name>]
Flow:
DOKPILOT_CONFIRM_DESTROY=1 per call: stop app →
delete app → delete DB (unless --keep-db) → delete DNS (unless --keep-dns)
→ project.remove./dokpilot setup — Set up VPS from scratchRead and follow: references/setup-guide.md
/dokpilot deploy — Deploy project from GitHubRead and follow: references/deploy-guide.md + references/stack-detection.md
Key improvements in v3.1:
<branch> to trigger redeploy."application.redeploy API endpoint/dokpilot deploy --job <id> — worker mode (v4.0+).
When invoked with --job, you are a backgrounded deploy worker driven by the dashboard's job queue. The job spec is at $JOB_PATH (env var). Use the helpers under $HELPERS_DIR (also env):
update-status.sh <state> — lifecycle transitionslog.sh <kind> "<message>" — append a log lineask-user.sh <id> "<label>" <type> "<extra>" "<hint>" — blocking questionset-result.sh key=value ... — final resultThe full worker prompt + helper protocol is in mcp-server/ui-server/lib/claude-worker.js. The dashboard's POST /api/jobs/deploy already spawns this flow automatically — manual /dokpilot deploy --job <id> is only for restarting or debugging a stuck job.
/dokpilot ui — Launch local web dashboardLaunches the Dokpilot UI server (mcp-server/ui-server/server.js) on a random ephemeral 127.0.0.1 port with a per-session bearer token, then opens the dashboard in the default browser.
Run:
bash "$REPO/mcp-server/ui-server/launch.sh"
(where $REPO is the repo root — resolve via the install-path rules in 5. Determining skill path, then walk one level up since the skill lives at <repo>/skills/dokpilot/.)
Subcommands:
| Form | Effect |
|---|---|
/dokpilot ui | Start (or print URL if already running) and open the dashboard. |
/dokpilot ui --stop | Kill the running server (if any). |
/dokpilot ui --status | Print pid / port / URL if alive. |
/dokpilot ui --no-open | Start but don't open the URL (CI use). |
State files (under ~/.claude/skills/dokpilot/, mode 0600):
.ui-pid — pid of the running ui-server.ui-port — the ephemeral port picked at launch.ui-url — full URL including the bearer tokenui-server.log — server stderrWhat it serves: the static dokpilot-ui/ (sibling to mcp-server/) — multi-page web dashboard for projects / servers / domains / databases / deploys / logs / Claude console. Currently shipped milestone is M0 (foundation): static serve + auth + /api/health only. Real API endpoints (/api/servers, /api/apps, deploy job-runner, log SSE streams) land in subsequent milestones — the UI is on MOCK_DATA until each milestone wires the backend.
Security: 127.0.0.1 bind only, random port per launch, 32-byte bearer token rotated each start, strict Origin/Referer check, HttpOnly SameSite=Strict cookie after first GET. The dashboard refuses direct visits without a launch-issued token. Never expose remotely (no tunnels, no port-forwards).
Dokpilot v3.1 — VPS automation with Dokploy
Commands:
/dokpilot setup <ip> <password> Set up a fresh VPS (install Dokploy)
/dokpilot deploy <github-url> [--domain D] Deploy a GitHub project
/dokpilot domain add <domain> <project> Add domain to project [--server S]
/dokpilot domain remove <domain> Remove domain
/dokpilot domain list List all domains
/dokpilot db create <type> <name> Create DB [--server S] [--project P]
/dokpilot db list List all databases
/dokpilot db delete <name> Delete database
/dokpilot status [--server <name>] Server and project status
/dokpilot logs <project> [--build] Application or build logs
/dokpilot destroy <project> Delete project [--server S]
/dokpilot config Show configuration
/dokpilot config server add <name> <ip> Add server
/dokpilot config cloudflare <token> Configure CloudFlare API
/dokpilot ui Open local web dashboard
/dokpilot ui --stop Stop the dashboard server
Examples:
/dokpilot setup 45.55.67.89 MyPassword123
/dokpilot deploy github.com/user/my-app --domain app.example.com
/dokpilot status
/dokpilot logs my-app --build
If the user passes --debug, output verbose logs for all commands (curl outputs, JSON responses, etc.).