DocsReferencedeploy.jsonFor AI agents
Reference

deploy.json

The plan the command inferred, and the one file your AI edits when a guess is wrong.

8 minutes to read

The first deploy writes deploy.json into the app's folder. It records everything the platform decided, with the evidence, and it is what your AI edits instead of passing flags. It contains no secrets and is safe to commit.

An edit takes effect on the next deploy, even when no other file has changed: the app is built again with the new plan.

JSON
{
  "version": 1,
  "project": { "id": "prj_...", "slug": "family-books" },
  "packageManager": "pnpm",
  "framework": "nextjs",
  "runtime": { "tier": "a", "base": "node22", "arch": "arm64", "memoryMb": 512, "timeoutSec": 30 },
  "build": { "planner": "railpack" },
  "database": { "engine": "postgres", "orm": "drizzle", "migrate": "npm run db:migrate" },
  "storage": { "enabled": true },
  "jobs": [{ "name": "monthly-report", "schedule": "0 2 1 * *", "path": "/api/reports/monthly" }],
  "secrets": { "required": ["RESEND_API_KEY"], "provided": ["DATABASE_URL", "PORT"] },
  "egress": { "allow": ["api.resend.com"] },
  "speed": { "enabled": true, "images": true, "otgr": true, "shared": ["/", "/about", "/blog/*"] },
  "languages": { "offer": ["fr", "es"] },
  "auth": { "library": "none" },
  "signals": [{ "for": "framework", "evidence": "next.config.ts present", "file": "next.config.ts" }]
}

What survives a re-deploy

Detection runs again on every deploy and follows the code, but it does not undo a person's decision. The file remembers what detection found last time (detected), so a field changed by hand stays changed; set it back to what detection found and the code decides again. Kept from the existing file:

  • project: assigned by the platform.
  • kind, framework, storage and database.engine, when changed by hand. A changed engine keeps the whole database block as written.
  • The order of the parts in services (the first is the project's main address), services[].project.id: the ids the platform gave each part, matched by folder, and whatever is set in a part's own entry.
  • build.rootDirectory, when a person picked one folder of a repository that holds several apps.
  • runtime.memoryMb, runtime.timeoutSec, runtime.start: hand-tuned resources.
  • build.command: an explicit override.
  • egress.allow, jobs and secrets.buildTime: unions. Additions are deliberate and kept.
  • database.migrate: if the engine is unchanged.
  • speed: the whole block, as written. Only a person changes it, so re-detection never turns anything back on.
  • languages: only a person decides which languages the app is read in.

Fields worth knowing

  • runtime.tier: a for ordinary request/response apps. Detection also records b when it sees something that needs a process outliving a request — a SQLite file, WebSockets, an in-process timer — but b is not yet a tier an app can run on: everything runs as a today. Treat b in a plan as a warning that the app needs rewriting (see Databases), not as a promise the platform will handle it.
  • build.command: the exact command that builds the app, for when the planner's own guess is not enough — a Django app that needs python manage.py collectstatic --noinput, a build that has to be run a particular way. It replaces the build step entirely and runs in the app's folder. Leave it out and the planner decides, which is right for most apps.
  • runtime.start: the command that starts the app's server. Same idea: leave it out unless the app is started in a way nothing could work out from its files. The app must listen on the port the platform gives it (PORT); a command that fixes its own port is refused at detection.
  • egress.allow: the outside hosts detection saw in the code, kept for information. It grants nothing. What an app may call depends on whether someone has kept it: an app nobody has kept yet can call a short list of common services over HTTPS (AI model providers, Stripe and PayPal, Supabase, Firebase and Upstash, Google, the main email providers, Twilio, GitHub, Microsoft and Apple sign-in, Sentry, PostHog, Cloudinary and Mapbox), and once the person keeps the app it can call any service with any protocol, including a database outside the platform. Ports 23, 25, 135, 137, 138, 139, 445 and 3389 are closed for every app; send email through a provider's API or port 587. See acceptable use.
  • speed.enabled: on by default. Every page gets a small script (/_uarpie/speed.js, readable in the page source) that starts loading the next page when a link is hovered, touched or scrolled into view, so the click is instant. It only ever fetches same-site links with GET, never anything that looks like an action, and it sends nothing anywhere of its own. Static files with no cache header get sensible ones. Everything the app sends with a body leaves compressed, whatever it answers; the page's own large uploads of data to the app (8 KB or more of JSON, text or a form without files) are sent compressed where they can be, and reach the app exactly as the page wrote them; data sent to the app compressed (Content-Encoding: gzip, br, zstd or deflate) reaches it unpacked, so any app accepts it; and visitors who come back download a fraction of each page; the page they see is exactly what the app sent, and search engines see an ordinary page. Set it to false only if the app needs to be served exactly as written — a strict content-security policy, or a page that must not be prefetched — and know that it also turns off the sign-in headers and translated pages, which run in the same layer. To leave out just one link, mark it with data-no-prefetch.
  • speed.otgr: on by default. A visitor who comes back sees the page the moment they open it, while the latest version loads. Only pages that are the same for everyone are kept on their device; personal pages show the site's frame instead. The site's own pictures are kept too, never longer than the site's caching headers allow: a changed picture shows at once on a good connection, and on a slow one from the next visit. If the connection drops, a page the visitor has already seen stays on screen with its pictures, and refreshes itself when the connection is back. Set it to false for an app that must only ever show a freshly loaded page. The person who owns the app can also turn this on or off in their dashboard, under Settings, and their choice wins over this file until they hand the decision back to it — so if it is not doing what this file says, check there first.
  • speed.shared: the pages of your app that look the same for every visitor, so they can open instantly for someone who has been before: ["/", "/about", "/blog/*"], or ["*"] for every page. A path ending in /* covers everything below it. Only needed when your app makes each page as it is asked for — Django, Flask, FastAPI, Laravel, Spring, Express, a Rust server. Next.js, Astro and static sites need nothing here. Never name a page that shows anything personal: a basket, an account, a dashboard, anything behind a sign-in. A page you name is shown again from that person's own device even if your app starts a session on it, as Laravel does on every page, so name only pages that show the same thing to everyone, signed out or signed in. The copy stays on that one device and is never shown to anybody else. A page answering no-store is left alone whatever this list says. A page can say it for itself instead, by answering with the header X-Uarpie-Public: 1.
  • speed.images: on by default. Every <img> pointing at a JPEG, PNG or WebP on the site gets a srcset of resized copies (384 to 1920 pixels wide, served as WebP where the browser accepts it), so a phone downloads a phone-sized picture instead of the 4 MB original. The original file is untouched and stays the fallback. Images that already have a srcset, SVGs, GIFs, and anything marked data-no-optimise are left alone. Set to false for a site that must serve its originals exactly.
  • languages.offer: other languages the site is read in, written the way a browser writes them: ["fr", "es", "pt-BR"], up to 30. Each gets its own address, /fr/about. The pages named in speed.shared (each by its address, not a * pattern) are translated when the app is deployed; everything else on screen is translated as the owner looks around the site in that language. Nobody else sees a translation until the owner publishes it in the dashboard. Needs uarpie 0.3.0 or later, a paid plan and a kept app. See Your app in other languages.
  • auth.library: if the app implements its own login, this names it and the deploy warns that the platform can replace it.
  • services: present only at the top of a repository that holds several apps. Lists each part's name and folder, after the first deploy its id, and anything set for that part alone (its start or build command, memory, migration command, jobs). See Several apps in one project.
  • signals: why each decision was made. Read these before overriding anything.
This page as Markdown: /docs/deploy-json.mdllms.txt
↑ ↓ to moveEnter to open