Skip to main content
PercherPercher

Troubleshooting common errors

Fixes for the errors you're most likely to hit during a deploy

A field guide to the errors you're most likely to see when running bunx percher publish. Every error name below matches what the CLI and dashboard show, so you can search for it verbatim.

Jump to
name already takenunknown runtimeport out of rangerepo size exceededbuild failedhealth check failedmissing API keyDAILY_QUOTA_EXCEEDEDDEPLOY_RATE_LIMITEDREQUIRED_ENV_MISSINGENV_KEY_UNDECLAREDRETRY_LIMIT_REACHEDalready_in_progressslow first paint (fonts)changes aren't showing

Configuration errors

"name already taken"

The app name in percher.toml is in use by another account. App names are global to the platform — they become part of the public URL (name.percher.run), so they have to be unique. Pick a different value for app.name and try again.

"unknown runtime"

The runtime field must be one of node, bun, python, static, or docker. Anything else fails parsing. See the percher.toml reference for the full list of allowed values.

"port out of range"

web.port must be between 1024 and 65535. Privileged ports below 1024 aren't supported because Percher app containers don't run as root.

"repo size exceeded"

Your project tarball is over 500 MB. Almost always this is node_modules, a stale .next / dist directory, large committed binaries, or .git history that wasn't excluded. Check what would actually be uploaded:

bunx percher publish --dry-run

Percher honors .gitignore plus a built-in default exclude list: node_modules, .git, dist, .next, .svelte-kit, build, coverage, .env*, .bun, *.log, and .DS_Store. If a file you don't need is listed in --dry-run, add it to .gitignore.

Build & startup errors

"build failed"

Your build command exited non-zero. Pull the build log to see exactly which step blew up:

bunx percher logs --build

You can also view the same log in the dashboard under the failed deploy. Common causes: a missing dev dependency, a TypeScript error that only shows up in production builds, or a build script that assumes an env var is set without declaring it (see REQUIRED_ENV_MISSING).

"health check failed"

The app started but its health endpoint either returned a non-200 status or didn't respond in time.

First, check whether the live site is actually broken. Visit https://your-app.percher.run/health in a browser. If it returns 200 and the app is responding normally, the pipeline's health-check step probably hit a transient network blip — the previous version is still serving (correct rollback behavior) and the new build was never swapped in. Update the CLI and try again:

bunx percher@latest publish

If the live site is also unhealthy, it's a real app problem. Check three things:

  1. The app is listening on the port declared in [web].port.
  2. The health endpoint exists and returns HTTP 200.
  3. The path in [web].health matches the route in your code.

"missing API key"

An environment variable your app needs at runtime isn't set in the encrypted env store. Set it with one of these (file/stdin/interactive modes are recommended for real secrets so the value never enters your shell history):

# inline (value visible in shell history — avoid for real secrets):
bunx percher env set OPENAI_API_KEY=sk-...

# from a file (recommended for secrets):
bunx percher env set OPENAI_API_KEY --from-file ./secret.txt

# from stdin:
echo "$MY_SECRET" | bunx percher env set OPENAI_API_KEY --from-stdin

# interactive prompt (hidden input, sudo-style):
bunx percher env set OPENAI_API_KEY

Then re-run bunx percher publish. Env changes only take effect on the next deploy.

Rate limits & quotas

DAILY_QUOTA_EXCEEDED (HTTP 429)

You've hit your per-day deploy cap. Defaults are: Free 50 live + 25 preview, Starter 100 / 50, Maker 200 / 100, Pro 1000 / 500. Counters reset at 00:00 UTC; the error response includes the exact resetAt timestamp.

Retrying immediately won't work — the cap is enforced until reset. Live and preview counters are independent, though, so if you've exhausted your live quota you can still preview-deploy:

bunx percher publish --preview

If you regularly hit this, upgrade at /settings.

DEPLOY_RATE_LIMITED (HTTP 429)

Per-app burst limit on a sliding 60-second window. This is different from the daily quota — it is retryable after a short wait. The error response includes a retryAfterSec field telling you how long to wait.

RETRY_LIMIT_REACHED

Percher auto-retries transient infra failures up to a per-deploy limit. When that limit is hit, the platform stops retrying and surfaces this error so you can decide what to do — usually that means trying again manually after a few minutes, or contacting support@percher.app if it persists.

