Install
openclaw skills install caprover-managementManage CapRover PaaS instances via API: create/update apps, deploy from Docker image or custom Dockerfile (tar file), configure ports, volumes, env vars, and serviceUpdateOverride for Docker Swarm settings. Use when the user wants to deploy, configure, or diagnose an app on a CapRover server — including setting up TCP ports for non-HTTP servers (game servers, databases), mounting persistent volumes, building custom Docker images on the host, or reading build/runtime logs.
openclaw skills install caprover-managementCapRover is a self-hosted PaaS that wraps Docker Swarm. It exposes a REST API for full app lifecycle management.
Always start by authenticating:
import urllib.request, json, ssl
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE # self-signed cert on CapRover is common
BASE = "https://<captain-domain>" # e.g. https://captain.example.com
def api(path, data=None, token=None, timeout=60):
body = json.dumps(data).encode() if data else None
headers = {"Content-Type": "application/json"}
if token:
headers["x-captain-auth"] = token
req = urllib.request.Request(f"{BASE}{path}", data=body, headers=headers)
resp = urllib.request.urlopen(req, context=ctx, timeout=timeout)
return json.loads(resp.read())
token = api("/api/v2/login", {"password": "<password>"})["data"]["token"]
See references/api.md for all endpoints. See scripts/caprover.py for a ready-to-use helper class.
api("/api/v2/user/apps/appDefinitions/register",
{"appName": "myapp", "hasPersistentData": False}, token)
Set hasPersistentData: True if the app needs persistent volumes.
api("/api/v2/user/apps/appDefinitions/update",
{"appName": "myapp", "imageName": "nginx:latest"}, token)
api("/api/v2/user/apps/appData/myapp/redeploy",
{"appName": "myapp", "gitHash": ""}, token)
Pack a captain-definition, Dockerfile, and support files into a .tar.gz, then POST:
# captain-definition (required in tar root):
# {"schemaVersion": 2, "dockerfilePath": "./Dockerfile"}
with open("app.tar.gz", "rb") as f:
tar_data = f.read()
boundary = "----FormBoundaryCaprover"
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="sourceFile"; filename="app.tar.gz"\r\n'
f"Content-Type: application/octet-stream\r\n\r\n"
).encode() + tar_data + f"\r\n--{boundary}--\r\n".encode()
req = urllib.request.Request(
f"{BASE}/api/v2/user/apps/appData/myapp",
data=body,
headers={
"Content-Type": f"multipart/form-data; boundary={boundary}",
"x-captain-auth": token,
},
)
resp = urllib.request.urlopen(req, context=ctx, timeout=180)
This builds the image natively on the CapRover host — critical for ARM64 hosts where pre-built amd64 images won't run.
api("/api/v2/user/apps/appDefinitions/update", {
"appName": "myapp",
"envVars": [{"key": "MY_VAR", "value": "hello"}],
"ports": [{"hostPort": 25565, "containerPort": 7777}],
"volumes": [{"containerPath": "/data", "volumeName": "myapp-data"}],
"instanceCount": 1,
}, token)
⚠️ Port update bug: The
portsfield update sometimes returns HTTP 500 on CapRover (known issue). Workaround: set ports once at app creation time or useserviceUpdateOverride.
For settings not exposed in the standard API — volume mounts, custom DNS, resource limits:
override = json.dumps({
"TaskTemplate": {
"ContainerSpec": {
"Mounts": [{
"Type": "volume",
"Source": "captain--myapp-data", # CapRover names: captain--<appname>-<name>
"Target": "/data"
}]
}
}
})
api("/api/v2/user/apps/appDefinitions/update",
{"appName": "myapp", "serviceUpdateOverride": override}, token)
⚠️ Setting
serviceUpdateOverrideto""(empty string) clears it and removes all Docker Swarm overrides, including volume mounts.
# Build logs (after deploying)
r = api("/api/v2/user/apps/appData/myapp", token=token)
build_lines = r["data"]["logs"]["lines"]
# Runtime logs (stdout of running container)
r = api("/api/v2/user/apps/appData/myapp/logs", token=token)
raw_logs = r["data"]["logs"]
If the CapRover host is ARM64 (uname -m returns aarch64):
mono-runtime in the Dockerfile and use mono ./App.exe as the entrypoint$(uname -m) returns aarch64 on ARM64| Symptom | Likely Cause | Fix |
|---|---|---|
HTTP 500 on port update | CapRover bug | Set ports at app creation, or use serviceUpdateOverride |
| Container crashes, no logs | Wrong arch image (amd64 on arm64) | Build from Dockerfile on host |
| Port open but server not responding | Server listening on 127.0.0.1 only | Check server bind address; use 0.0.0.0 |
| World/data lost on restart | No volume mount | Add serviceUpdateOverride with Mounts |
| Logs empty | App writes logs to file, not stdout | Override entrypoint to redirect to stdout |
volumes: [] in API but data persists | serviceUpdateOverride holds the mount — API and Swarm state diverge | Check serviceUpdateOverride, not just app definition |
r = api("/api/v2/user/system/info", token=token)
nodes = r["data"]["nodes"]
references/api.mdscripts/caprover.py