Step 19: Redeploy
Phase: iterate
Context
You have updated app_files. Deploy the new version.
What to do
Redeploy via the same unified primitive as Step 15 — (await r.project(id)).apply(spec) (SDK 2.0+), run402 deploy apply (CLI), or the deploy MCP tool. Two redeploy modes:
Mode 1: full replace (most common)
Send the complete site again. Any files absent from site.replace are removed from the release. Subdomain reassign is automatic — keep the same subdomains.set name and the URL stays the same.
{
"site": { "replace": { /* ALL current files */ } },
"subdomains": { "set": ["{subdomain}"] }
}
Mode 2: surgical patch (for small edits)
Patch only the files that changed. Set base: { release: "current" } to derive from the live release. Unchanged files carry forward; deleted files go in patch.delete.
{
"base": { "release": "current" },
"site": {
"patch": {
"put": { "index.html": "<!doctype html>..." },
"delete": ["old-page.html"]
}
}
}
Bytes already in CAS are not re-uploaded. Functions whose source / config didn't change are kept as-is — no Lambda churn.
SDK example (Node, @run402/sdk ^2.0.0)
const p = await r.project(env.PROJECT_ID);
const result = await p.apply({
site: { replace: fileSet },
...(env.SUBDOMAIN ? { subdomains: { set: [env.SUBDOMAIN] } } : {}),
});
const url = env.SUBDOMAIN
? `https://${env.SUBDOMAIN}.run402.com`
: result.urls.site;
The SDK auto-retries safe BASE_RELEASE_CONFLICT races when another deploy commits between your plan and commit. Don't hand-roll the retry loop.
Subdomain reassignment is automatic
Including subdomains.set: ["{subdomain}"] with the same name reassigns the subdomain to the new release atomically as part of activation. No separate API call. The subdomain_url stays the same — only the underlying release changes.
If you omit subdomains, the existing subdomain assignment carries forward unchanged.
If the deploy fails
The apply call throws Run402DeployError with a structured code and nextActions. Common cases:
INVALID_SPEC— typo in spec (e.g.site.replcae). Fix locally; nothing was uploaded.MIGRATION_CHECKSUM_MISMATCH— you edited an already-applied migration's SQL. Use a fresh migration id.MISSING_REQUIRED_SECRETwarning — declared a secret insecrets.require[]that doesn't exist. Set it first viap.secrets.set(key, value)orPOST /projects/v1/admin/{id}/secrets.BASE_RELEASE_CONFLICTwithsafe_to_retry: true— the SDK already retried; if you see this, your deploy budget was exhausted. Reapply.
What to tell the user
If subdomain exists:
"Your app has been updated! Same link as before:
{subdomain_url}
Try the changes and let me know what you think!"
If no subdomain:
"Here's your updated app:
{deployment_url}
Try the changes and let me know what you think!"
Note: If you've been iterating many times, only share the latest URL.
Expected output
release_id— New release id from this deploydeployment_url— Raw deployment URL (changes per release)subdomain_url— Unchanged if subdomain was reassigned
Memory directive
Next step
Back to Step 17: Gather Feedback → (iterate loop)