Use when operating a Dokploy-managed deployment via REST API from the shell — creating/updating/deploying postgres/redis/compose/application services, switching an application's source between docker registry and git build, or scripting redeploys. Covers auth, the working endpoint catalog, and a critical gotcha about deployment status semantics.
Install
npx skillscat add innei/skill/dokploy-api-cli Install via the SkillsCat registry.
Dokploy API from the CLI
Operate Dokploy entirely from curl when the dashboard isn't practical (scripting, agents, headless ops).
Auth
Generate an API token in dashboard → Profile → API. Pass on every request:
-H "x-api-key: $TOKEN"Other auth headers (Authorization: Bearer …) return 401 Unauthorized. There is no public OpenAPI/swagger endpoint — discover routes by probing.
Endpoint Catalog (verified)
All endpoints are under https://<dokploy-host>/api/<router>.<procedure>. Reads are GET with query params, writes are POST with JSON body.
Discovery
| Endpoint | Notes |
|---|---|
GET /api/project.all |
List projects |
GET /api/project.one?projectId=<id> |
Project + nested environments[].{applications, mongo, postgres, redis, compose} |
GET /api/postgres.one?postgresId=<id> |
Includes appName (internal hostname), databasePassword, externalPort |
GET /api/application.one?applicationId=<id> |
Includes env (multiline string), sourceType, dockerImage, customGitUrl |
GET /api/compose.one?composeId=<id> |
Includes composeFile, sourceType |
GET /api/deployment.all?applicationId=<id> |
Deploy history (most recent first); composeId works too |
GET /api/docker.getContainersByAppNameMatch?appName=<appName> |
Live container state — use to check actual run status |
Postgres
| Endpoint | Required body |
|---|---|
POST /api/postgres.create |
{name, appName, databaseName, databaseUser, databasePassword, dockerImage, environmentId} |
POST /api/postgres.update |
{postgresId, ...fields-to-change} (e.g. externalPort: 5433 or null) |
POST /api/postgres.deploy |
{postgresId} — required after update to apply port/image changes |
Compose (one-shot or long-running stacks)
| Endpoint | Required body |
|---|---|
POST /api/compose.create |
{name, appName, environmentId, composeType: "docker-compose"} |
POST /api/compose.update |
{composeId, name, description, env, composeFile, sourceType: "raw", composeType, autoDeploy, command} — full replace, all fields required |
POST /api/compose.deploy |
{composeId} |
POST /api/compose.stop |
{composeId} |
POST /api/compose.delete |
{composeId} — also removes containers |
Application (long-running services)
| Endpoint | Required body |
|---|---|
POST /api/application.saveDockerProvider |
{applicationId, dockerImage, username, password, registryUrl} (use empty strings for public images) |
POST /api/application.saveGitProvider |
{applicationId, customGitUrl, customGitBranch, customGitBuildPath, watchPaths, customGitSSHKeyId} |
POST /api/application.saveBuildType |
{applicationId, buildType: "dockerfile"|"nixpacks"|"heroku"|…, dockerfile, dockerContextPath, dockerBuildStage, herokuVersion, railpackVersion} |
POST /api/application.saveEnvironment |
{applicationId, env, buildArgs, buildSecrets, createEnvFile} (env is the entire multiline KEY=VALUE string) |
POST /api/application.deploy |
{applicationId} |
Critical Gotcha: deploy status ≠ container status
deployment.all returns status: "done" as soon as Dokploy's outer compose-up exits successfully. The actual workload container may still be initializing, building, or even crashed afterwards.
For a true pass/fail signal, poll docker.getContainersByAppNameMatch and watch the container's state (running → exited) and status text.
# Wrong: only watches deploy step
poll deployment.all → done = ❌ may still be building inside
# Right: watch the actual container
poll docker.getContainersByAppNameMatch → state=running → state=exited → check exit codeSwitching application source: docker registry ↔ git build
Useful when the registry image is outdated (e.g., you need today's master before tagging a release):
application.saveGitProvider— point at the repo + branchapplication.saveBuildType— setbuildType: "dockerfile",dockerfile: "<path>"(e.g.dockerfile)application.deploy— Dokploy clones, builds, swaps service
To switch back after the image is published:
application.saveDockerProvider— setdockerImage: "<repo>:<tag>",registryUrl: "index.docker.io"application.deploy— pulls and swaps
sourceType and related fields update automatically based on which save*Provider you called last.
Probing for unknown endpoint shapes
POST with empty body to discover the required fields via the Zod error response:
curl -sS -X POST -H "x-api-key: $TOKEN" -H "content-type: application/json" \
"https://<host>/api/postgres.create" -d '{}'
# → {"zodError":{"fieldErrors":{"name":[...],"databaseName":[...],...}}}This is faster than guessing and works for every mutation route.
Common Mistakes
| Mistake | Fix |
|---|---|
Trusting deployment.all status alone |
Cross-check docker.getContainersByAppNameMatch |
Calling compose.deploy with stale exited containers from a prior run |
ssh host docker rm -f <appName>-<service>-1 first, or compose.stop then compose.deploy |
| Hostname mismatch when service names contain hyphens | Use the auto-generated appName (e.g. mx-space-pg-7pacdz), not the user-friendly name |
| Updating compose YAML and expecting current container to reflect it | YAML applies on next deploy; running container keeps the YAML it started with |
Updating externalPort without redeploy |
Must call postgres.deploy (or equivalent) to apply port mapping |