Troubleshooting & getting help
When something goes wrong, work it in this order: read the structured error, read the logs, read the relevant guide, then escalate. This page covers the common failures and how to recover from each.
Start with the error code, not the message
Every tool returns structured output. Failures carry a machine-readable code you can branch on, so don't parse the human message. That code arrives on one of two channels — check both:
- Error result (most tools). The call fails as an MCP error whose body is
{ "error": <message>, "code": <machine-code> }. Branch oncode(e.g.not_authorized,not_found,upgrade_required,pool_exhausted,internal_error). - In-band result (tools that model expected failure as data). The call succeeds at the transport level and returns a discriminated object:
{ ok: true, ... }on success or{ ok: false, errorCode, message, ... }on an expected, recoverable failure. Branch onokfirst, then onerrorCode. Object storage, backups, database tools, previews, andset_app_protectionuse this shape. (attach_domainalso carries the same failure under its original field names,code/reason— both pairs point at the same values, so branch on whichever spelling you already know.)
Multi-step tools (start_project, import_repo) are in-band and return { ok: false, failedAt, partial } so you can resume from the exact step that failed rather than starting over.
A deploy failed (status: "error")
The build or deploy didn't complete. Read the build log:
get_logs({ applicationId, source: "build" })
This is the output of building and shipping the image: compile errors, failed installs, a bad Dockerfile. Fix in the repo, then push_files with deploy: { applicationId } (or call deploy) to ship the fix. See deploymill://guides/logs for the full failed-deploy loop and log filtering (tail / grep / level / since).
The deploy succeeded but the app misbehaves
The image is running but the app is wrong at runtime. Read the container's own stdout/stderr:
get_logs({ applicationId, source: "runtime" })
(Runtime logs are read straight from the compute backend. If the app isn't running you get an empty result with a note instead of an error.)
The deploy says done but my new code isn't live (rolledOver: false)
A rebuild can produce a byte-identical image: when every build layer is a Docker cache hit, the new image has the same digest as the last one, so the platform never recreates the container and your latest commit never goes live. deploy detects this and returns rolledOver: false with a rolloverNote. A healthy edge probe here is the old container answering, so don't read done as proof the new code shipped.
To fix it, force a clean rebuild that ignores the build cache:
deploy({ applicationId, noCache: true })
This rebuilds every layer from scratch (slower), so a real source change always produces a new image. Confirm rolledOver: true (or a changed imageDigest) on the response. DeployMill restores fast cached builds automatically afterward. If it still comes back rolledOver: false, the source the platform built didn't actually change, so check that your commit was pushed to the deployed branch.
The health gate is failing / auto-rollback fired
Deploy, rollback, and auto-rollback all key off one health endpoint (default /healthz) that must return 200 iff fully healthy. Anything else (non-200, connection error, timeout) counts as unhealthy. If deploys keep rolling back, your health handler is the first suspect: make sure it actually returns 200 once the app is ready (DB reachable, migrations run). Full contract in deploymill://guides/health.
Auto-rollback only ever fires on the rollout — the new container going live and failing its health gate, or never coming up at all. A deploy that fails at the build stage (a broken build, or a registry push that couldn't complete) never swapped a container, so whatever was serving keeps serving and nothing is reverted; the deploy reports autoRollback: { attempted: false, reason: "build_failed_no_rollout" } and you fix the build instead.
Did something roll my app back?
The Deployments tab is the record. Every build appears there, including the ones that failed, and a deploy created by a rollback is badged rolled back (you or your agent asked for it) or auto-rolled back (the health gate fired on its own). Above the table, Live now names the version actually serving — and says outright when that isn't the most recent deploy, which is the signature of a rollback or of a later deploy that failed. Over MCP the same facts are on list_deployments: live: true marks the serving record, and a rollbackOf: { toDeploymentId, fromDeploymentId, trigger } block marks any record a rollback created.
Each row links to that deploy's build log, so the output of the deploy that actually failed stays readable after something else deploys on top of it. Build logs are kept for the 10 most recent deploys; over MCP, pass deploymentId to get_logs.
Common error codes
| Code | Meaning | What to do |
|---|---|---|
domain_verification_required | A custom domain's ownership isn't proven yet | Publish the DNS TXT record the error carries (txtRecord), then retry |
dns_not_pointed | A custom domain's DNS isn't pointed at the ingress yet | Create the CNAME/A record the error reports (expected.value), unproxied, then retry |
invalid_hostname / reserved_hostname | The host is malformed or reserved | Choose a different host |
host_claimed | Another org already verified that host | Use a host you control |
active_app_limit_reached | Your org is at its app quota | Delete an unused app or check get_account |
preview_app_limit_reached | Too many active previews at once. Your plan sets this number outright (Explore 1, Builder 10, Studio 20), separately from the app quota | Let previews expire or delete some |
storage_limit_reached | A volume would exceed the storage quota | Lower the requested size or free space |
pool_exhausted | The org has used up its monthly awake-compute pool (Explore 150 GB-hours, Builder 2,000, Studio 10,000). New app creation and waking a sleeping app or preview are refused. A running prod app keeps serving until the 125% hard ceiling | Sleep idle apps and previews to free headroom, wait for the reset on the 1st (UTC), or move up a tier. Never a billing event |
database_limit_reached | The managed database is over its size ceiling (maxDatabaseGb) and the grace window has elapsed | Drop unused data or apps, or ask an operator to raise the ceiling. Data is never deleted |
build_queue_full | The platform-wide free-tier build pool is full (5 concurrent free builds). Paid builds are never subject to it | Retry after the retryAfter hint the error carries, or upgrade |
cluster_at_capacity | Not about your account. DeployMill itself has no node with room to run the app right now, so a deploy couldn't be placed or a sleeping app couldn't wake. Nothing is wrong with your code, and no plan changes this | Wait the retryAfterSeconds the error carries and retry the identical call. Don't rebuild or shrink the app. Anything already serving keeps serving |
Call get_account for a read-only view of your quota headroom before a workflow.
DNS and custom domains
Attaching a domain you own takes two DNS records, and the tools hand you the exact value for each, so you don't have to look them up:
- Prove you own it. The first attach returns a
domain_verification_requirederror carrying a TXT record (_deploymill-challenge.<host>= a token). Publish it, then retry. One-time per host. You can remove it after the attach succeeds. - Point it at the ingress. Once ownership is proven, the attach checks the host resolves to DeployMill. If not, a
dns_not_pointederror reports the target. CNAME a subdomain at it, or A-record an apex at its IP. The record must be DNS-only / unproxied (no Cloudflare orange cloud / CDN in front), or the automatic Let's Encrypt cert can't issue.
DeployMill issues the TLS certificate for you, and you never supply one. Issuance isn't instant, so confirm https://<host> actually serves before assuming it's done. Declare the host under domains.custom in .deploymill/project.json and run reconcile_project (rather than a one-off attach_domain) so it survives future reconciles. Full playbook: deploymill://guides/domains.
When the docs run out
If you've read the structured error, the logs, and the relevant guide and you're still stuck, escalate. Use the support avenue surfaced in your account, and include the failing tool, the code, and the applicationId. That context is what lets a request be answered quickly. Never paste secrets or connection strings into a support request.