already_in_progress

A previous deploy for the same app is still queued or building. Wait for it to finish (or cancel it from the dashboard) before kicking off another one. The error response includes the active deploy id so you can find it in the deploys list.

Env contract errors

percher.toml supports an explicit env contract that catches missing-env problems at upload time, before a 90-second build wastes your quota:

[env]
required = ["OPENAI_API_KEY", "DATABASE_URL"]   # must be set before deploy queues
optional = ["SENTRY_DSN"]                        # may be referenced; not required
ignore   = ["NODE_ENV"]                          # explicitly ignored by the scanner

REQUIRED_ENV_MISSING

A key listed under [env].required (or detected as required by the source scanner) isn't set in the encrypted env store. Set the missing keys and re-run publish:

bunx percher env set OPENAI_API_KEY --from-file ./key.txt

ENV_KEY_UNDECLARED

Your source code references an env key that isn't in any of required, optional, or ignore. Add it to one of those lists in percher.toml so the scanner knows how to treat it. Use required for keys the app can't boot without, optional for nice-to-have integrations, and ignore for keys the platform sets for you (like NODE_ENV).

Performance & freshness

App takes 7+ seconds to load on first visit

The app itself is almost certainly fine — this is usually third-party fonts hanging the first paint. Apps generated by Lovable, v0, shadcn, and similar starters typically load fonts directly from https://fonts.googleapis.com/css2?family=.... When the visitor's network has any trouble reaching Google's font CDN — a brief incident, an aggressive ad-blocker, a corporate firewall, a Pi-hole — the browser hangs 10–19 seconds on that one request before giving up and falling back to system fonts.

How to detect. Open DevTools → Network → reload. If you see a single fonts.googleapis.com request stuck in (pending) for many seconds before failing with ERR_FAILED, you're hitting this. Every other request (HTML, your bundle, your API) will look fast — only the Google Fonts row hangs.

Why Percher can't fix it server-side. The fonts request goes directly from the visitor's browser to Google. It doesn't pass through Percher's reverse proxy or your VPS.

Fix: self-host fonts. Best practice for production apps in any case — also avoids GDPR issues with Google-Fonts-embed and removes a cross-origin TLS handshake from the critical path.

For Next.js:

// app/layout.tsx — replace any <link href="fonts.googleapis.com/...">
import { DM_Sans, JetBrains_Mono, Inter_Tight } from "next/font/google";
const dmSans = DM_Sans({ subsets: ["latin"], display: "swap" });
// next/font downloads + self-hosts at BUILD time. No runtime call to Google.
// Apply via <html className={dmSans.className}>.

For Vite, SvelteKit, Astro, or Solid:

bun add @fontsource/dm-sans @fontsource/jetbrains-mono
/* in app.css or main.tsx */
@import "@fontsource/dm-sans/400.css";
@import "@fontsource/dm-sans/500.css";
@import "@fontsource/jetbrains-mono/400.css";

For plain CSS / static sites: download the font files from fonts.google.com, drop them in public/fonts/, and reference them via @font-face with src: url('/fonts/...woff2').

After redeploying, the first visit may still hang once if a stale service worker is cached — tell affected users to unregister via DevTools → Application → Service Workers, or just hard-reload twice.

"my changes aren't showing up after deploy"

The build pipeline has an image cache that skips a full rebuild when the build inputs haven't changed (keyed on a content hash that includes percher.toml, package.json, the lockfile, Dockerfile, build env, and every source file in the tarball). If a deploy reports Build cache: hit and you expected new behavior, force a fresh build:

bunx percher publish --no-cache

The freshly-built image gets registered into the cache so future deploys still benefit. Every successful publish prints either Build cache: hit or Build cache: miss (fresh build) so you can tell at a glance which path ran.

If --no-cache still shows the same stale behavior, the source bytes themselves aren't reaching the upload. Check that you're running publish from the project root (not a subdirectory) and that .gitignore isn't excluding the changed files.

Still stuck?

Email support@percher.app with the deploy id (visible in the dashboard or printed by the CLI on failure) and the build log if you have one. We reply same-day on paid plans.

PrevZero-downtime deploysNextMulti-instance & auto-scaling
Troubleshooting common errors — Percher docs