Reading logs reference (deploymill)
get_logs returns an app's logs in two flavors, selected with source: build/deploy logs (source: "build", the default) and runtime container logs (source: "runtime"). Both come back as raw logs text plus parsed, filterable entries.
source: "build"is the first thing to reach for when adeploycomes back withstatus: "error": that bare status tells you the build failed, and the build log tells you why.source: "runtime"is what you want when the deploy succeeded but the app misbehaves once it's live (500s, crashes on a request) — it's the running container's own stdout/stderr.
What source: "build" covers
- Build logs —
docker build/ buildpack output: dependency installs, compile errors, missing files, failedRUNsteps. - Deploy logs — the platform pushing the image and starting the service: the steps Dokploy runs around your container.
These are stored as files on the host; the build source tails the file and returns the text. Under the hood it resolves a deployment, then reads its log content via Dokploy's REST surface.
What source: "runtime" covers
- Runtime / container logs — your app's own stdout/stderr once it's running (request logs,
console.log, stack traces from a live request), aggregated across all replicas of the service and timestamped.
Dokploy has no REST surface for these (they're WebSocket-only), so deploymill reads them through a small host-side log-reader sidecar that talks to the Docker Swarm API (see sidecar/README.md). This is an opt-in piece of infrastructure:
- When the sidecar is configured (the server has
RUNTIME_LOGS_URL+RUNTIME_LOGS_TOKEN),source: "runtime"returns parsed runtime entries just like the build source. - When it is not configured, the call returns
{ configured: false }with anoterather than erroring — branch onconfigured. Fall back to the edge-probe signal fromdeploy/rollback(theedges/edgeNotefields) and a readiness route (probePath) to narrow down the runtime issue. - If the app isn't running (stopped / never deployed), you get a
service_not_found-stylenoteand empty entries — checkget_app_health, then deploy or start it.
Usage
get_logs({ applicationId }) → latest deployment, last 200 lines, parsed
get_logs({ applicationId, tail: 1000 }) → last 1000 lines of the latest deployment
get_logs({ applicationId, deploymentId, tail: 500 }) → a specific historical deployment
get_logs({ applicationId, level: "error+" }) → only error/fatal entries
get_logs({ applicationId, grep: "npm ERR|ENOENT" }) → lines matching a regex (case-insensitive)
get_logs({ applicationId, grep: "out of memory", grepRegex: false }) → literal substring
get_logs({ applicationId, since: "2026-05-30T23:00:00Z" }) → entries at/after an instant
get_logs({ applicationId, source: "runtime" }) → running container stdout/stderr, last 200 lines
get_logs({ applicationId, source: "runtime", level: "error+", tail: 1000 }) → runtime errors only
- Omit
deploymentIdto read the most recent deployment — the usual case right after a failed deploy. (deploymentIdis ignored forsource: "runtime", which reads the live service.) - All the filters (
grep/level/since) andtailwork identically across both sources — same parser. tailbounds how many trailing lines are fetched (default200, max10000) before filtering. Build logs can be long; start small and raisetailif the error is scrolled off the top.- Get a specific
deploymentIdfromlist_deploymentswhen you want an older build (e.g. comparing the last good deploy against the broken one).
Filtering
All three filters are best-effort and applied server-side over the fetched window:
grep— matched against the raw line, case-insensitive. A regex by default; an invalid pattern silently falls back to a substring match. SetgrepRegex: falseto force a literal substring.level— keep only entries whose level was parsed off the line. Accepts a single level (trace/debug/info/warn/error/fatal) orwarn+/error+for "that level or worse". Lines with no detectable level are dropped when this is set.since— keep entries timestamped at/after an ISO instant. Lines with no parseable timestamp are kept (we can't prove they're older).
Return shape
{
"deploymentId": "…",
"status": "error",
"title": "…",
"tail": 200,
"logs": "…raw build output…",
"entries": [
{ "line": "2026-05-30T23:12:18Z ERROR npm ERR! missing script: build",
"ts": "2026-05-30T23:12:18.000Z", "level": "error",
"message": "ERROR npm ERR! missing script: build" }
],
"total": 200,
"matched": 1,
"truncated": true
}
logsis the raw log text (unchanged contract). An empty string means the log file is missing or not yet written (the deploy may still be starting, or the log rotated) — the response adds anoteflagging that.entriesis the parsed, filtered view: one object per non-blank line, with a best-efforttsandlevelwhen they can be detected, andmessage(the line minus any leading timestamp). Branch on these instead of regexinglogs.totalis how many entries were parsed from the fetched window;matchedis how many passed the filters (and equalsentries.length).truncatedistruewhen the fetched window was completely full (total >= tail), i.e. older lines almost certainly exist — raisetailto see them. (The REST surface gives no exact line total, so this is a heuristic.)- If the app has no deployments yet,
deploymentIdisnull,entriesis[], and anotesays to deploy first.
For source: "runtime" the shape is the same logs/entries/total/matched/truncated, but instead of deploymentId/status/title you get source: "runtime", serviceName (the Swarm service read), and configured (a boolean — false means no log-reader sidecar is set up, with a note explaining the fallback).
The failed-deploy loop
1. deploy({ applicationId }) → status: "error" (+ failNote pointing here)
2. get_logs({ applicationId }) → read the build output, find the failing step
3. fix the cause (Dockerfile, deps, source) and push
4. deploy({ applicationId }) again → repeat until status: "done"
start_project and deploy both surface this pointer on failure, so an agent that hits an error status knows to call get_logs next instead of guessing.
Troubleshooting
logsis empty right after triggering a deploy → the build hasn't written to the file yet. Wait fordeployto reach a terminal status (done/error), then read.- Error is cut off at the top → raise
tail. - Deploy succeeded but the app 502s / throws on requests → that's a runtime problem, not a build one. Read
get_logs({ applicationId, source: "runtime" })for the container's own stdout/stderr. If that comes back{ configured: false }, the server has no log-reader sidecar — fall back to the edge probe and your app's readiness route.