From d618e2ad1581ff3e86f2e326e6b3d9a9ae285a23 Mon Sep 17 00:00:00 2001 From: alitariksahin Date: Mon, 21 Sep 2026 15:29:00 +0300 Subject: [PATCH 1/5] DX-3029: Document wake-on-request URLs and init commands on every box Init commands were documented only inside keep-alive, which is no longer where they belong: a startup script now runs on every box. They get their own page under Lifecycle, and keep-alive links to it instead of owning it. Corrects three things that were wrong rather than merely incomplete: Public URLs do not expire when a box is paused. They survive sleep and are removed only when the box is deleted. A box restored from a snapshot does not inherit the snapshot's init command. The backend clears the stored script when no init command is supplied, so the caller has to pass it again on the restore. The wake wait is bounded at 30 seconds and then answers 503 with Retry-After, so the page no longer promises the app's response unconditionally. Every request example sets a client timeout longer than the cold-start window, since httpx defaults to five seconds. --- box/overall/how-it-works.mdx | 4 + box/overall/init-commands.mdx | 199 ++++++++++++++++++++ box/overall/keep-alive.mdx | 35 +--- box/overall/preview.mdx | 89 ++++++++- box/overall/quickstart.mdx | 2 +- docs.json | 1 + llms-full.txt | 329 +++++++++++++++++++++++++++++----- llms.txt | 1 + 8 files changed, 580 insertions(+), 80 deletions(-) create mode 100644 box/overall/init-commands.mdx diff --git a/box/overall/how-it-works.mdx b/box/overall/how-it-works.mdx index 218de5639..a8b295a78 100644 --- a/box/overall/how-it-works.mdx +++ b/box/overall/how-it-works.mdx @@ -88,6 +88,8 @@ A box retains its full state between runs (files, installed packages, git histor When you create a box, Upstash provisions a new isolated container with its own filesystem, shell, and network stack. You can start from a fresh box or restore from a snapshot. Once provisioning finishes, the box is ready to receive commands. +If the box has an [init command](/box/overall/init-commands), the box runs it once after the container starts, on create, on resume, and after a snapshot restore. + ### 2. Running The box automatically enters Running state after creation. Your agent can run bash commands, read and write files, interact with git, and make outbound network requests. `stdout` and `stderr` stream back in real-time. @@ -248,6 +250,8 @@ box.resume() Paused boxes do not accrue active CPU charges. Pause/resume is not available when `keepAlive` is enabled. +Resuming reruns the box's [init command](/box/overall/init-commands), so a server started that way comes back with the box. + ### Snapshot and restore Snapshots are the best way to turn a prepared environment into a reusable starting point, especially installing dependencies. diff --git a/box/overall/init-commands.mdx b/box/overall/init-commands.mdx new file mode 100644 index 000000000..a772d64bd --- /dev/null +++ b/box/overall/init-commands.mdx @@ -0,0 +1,199 @@ +--- +title: "Box Init Commands" +--- + +An init command is a startup script stored on the box. Every box can have one, and the box runs it once each time its container starts: after create, after resume, and after a snapshot restore. + +Use it to bring the environment back to a working state without sending the same commands again, for example starting a web server, launching a background process, or warming a cache. + +--- + +## Set an init command at creation + +Pass `initCommand` when creating the box: + + +```typescript box.ts +import { Box } from "@upstash/box" + +const box = await Box.create({ + runtime: "node", + initCommand: "npm install && npm run dev", +}) +``` + +```python box.py +from upstash_box import Box + +box = Box.create( + runtime="node", + init_command="npm install && npm run dev", +) +``` + + +This works on any box. It is not limited to [keep-alive](/box/overall/keep-alive) boxes. + +The command runs from `/workspace/home`, so it assumes a project is already there: clone a repository with [git](/box/overall/git), restore a [snapshot](/box/overall/snapshots), or write the files yourself before the box starts. The end-to-end example below does the last of those. + +A box restored from a snapshot does not inherit the snapshot's init command, so pass it again on the restore: + + +```typescript box.ts +const box = await Box.fromSnapshot("snap_abc123", { + initCommand: "npm run dev", +}) +``` + +```python box.py +box = Box.from_snapshot( + "snap_abc123", + init_command="npm run dev", +) +``` + + +--- + +## Manage the init command later + +Get, set, and delete work on any box, including a paused one. + + +```typescript box.ts +await box.setInitCommand("npm run dev") + +const command = await box.getInitCommand() +console.log(command) // "npm run dev" + +await box.deleteInitCommand() +``` + +```python box.py +box.set_init_command("npm run dev") + +command = box.get_init_command() +print(command) # "npm run dev" + +box.delete_init_command() +``` + + +On a paused box the change is persisted right away and applied the next time the box resumes. The box is not woken up to accept it. + + +```typescript box.ts +await box.pause() + +// Persisted now, applied on the next resume +await box.setInitCommand("npm run start") + +await box.resume() // runs "npm run start" +``` + +```python box.py +box.pause() + +# Persisted now, applied on the next resume +box.set_init_command("npm run start") + +box.resume() # runs "npm run start" +``` + + +--- + +## When it runs + +| Event | Init command runs | +| --- | --- | +| Box created | Yes | +| Box resumed from paused | Yes | +| Box restored from a snapshot | Yes | +| `exec.command()` or an agent run | No | +| Already running container | No, it does not run twice in one container lifetime | + +The init command runs once per container start. It is not re-run on every command you send, and a second trigger within the same container lifetime is a no-op. If you need the command to run again without restarting the box, run it yourself with [`box.exec`](/box/overall/shell). + +The command runs in the background, so box creation and resume do not block on it finishing. A long install keeps running while you send other work to the box. + +--- + +## Pairing with public URLs + +An init command is what makes a paused box useful again behind a [public URL](/box/overall/preview). If the public URL was created with wake on request, an incoming HTTP request resumes the box, the init command starts the server again, and the request is held until the port is listening. + + +```typescript box.ts +import { Box } from "@upstash/box" + +const box = await Box.create({ runtime: "node" }) + +// The init command runs a file, so the file has to exist first. +await box.files.write({ + path: "server.js", + content: `require("http").createServer((_, res) => res.end("ok")).listen(3000)`, +}) + +await box.setInitCommand("node server.js") + +const publicUrl = await box.getPublicURL(3000, { + wakeOnRequest: true, + bearerToken: true, +}) + +await box.pause() + +// Resumes the box, reruns the init command, then answers. +// Allow for the cold start: the wake wait is bounded at 30 seconds. +const response = await fetch(publicUrl.url, { + headers: { Authorization: `Bearer ${publicUrl.token}` }, + signal: AbortSignal.timeout(60_000), +}) +``` + +```python box.py +import httpx +from upstash_box import Box + +box = Box.create(runtime="node") + +# The init command runs a file, so the file has to exist first. +box.files.write( + path="server.js", + content='require("http").createServer((_, res) => res.end("ok")).listen(3000)', +) + +box.set_init_command("node server.js") + +public_url = box.get_public_url( + 3000, + wake_on_request=True, + bearer_token=True, +) + +box.pause() + +# Resumes the box, reruns the init command, then answers. +# httpx defaults to a 5 second timeout, which a cold start can outlast. +response = httpx.get( + public_url.url, + headers={"Authorization": f"Bearer {public_url.token}"}, + timeout=60.0, +) +``` + + +--- + +## Console + +In the Upstash Console you can set an **Init Command** while creating a box, and change or remove it later from the box settings page. + +--- + +## Notes + +- An init command is capped at 64KB. +- Deleting the init command removes the stored script. Anything it already started keeps running until the container stops. +- Snapshots capture the init command, but a box restored from a snapshot does not inherit it. Pass `initCommand` on the restore, or the stored script is cleared and nothing runs at startup. diff --git a/box/overall/keep-alive.mdx b/box/overall/keep-alive.mdx index 684016d75..ed310ef35 100644 --- a/box/overall/keep-alive.mdx +++ b/box/overall/keep-alive.mdx @@ -67,7 +67,7 @@ If you do not need the box to remain continuously available, keep `keepAlive` di ## Init command -Boxes with `keepAlive` enabled can run a startup command whenever the box starts. +A keep-alive box can run an init command, a startup script the box runs once each time its container starts. ```typescript box.ts @@ -87,36 +87,7 @@ box = Box.create( ``` -This is useful for: - -- starting a web server -- launching a background process -- preparing a long-running agent environment -- restoring a development workflow automatically after the box starts - -You can also manage the init command after creation: - - -```typescript box.ts -await box.setInitCommand("npm run dev") - -const command = await box.getInitCommand() -await box.deleteInitCommand() - -console.log(box.keepAlive) // true -``` - -```python box.py -box.set_init_command("npm run dev") - -command = box.get_init_command() -box.delete_init_command() - -print(box.keep_alive) # True -``` - - -Init command management is only available when `keepAlive` is enabled. +Init commands are not a keep-alive feature. Every box can have one, and get, set, and delete work on any box, including a paused one. See [Init Commands](/box/overall/init-commands) for the full behavior. --- @@ -126,7 +97,7 @@ In the Upstash Console you can: - enable **Keep alive** while creating a box - choose the box **Size** -- manage the **Init Command** later from the box settings page +- manage the **Init Command** later from the box settings page (available on every box, not only keep-alive ones) --- diff --git a/box/overall/preview.mdx b/box/overall/preview.mdx index c2cb4937e..ba795cb56 100644 --- a/box/overall/preview.mdx +++ b/box/overall/preview.mdx @@ -196,6 +196,40 @@ print(public_url.password) # -> "f0f145f0..." ``` +#### With Wake on Request + +Add `wakeOnRequest: true` (`wake_on_request` in Python and the REST API) to let an incoming HTTP request resume a paused box. Defaults to `false`. See [Wake on Request](#wake-on-request) for the full behavior and the billing caveat. + + +```typescript box.ts +const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true }) + +console.log(publicUrl.wake_on_request) // → true +``` + +```python box.py +public_url = box.get_public_url(3000, wake_on_request=True) + +print(public_url.wake_on_request) # -> True +``` + + +Wake on request combines with either authentication method, which is the recommended setup: + + +```typescript box.ts +const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true, bearerToken: true }) + +console.log(publicUrl.token) // → "63d8b153..." +``` + +```python box.py +public_url = box.get_public_url(3000, wake_on_request=True, bearer_token=True) + +print(public_url.token) # -> "63d8b153..." +``` + + --- ### List Public URLs @@ -208,8 +242,10 @@ const { publicURLs } = await box.listPublicURLs() console.log(publicURLs) // [ -// { url: "https://{BOX_ID}-3000.preview.box.upstash.com", port: 3000 }, -// { url: "https://{BOX_ID}-8080.preview.box.upstash.com", port: 8080 }, +// { id: "{BOX_ID}-3000", port: 3000, url: "https://{BOX_ID}-3000.preview.box.upstash.com", +// created_at: 1789990404, basic_auth: false, bearer_token: false, wake_on_request: true }, +// { id: "{BOX_ID}-8080", port: 8080, url: "https://{BOX_ID}-8080.preview.box.upstash.com", +// created_at: 1789990512, basic_auth: false, bearer_token: true, wake_on_request: false }, // ] ``` @@ -218,8 +254,8 @@ result = box.list_public_urls() print(result["public_urls"]) # [ -# PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000), -# PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080), +# PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000, wake_on_request=True), +# PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080, wake_on_request=False), # ] ``` @@ -272,9 +308,48 @@ public_url2 = box.get_public_url(3000, bearer_token=True) ### Public URL Lifecycle -Public URLs expire automatically when: -- The box is paused -- The box is deleted +A public URL outlives a pause. While the box is paused, a request to the URL returns a "box is sleeping" response unless the public URL was created with wake on request. + +Public URLs are removed when the box is deleted. + +### Wake on Request + +A public URL created with `wakeOnRequest: true` resumes the box on an incoming HTTP request. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. With `wakeOnRequest: false` (the default), the same request gets a "box is sleeping" response and the box stays paused. + +The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/box/overall/init-commands). + +Set a client timeout longer than that window. Both examples below use library defaults, and some clients default to less than the cold start takes. + + +```typescript box.ts +const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true }) + +await box.pause() + +// This request resumes the box and returns the app's response +const response = await fetch(publicUrl.url, { signal: AbortSignal.timeout(60_000) }) +console.log(response.status) // 200, or 503 if the app is still starting +``` + +```python box.py +import httpx + +public_url = box.get_public_url(3000, wake_on_request=True) + +box.pause() + +# This request resumes the box and returns the app's response. +# httpx defaults to a 5 second timeout, which a cold start can outlast. +response = httpx.get(public_url.url, timeout=60.0) +print(response.status_code) # 200, or 503 if the app is still starting +``` + + + +Wake on request means anyone who can reach the URL can start the box, and therefore cause compute charges. That is why it is off by default. Pair it with `bearerToken` or `basicAuth` so only callers holding the credentials can wake the box. + + +For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/box/overall/init-commands) does that: the box runs it once every time the container starts, including after a resume. ### Auto-Resume diff --git a/box/overall/quickstart.mdx b/box/overall/quickstart.mdx index 7dd439688..924d2f168 100644 --- a/box/overall/quickstart.mdx +++ b/box/overall/quickstart.mdx @@ -77,7 +77,7 @@ box = Box.create(runtime="node") Your box is ready to use! You can already use it as a standalone, secure, isolated sandbox with full shell access, git, and filesystem operations. - You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions and can run an `initCommand` at startup. See [Keep Alive](/box/overall/keep-alive). + You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions instead of auto-pausing. See [Keep Alive](/box/overall/keep-alive). Any box, keep-alive or not, can run an `initCommand` each time it starts. See [Init Commands](/box/overall/init-commands). --- diff --git a/docs.json b/docs.json index b91c2a740..7c155f8b7 100644 --- a/docs.json +++ b/docs.json @@ -2134,6 +2134,7 @@ "box/overall/preview", "box/overall/schedules", "box/overall/keep-alive", + "box/overall/init-commands", "box/overall/ephemeral-box" ] }, diff --git a/llms-full.txt b/llms-full.txt index 92e4bcdf4..57d2988f0 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -9493,6 +9493,8 @@ A box retains its full state between runs (files, installed packages, git histor When you create a box, Upstash provisions a new isolated container with its own filesystem, shell, and network stack. You can start from a fresh box or restore from a snapshot. Once provisioning finishes, the box is ready to receive commands. +If the box has an [init command](/docs/box/overall/init-commands), the box runs it once after the container starts, on create, on resume, and after a snapshot restore. + ### 2. Running The box automatically enters Running state after creation. Your agent can run bash commands, read and write files, interact with git, and make outbound network requests. `stdout` and `stderr` stream back in real-time. @@ -9653,6 +9655,8 @@ box.resume() Paused boxes do not accrue active CPU charges. Pause/resume is not available when `keepAlive` is enabled. +Resuming reruns the box's [init command](/docs/box/overall/init-commands), so a server started that way comes back with the box. + ### Snapshot and restore Snapshots are the best way to turn a prepared environment into a reusable starting point, especially installing dependencies. @@ -9737,6 +9741,205 @@ Boxes are available in three sizes: For exact pricing details, see the [pricing page](https://upstash.com/pricing/box). For keep-alive behavior, see [Keep Alive](/docs/box/overall/keep-alive). +# Box Init Commands +Source: https://upstash.com/docs/box/overall/init-commands + +An init command is a startup script stored on the box. Every box can have one, and the box runs it once each time its container starts: after create, after resume, and after a snapshot restore. + +Use it to bring the environment back to a working state without sending the same commands again, for example starting a web server, launching a background process, or warming a cache. + +*** + +## Set an init command at creation + +Pass `initCommand` when creating the box: + + +```typescript box.ts +import { Box } from "@upstash/box" + +const box = await Box.create({ + runtime: "node", + initCommand: "npm install && npm run dev", +}) +``` + +```python box.py +from upstash_box import Box + +box = Box.create( + runtime="node", + init_command="npm install && npm run dev", +) +``` + + +This works on any box. It is not limited to [keep-alive](/docs/box/overall/keep-alive) boxes. + +The command runs from `/workspace/home`, so it assumes a project is already there: clone a repository with [git](/docs/box/overall/git), restore a [snapshot](/docs/box/overall/snapshots), or write the files yourself before the box starts. The end-to-end example below does the last of those. + +A box restored from a snapshot does not inherit the snapshot's init command, so pass it again on the restore: + + +```typescript box.ts +const box = await Box.fromSnapshot("snap_abc123", { + initCommand: "npm run dev", +}) +``` + +```python box.py +box = Box.from_snapshot( + "snap_abc123", + init_command="npm run dev", +) +``` + + +*** + +## Manage the init command later + +Get, set, and delete work on any box, including a paused one. + + +```typescript box.ts +await box.setInitCommand("npm run dev") + +const command = await box.getInitCommand() +console.log(command) // "npm run dev" + +await box.deleteInitCommand() +``` + +```python box.py +box.set_init_command("npm run dev") + +command = box.get_init_command() +print(command) # "npm run dev" + +box.delete_init_command() +``` + + +On a paused box the change is persisted right away and applied the next time the box resumes. The box is not woken up to accept it. + + +```typescript box.ts +await box.pause() + +// Persisted now, applied on the next resume +await box.setInitCommand("npm run start") + +await box.resume() // runs "npm run start" +``` + +```python box.py +box.pause() + +# Persisted now, applied on the next resume +box.set_init_command("npm run start") + +box.resume() # runs "npm run start" +``` + + +*** + +## When it runs + +| Event | Init command runs | +| --- | --- | +| Box created | Yes | +| Box resumed from paused | Yes | +| Box restored from a snapshot | Yes | +| `exec.command()` or an agent run | No | +| Already running container | No, it does not run twice in one container lifetime | + +The init command runs once per container start. It is not re-run on every command you send, and a second trigger within the same container lifetime is a no-op. If you need the command to run again without restarting the box, run it yourself with [`box.exec`](/docs/box/overall/shell). + +The command runs in the background, so box creation and resume do not block on it finishing. A long install keeps running while you send other work to the box. + +*** + +## Pairing with public URLs + +An init command is what makes a paused box useful again behind a [public URL](/docs/box/overall/preview). If the public URL was created with wake on request, an incoming HTTP request resumes the box, the init command starts the server again, and the request is held until the port is listening. + + +```typescript box.ts +import { Box } from "@upstash/box" + +const box = await Box.create({ runtime: "node" }) + +// The init command runs a file, so the file has to exist first. +await box.files.write({ + path: "server.js", + content: `require("http").createServer((_, res) => res.end("ok")).listen(3000)`, +}) + +await box.setInitCommand("node server.js") + +const publicUrl = await box.getPublicURL(3000, { + wakeOnRequest: true, + bearerToken: true, +}) + +await box.pause() + +// Resumes the box, reruns the init command, then answers. +// Allow for the cold start: the wake wait is bounded at 30 seconds. +const response = await fetch(publicUrl.url, { + headers: { Authorization: `Bearer ${publicUrl.token}` }, + signal: AbortSignal.timeout(60_000), +}) +``` + +```python box.py +import httpx +from upstash_box import Box + +box = Box.create(runtime="node") + +# The init command runs a file, so the file has to exist first. +box.files.write( + path="server.js", + content='require("http").createServer((_, res) => res.end("ok")).listen(3000)', +) + +box.set_init_command("node server.js") + +public_url = box.get_public_url( + 3000, + wake_on_request=True, + bearer_token=True, +) + +box.pause() + +# Resumes the box, reruns the init command, then answers. +# httpx defaults to a 5 second timeout, which a cold start can outlast. +response = httpx.get( + public_url.url, + headers={"Authorization": f"Bearer {public_url.token}"}, + timeout=60.0, +) +``` + + +*** + +## Console + +In the Upstash Console you can set an **Init Command** while creating a box, and change or remove it later from the box settings page. + +*** + +## Notes + +* An init command is capped at 64KB. +* Deleting the init command removes the stored script. Anything it already started keeps running until the container stops. +* Snapshots capture the init command, but a box restored from a snapshot does not inherit it. Pass `initCommand` on the restore, or the stored script is cleared and nothing runs at startup. + # Keep Alive Source: https://upstash.com/docs/box/overall/keep-alive @@ -9805,7 +10008,7 @@ If you do not need the box to remain continuously available, keep `keepAlive` di ## Init command -Boxes with `keepAlive` enabled can run a startup command whenever the box starts. +A keep-alive box can run an init command, a startup script the box runs once each time its container starts. ```typescript box.ts @@ -9825,36 +10028,7 @@ box = Box.create( ``` -This is useful for: - -* starting a web server -* launching a background process -* preparing a long-running agent environment -* restoring a development workflow automatically after the box starts - -You can also manage the init command after creation: - - -```typescript box.ts -await box.setInitCommand("npm run dev") - -const command = await box.getInitCommand() -await box.deleteInitCommand() - -console.log(box.keepAlive) // true -``` - -```python box.py -box.set_init_command("npm run dev") - -command = box.get_init_command() -box.delete_init_command() - -print(box.keep_alive) # True -``` - - -Init command management is only available when `keepAlive` is enabled. +Init commands are not a keep-alive feature. Every box can have one, and get, set, and delete work on any box, including a paused one. See [Init Commands](/docs/box/overall/init-commands) for the full behavior. *** @@ -9864,7 +10038,7 @@ In the Upstash Console you can: * enable **Keep alive** while creating a box * choose the box **Size** -* manage the **Init Command** later from the box settings page +* manage the **Init Command** later from the box settings page (available on every box, not only keep-alive ones) *** @@ -10696,6 +10870,40 @@ print(public_url.password) # -> "f0f145f0..." ``` +#### With Wake on Request + +Add `wakeOnRequest: true` (`wake_on_request` in Python and the REST API) to let an incoming HTTP request resume a paused box. Defaults to `false`. See [Wake on Request](#wake-on-request) for the full behavior and the billing caveat. + + +```typescript box.ts +const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true }) + +console.log(publicUrl.wake_on_request) // → true +``` + +```python box.py +public_url = box.get_public_url(3000, wake_on_request=True) + +print(public_url.wake_on_request) # -> True +``` + + +Wake on request combines with either authentication method, which is the recommended setup: + + +```typescript box.ts +const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true, bearerToken: true }) + +console.log(publicUrl.token) // → "63d8b153..." +``` + +```python box.py +public_url = box.get_public_url(3000, wake_on_request=True, bearer_token=True) + +print(public_url.token) # -> "63d8b153..." +``` + + *** ### List Public URLs @@ -10708,8 +10916,10 @@ const { publicURLs } = await box.listPublicURLs() console.log(publicURLs) // [ -// { url: "https://{BOX_ID}-3000.preview.box.upstash.com", port: 3000 }, -// { url: "https://{BOX_ID}-8080.preview.box.upstash.com", port: 8080 }, +// { id: "{BOX_ID}-3000", port: 3000, url: "https://{BOX_ID}-3000.preview.box.upstash.com", +// created_at: 1789990404, basic_auth: false, bearer_token: false, wake_on_request: true }, +// { id: "{BOX_ID}-8080", port: 8080, url: "https://{BOX_ID}-8080.preview.box.upstash.com", +// created_at: 1789990512, basic_auth: false, bearer_token: true, wake_on_request: false }, // ] ``` @@ -10718,8 +10928,8 @@ result = box.list_public_urls() print(result["public_urls"]) # [ -# PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000), -# PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080), +# PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000, wake_on_request=True), +# PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080, wake_on_request=False), # ] ``` @@ -10772,9 +10982,48 @@ public_url2 = box.get_public_url(3000, bearer_token=True) ### Public URL Lifecycle -Public URLs expire automatically when: -* The box is paused -* The box is deleted +A public URL outlives a pause. While the box is paused, a request to the URL returns a "box is sleeping" response unless the public URL was created with wake on request. + +Public URLs are removed when the box is deleted. + +### Wake on Request + +A public URL created with `wakeOnRequest: true` resumes the box on an incoming HTTP request. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. With `wakeOnRequest: false` (the default), the same request gets a "box is sleeping" response and the box stays paused. + +The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/docs/box/overall/init-commands). + +Set a client timeout longer than that window. Both examples below use library defaults, and some clients default to less than the cold start takes. + + +```typescript box.ts +const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true }) + +await box.pause() + +// This request resumes the box and returns the app's response +const response = await fetch(publicUrl.url, { signal: AbortSignal.timeout(60_000) }) +console.log(response.status) // 200, or 503 if the app is still starting +``` + +```python box.py +import httpx + +public_url = box.get_public_url(3000, wake_on_request=True) + +box.pause() + +# This request resumes the box and returns the app's response. +# httpx defaults to a 5 second timeout, which a cold start can outlast. +response = httpx.get(public_url.url, timeout=60.0) +print(response.status_code) # 200, or 503 if the app is still starting +``` + + + +Wake on request means anyone who can reach the URL can start the box, and therefore cause compute charges. That is why it is off by default. Pair it with `bearerToken` or `basicAuth` so only callers holding the credentials can wake the box. + + +For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/docs/box/overall/init-commands) does that: the box runs it once every time the container starts, including after a resume. ### Auto-Resume @@ -11153,7 +11402,7 @@ box = Box.create(runtime="node") Your box is ready to use! You can already use it as a standalone, secure, isolated sandbox with full shell access, git, and filesystem operations. - You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions and can run an `initCommand` at startup. See [Keep Alive](/docs/box/overall/keep-alive). + You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions instead of auto-pausing. See [Keep Alive](/docs/box/overall/keep-alive). Any box, keep-alive or not, can run an `initCommand` each time it starts. See [Init Commands](/docs/box/overall/init-commands). *** diff --git a/llms.txt b/llms.txt index 8048b3b60..f2b043768 100644 --- a/llms.txt +++ b/llms.txt @@ -93,6 +93,7 @@ - [Filesystem](https://upstash.com/docs/box/overall/files.md) - [Git](https://upstash.com/docs/box/overall/git.md) - [Box Basics](https://upstash.com/docs/box/overall/how-it-works.md) +- [Box Init Commands](https://upstash.com/docs/box/overall/init-commands.md) - [Keep Alive](https://upstash.com/docs/box/overall/keep-alive.md) - [Live Sessions](https://upstash.com/docs/box/overall/live-sessions.md) - [Network Policy](https://upstash.com/docs/box/overall/network-policy.md) From 0701e927549d14a84464508175af53abad7d1149 Mon Sep 17 00:00:00 2001 From: alitariksahin Date: Tue, 22 Sep 2026 12:06:29 +0300 Subject: [PATCH 2/5] DX-3029: Waking a paused box is what a public URL does, not an option The backend made it unconditional and the option no longer exists anywhere, so the docs describe it as plain behaviour: a request to the URL resumes the box and is held until the port is listening, bounded at 30 seconds with a 503 and Retry-After after that, with auth checked before the wake. The create examples that passed the option, the field on the list output and the warning about reachability are gone. The init commands page keeps its end-to-end example with the option removed from the public URL call. --- box/overall/init-commands.mdx | 13 ++------ box/overall/preview.mdx | 58 ++++++----------------------------- 2 files changed, 13 insertions(+), 58 deletions(-) diff --git a/box/overall/init-commands.mdx b/box/overall/init-commands.mdx index a772d64bd..ad28d94eb 100644 --- a/box/overall/init-commands.mdx +++ b/box/overall/init-commands.mdx @@ -121,7 +121,7 @@ The command runs in the background, so box creation and resume do not block on i ## Pairing with public URLs -An init command is what makes a paused box useful again behind a [public URL](/box/overall/preview). If the public URL was created with wake on request, an incoming HTTP request resumes the box, the init command starts the server again, and the request is held until the port is listening. +An init command is what makes a paused box useful again behind a [public URL](/box/overall/preview). An incoming HTTP request resumes the paused box, the init command starts the server again, and the request is held until the port is listening. ```typescript box.ts @@ -137,10 +137,7 @@ await box.files.write({ await box.setInitCommand("node server.js") -const publicUrl = await box.getPublicURL(3000, { - wakeOnRequest: true, - bearerToken: true, -}) +const publicUrl = await box.getPublicURL(3000, { bearerToken: true }) await box.pause() @@ -166,11 +163,7 @@ box.files.write( box.set_init_command("node server.js") -public_url = box.get_public_url( - 3000, - wake_on_request=True, - bearer_token=True, -) +public_url = box.get_public_url(3000, bearer_token=True) box.pause() diff --git a/box/overall/preview.mdx b/box/overall/preview.mdx index ba795cb56..e49b76097 100644 --- a/box/overall/preview.mdx +++ b/box/overall/preview.mdx @@ -196,40 +196,6 @@ print(public_url.password) # -> "f0f145f0..." ``` -#### With Wake on Request - -Add `wakeOnRequest: true` (`wake_on_request` in Python and the REST API) to let an incoming HTTP request resume a paused box. Defaults to `false`. See [Wake on Request](#wake-on-request) for the full behavior and the billing caveat. - - -```typescript box.ts -const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true }) - -console.log(publicUrl.wake_on_request) // → true -``` - -```python box.py -public_url = box.get_public_url(3000, wake_on_request=True) - -print(public_url.wake_on_request) # -> True -``` - - -Wake on request combines with either authentication method, which is the recommended setup: - - -```typescript box.ts -const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true, bearerToken: true }) - -console.log(publicUrl.token) // → "63d8b153..." -``` - -```python box.py -public_url = box.get_public_url(3000, wake_on_request=True, bearer_token=True) - -print(public_url.token) # -> "63d8b153..." -``` - - --- ### List Public URLs @@ -243,9 +209,9 @@ const { publicURLs } = await box.listPublicURLs() console.log(publicURLs) // [ // { id: "{BOX_ID}-3000", port: 3000, url: "https://{BOX_ID}-3000.preview.box.upstash.com", -// created_at: 1789990404, basic_auth: false, bearer_token: false, wake_on_request: true }, +// created_at: 1789990404, basic_auth: false, bearer_token: false }, // { id: "{BOX_ID}-8080", port: 8080, url: "https://{BOX_ID}-8080.preview.box.upstash.com", -// created_at: 1789990512, basic_auth: false, bearer_token: true, wake_on_request: false }, +// created_at: 1789990512, basic_auth: false, bearer_token: true }, // ] ``` @@ -254,8 +220,8 @@ result = box.list_public_urls() print(result["public_urls"]) # [ -# PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000, wake_on_request=True), -# PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080, wake_on_request=False), +# PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000), +# PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080), # ] ``` @@ -308,21 +274,21 @@ public_url2 = box.get_public_url(3000, bearer_token=True) ### Public URL Lifecycle -A public URL outlives a pause. While the box is paused, a request to the URL returns a "box is sleeping" response unless the public URL was created with wake on request. +A public URL outlives a pause. While the box is paused, a request to the URL resumes it. See [Waking a Paused Box](#waking-a-paused-box). Public URLs are removed when the box is deleted. -### Wake on Request +### Waking a Paused Box -A public URL created with `wakeOnRequest: true` resumes the box on an incoming HTTP request. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. With `wakeOnRequest: false` (the default), the same request gets a "box is sleeping" response and the box stays paused. +An incoming HTTP request to a public URL resumes its box if the box is paused. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. If the public URL requires authentication, the credentials are checked before the box is woken. The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/box/overall/init-commands). -Set a client timeout longer than that window. Both examples below use library defaults, and some clients default to less than the cold start takes. +Set a client timeout longer than that window. Both examples below set one explicitly, because some clients default to less than a cold start takes. ```typescript box.ts -const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true }) +const publicUrl = await box.getPublicURL(3000) await box.pause() @@ -334,7 +300,7 @@ console.log(response.status) // 200, or 503 if the app is still starting ```python box.py import httpx -public_url = box.get_public_url(3000, wake_on_request=True) +public_url = box.get_public_url(3000) box.pause() @@ -345,10 +311,6 @@ print(response.status_code) # 200, or 503 if the app is still starting ``` - -Wake on request means anyone who can reach the URL can start the box, and therefore cause compute charges. That is why it is off by default. Pair it with `bearerToken` or `basicAuth` so only callers holding the credentials can wake the box. - - For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/box/overall/init-commands) does that: the box runs it once every time the container starts, including after a resume. ### Auto-Resume From 1a9c8e2f6c72ded895d6c8516d5c5c20bdb8706a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:06:53 +0000 Subject: [PATCH 3/5] chore(llms): regenerate llms.txt and llms-full.txt --- llms-full.txt | 71 ++++++++++----------------------------------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/llms-full.txt b/llms-full.txt index 57d2988f0..5a7eb647b 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -9863,7 +9863,7 @@ The command runs in the background, so box creation and resume do not block on i ## Pairing with public URLs -An init command is what makes a paused box useful again behind a [public URL](/docs/box/overall/preview). If the public URL was created with wake on request, an incoming HTTP request resumes the box, the init command starts the server again, and the request is held until the port is listening. +An init command is what makes a paused box useful again behind a [public URL](/docs/box/overall/preview). An incoming HTTP request resumes the paused box, the init command starts the server again, and the request is held until the port is listening. ```typescript box.ts @@ -9879,10 +9879,7 @@ await box.files.write({ await box.setInitCommand("node server.js") -const publicUrl = await box.getPublicURL(3000, { - wakeOnRequest: true, - bearerToken: true, -}) +const publicUrl = await box.getPublicURL(3000, { bearerToken: true }) await box.pause() @@ -9908,11 +9905,7 @@ box.files.write( box.set_init_command("node server.js") -public_url = box.get_public_url( - 3000, - wake_on_request=True, - bearer_token=True, -) +public_url = box.get_public_url(3000, bearer_token=True) box.pause() @@ -10870,40 +10863,6 @@ print(public_url.password) # -> "f0f145f0..." ``` -#### With Wake on Request - -Add `wakeOnRequest: true` (`wake_on_request` in Python and the REST API) to let an incoming HTTP request resume a paused box. Defaults to `false`. See [Wake on Request](#wake-on-request) for the full behavior and the billing caveat. - - -```typescript box.ts -const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true }) - -console.log(publicUrl.wake_on_request) // → true -``` - -```python box.py -public_url = box.get_public_url(3000, wake_on_request=True) - -print(public_url.wake_on_request) # -> True -``` - - -Wake on request combines with either authentication method, which is the recommended setup: - - -```typescript box.ts -const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true, bearerToken: true }) - -console.log(publicUrl.token) // → "63d8b153..." -``` - -```python box.py -public_url = box.get_public_url(3000, wake_on_request=True, bearer_token=True) - -print(public_url.token) # -> "63d8b153..." -``` - - *** ### List Public URLs @@ -10917,9 +10876,9 @@ const { publicURLs } = await box.listPublicURLs() console.log(publicURLs) // [ // { id: "{BOX_ID}-3000", port: 3000, url: "https://{BOX_ID}-3000.preview.box.upstash.com", -// created_at: 1789990404, basic_auth: false, bearer_token: false, wake_on_request: true }, +// created_at: 1789990404, basic_auth: false, bearer_token: false }, // { id: "{BOX_ID}-8080", port: 8080, url: "https://{BOX_ID}-8080.preview.box.upstash.com", -// created_at: 1789990512, basic_auth: false, bearer_token: true, wake_on_request: false }, +// created_at: 1789990512, basic_auth: false, bearer_token: true }, // ] ``` @@ -10928,8 +10887,8 @@ result = box.list_public_urls() print(result["public_urls"]) # [ -# PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000, wake_on_request=True), -# PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080, wake_on_request=False), +# PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000), +# PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080), # ] ``` @@ -10982,21 +10941,21 @@ public_url2 = box.get_public_url(3000, bearer_token=True) ### Public URL Lifecycle -A public URL outlives a pause. While the box is paused, a request to the URL returns a "box is sleeping" response unless the public URL was created with wake on request. +A public URL outlives a pause. While the box is paused, a request to the URL resumes it. See [Waking a Paused Box](#waking-a-paused-box). Public URLs are removed when the box is deleted. -### Wake on Request +### Waking a Paused Box -A public URL created with `wakeOnRequest: true` resumes the box on an incoming HTTP request. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. With `wakeOnRequest: false` (the default), the same request gets a "box is sleeping" response and the box stays paused. +An incoming HTTP request to a public URL resumes its box if the box is paused. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. If the public URL requires authentication, the credentials are checked before the box is woken. The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/docs/box/overall/init-commands). -Set a client timeout longer than that window. Both examples below use library defaults, and some clients default to less than the cold start takes. +Set a client timeout longer than that window. Both examples below set one explicitly, because some clients default to less than a cold start takes. ```typescript box.ts -const publicUrl = await box.getPublicURL(3000, { wakeOnRequest: true }) +const publicUrl = await box.getPublicURL(3000) await box.pause() @@ -11008,7 +10967,7 @@ console.log(response.status) // 200, or 503 if the app is still starting ```python box.py import httpx -public_url = box.get_public_url(3000, wake_on_request=True) +public_url = box.get_public_url(3000) box.pause() @@ -11019,10 +10978,6 @@ print(response.status_code) # 200, or 503 if the app is still starting ``` - -Wake on request means anyone who can reach the URL can start the box, and therefore cause compute charges. That is why it is off by default. Pair it with `bearerToken` or `basicAuth` so only callers holding the credentials can wake the box. - - For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/docs/box/overall/init-commands) does that: the box runs it once every time the container starts, including after a resume. ### Auto-Resume From 0fdb6d762df3f04f6789c48dbc63c6bdbcf9e442 Mon Sep 17 00:00:00 2001 From: alitariksahin Date: Tue, 22 Sep 2026 13:13:19 +0300 Subject: [PATCH 4/5] DX-3029: Mention init commands on the create step instead of a page of their own A short section under Create a Box in the quickstart now carries what matters: pass initCommand to run a startup script once per container start, on create, resume and snapshot restore; it runs from the workspace root; it can be read, changed or removed later on any box including a paused one; and a snapshot restore does not inherit it. Every link that pointed at the standalone page points there now. --- box/overall/how-it-works.mdx | 4 +- box/overall/init-commands.mdx | 192 ---------------------------------- box/overall/keep-alive.mdx | 2 +- box/overall/preview.mdx | 4 +- box/overall/quickstart.mdx | 24 ++++- docs.json | 1 - 6 files changed, 28 insertions(+), 199 deletions(-) delete mode 100644 box/overall/init-commands.mdx diff --git a/box/overall/how-it-works.mdx b/box/overall/how-it-works.mdx index a8b295a78..6cb8b0e4f 100644 --- a/box/overall/how-it-works.mdx +++ b/box/overall/how-it-works.mdx @@ -88,7 +88,7 @@ A box retains its full state between runs (files, installed packages, git histor When you create a box, Upstash provisions a new isolated container with its own filesystem, shell, and network stack. You can start from a fresh box or restore from a snapshot. Once provisioning finishes, the box is ready to receive commands. -If the box has an [init command](/box/overall/init-commands), the box runs it once after the container starts, on create, on resume, and after a snapshot restore. +If the box has an [init command](/box/overall/quickstart#init-command), the box runs it once after the container starts, on create, on resume, and after a snapshot restore. ### 2. Running @@ -250,7 +250,7 @@ box.resume() Paused boxes do not accrue active CPU charges. Pause/resume is not available when `keepAlive` is enabled. -Resuming reruns the box's [init command](/box/overall/init-commands), so a server started that way comes back with the box. +Resuming reruns the box's [init command](/box/overall/quickstart#init-command), so a server started that way comes back with the box. ### Snapshot and restore diff --git a/box/overall/init-commands.mdx b/box/overall/init-commands.mdx deleted file mode 100644 index ad28d94eb..000000000 --- a/box/overall/init-commands.mdx +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: "Box Init Commands" ---- - -An init command is a startup script stored on the box. Every box can have one, and the box runs it once each time its container starts: after create, after resume, and after a snapshot restore. - -Use it to bring the environment back to a working state without sending the same commands again, for example starting a web server, launching a background process, or warming a cache. - ---- - -## Set an init command at creation - -Pass `initCommand` when creating the box: - - -```typescript box.ts -import { Box } from "@upstash/box" - -const box = await Box.create({ - runtime: "node", - initCommand: "npm install && npm run dev", -}) -``` - -```python box.py -from upstash_box import Box - -box = Box.create( - runtime="node", - init_command="npm install && npm run dev", -) -``` - - -This works on any box. It is not limited to [keep-alive](/box/overall/keep-alive) boxes. - -The command runs from `/workspace/home`, so it assumes a project is already there: clone a repository with [git](/box/overall/git), restore a [snapshot](/box/overall/snapshots), or write the files yourself before the box starts. The end-to-end example below does the last of those. - -A box restored from a snapshot does not inherit the snapshot's init command, so pass it again on the restore: - - -```typescript box.ts -const box = await Box.fromSnapshot("snap_abc123", { - initCommand: "npm run dev", -}) -``` - -```python box.py -box = Box.from_snapshot( - "snap_abc123", - init_command="npm run dev", -) -``` - - ---- - -## Manage the init command later - -Get, set, and delete work on any box, including a paused one. - - -```typescript box.ts -await box.setInitCommand("npm run dev") - -const command = await box.getInitCommand() -console.log(command) // "npm run dev" - -await box.deleteInitCommand() -``` - -```python box.py -box.set_init_command("npm run dev") - -command = box.get_init_command() -print(command) # "npm run dev" - -box.delete_init_command() -``` - - -On a paused box the change is persisted right away and applied the next time the box resumes. The box is not woken up to accept it. - - -```typescript box.ts -await box.pause() - -// Persisted now, applied on the next resume -await box.setInitCommand("npm run start") - -await box.resume() // runs "npm run start" -``` - -```python box.py -box.pause() - -# Persisted now, applied on the next resume -box.set_init_command("npm run start") - -box.resume() # runs "npm run start" -``` - - ---- - -## When it runs - -| Event | Init command runs | -| --- | --- | -| Box created | Yes | -| Box resumed from paused | Yes | -| Box restored from a snapshot | Yes | -| `exec.command()` or an agent run | No | -| Already running container | No, it does not run twice in one container lifetime | - -The init command runs once per container start. It is not re-run on every command you send, and a second trigger within the same container lifetime is a no-op. If you need the command to run again without restarting the box, run it yourself with [`box.exec`](/box/overall/shell). - -The command runs in the background, so box creation and resume do not block on it finishing. A long install keeps running while you send other work to the box. - ---- - -## Pairing with public URLs - -An init command is what makes a paused box useful again behind a [public URL](/box/overall/preview). An incoming HTTP request resumes the paused box, the init command starts the server again, and the request is held until the port is listening. - - -```typescript box.ts -import { Box } from "@upstash/box" - -const box = await Box.create({ runtime: "node" }) - -// The init command runs a file, so the file has to exist first. -await box.files.write({ - path: "server.js", - content: `require("http").createServer((_, res) => res.end("ok")).listen(3000)`, -}) - -await box.setInitCommand("node server.js") - -const publicUrl = await box.getPublicURL(3000, { bearerToken: true }) - -await box.pause() - -// Resumes the box, reruns the init command, then answers. -// Allow for the cold start: the wake wait is bounded at 30 seconds. -const response = await fetch(publicUrl.url, { - headers: { Authorization: `Bearer ${publicUrl.token}` }, - signal: AbortSignal.timeout(60_000), -}) -``` - -```python box.py -import httpx -from upstash_box import Box - -box = Box.create(runtime="node") - -# The init command runs a file, so the file has to exist first. -box.files.write( - path="server.js", - content='require("http").createServer((_, res) => res.end("ok")).listen(3000)', -) - -box.set_init_command("node server.js") - -public_url = box.get_public_url(3000, bearer_token=True) - -box.pause() - -# Resumes the box, reruns the init command, then answers. -# httpx defaults to a 5 second timeout, which a cold start can outlast. -response = httpx.get( - public_url.url, - headers={"Authorization": f"Bearer {public_url.token}"}, - timeout=60.0, -) -``` - - ---- - -## Console - -In the Upstash Console you can set an **Init Command** while creating a box, and change or remove it later from the box settings page. - ---- - -## Notes - -- An init command is capped at 64KB. -- Deleting the init command removes the stored script. Anything it already started keeps running until the container stops. -- Snapshots capture the init command, but a box restored from a snapshot does not inherit it. Pass `initCommand` on the restore, or the stored script is cleared and nothing runs at startup. diff --git a/box/overall/keep-alive.mdx b/box/overall/keep-alive.mdx index ed310ef35..3e26b5f23 100644 --- a/box/overall/keep-alive.mdx +++ b/box/overall/keep-alive.mdx @@ -87,7 +87,7 @@ box = Box.create( ``` -Init commands are not a keep-alive feature. Every box can have one, and get, set, and delete work on any box, including a paused one. See [Init Commands](/box/overall/init-commands) for the full behavior. +Init commands are not a keep-alive feature. Every box can have one, and get, set, and delete work on any box, including a paused one. See [Create a Box](/box/overall/quickstart#init-command). --- diff --git a/box/overall/preview.mdx b/box/overall/preview.mdx index e49b76097..982780f68 100644 --- a/box/overall/preview.mdx +++ b/box/overall/preview.mdx @@ -282,7 +282,7 @@ Public URLs are removed when the box is deleted. An incoming HTTP request to a public URL resumes its box if the box is paused. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. If the public URL requires authentication, the credentials are checked before the box is woken. -The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/box/overall/init-commands). +The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/box/overall/quickstart#init-command). Set a client timeout longer than that window. Both examples below set one explicitly, because some clients default to less than a cold start takes. @@ -311,7 +311,7 @@ print(response.status_code) # 200, or 503 if the app is still starting ``` -For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/box/overall/init-commands) does that: the box runs it once every time the container starts, including after a resume. +For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/box/overall/quickstart#init-command) does that: the box runs it once every time the container starts, including after a resume. ### Auto-Resume diff --git a/box/overall/quickstart.mdx b/box/overall/quickstart.mdx index 924d2f168..256d2cbf2 100644 --- a/box/overall/quickstart.mdx +++ b/box/overall/quickstart.mdx @@ -77,9 +77,31 @@ box = Box.create(runtime="node") Your box is ready to use! You can already use it as a standalone, secure, isolated sandbox with full shell access, git, and filesystem operations. - You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions instead of auto-pausing. See [Keep Alive](/box/overall/keep-alive). Any box, keep-alive or not, can run an `initCommand` each time it starts. See [Init Commands](/box/overall/init-commands). + You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions instead of auto-pausing. See [Keep Alive](/box/overall/keep-alive). +#### Init command + +Pass `initCommand` to run a startup script each time the box starts. It runs once per container start, on create, on resume, and after a snapshot restore, so it is the place to start a server that should come back with the box. + + +```typescript box.ts +const box = await Box.create({ + runtime: "node", + initCommand: "npm install && npm run dev", +}) +``` + +```python box.py +box = Box.create( + runtime="node", + init_command="npm install && npm run dev", +) +``` + + +The command runs from `/workspace/home`, so it assumes a project is already there. You can read, change, or remove it later with `getInitCommand`, `setInitCommand`, and `deleteInitCommand` (`get_init_command`, `set_init_command`, `delete_init_command` in Python), on any box including a paused one. A box restored from a snapshot does not inherit the snapshot's init command, so pass it again on the restore. + --- ### 5. Configure an Agent (optional) diff --git a/docs.json b/docs.json index 7c155f8b7..b91c2a740 100644 --- a/docs.json +++ b/docs.json @@ -2134,7 +2134,6 @@ "box/overall/preview", "box/overall/schedules", "box/overall/keep-alive", - "box/overall/init-commands", "box/overall/ephemeral-box" ] }, From 4cf77d0d976d7880240544437594949dd329a348 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:15:00 +0000 Subject: [PATCH 5/5] chore(llms): regenerate llms.txt and llms-full.txt --- llms-full.txt | 226 +++++++------------------------------------------- llms.txt | 1 - 2 files changed, 28 insertions(+), 199 deletions(-) diff --git a/llms-full.txt b/llms-full.txt index 5a7eb647b..24e82cfbe 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -9493,7 +9493,7 @@ A box retains its full state between runs (files, installed packages, git histor When you create a box, Upstash provisions a new isolated container with its own filesystem, shell, and network stack. You can start from a fresh box or restore from a snapshot. Once provisioning finishes, the box is ready to receive commands. -If the box has an [init command](/docs/box/overall/init-commands), the box runs it once after the container starts, on create, on resume, and after a snapshot restore. +If the box has an [init command](/docs/box/overall/quickstart#init-command), the box runs it once after the container starts, on create, on resume, and after a snapshot restore. ### 2. Running @@ -9655,7 +9655,7 @@ box.resume() Paused boxes do not accrue active CPU charges. Pause/resume is not available when `keepAlive` is enabled. -Resuming reruns the box's [init command](/docs/box/overall/init-commands), so a server started that way comes back with the box. +Resuming reruns the box's [init command](/docs/box/overall/quickstart#init-command), so a server started that way comes back with the box. ### Snapshot and restore @@ -9741,198 +9741,6 @@ Boxes are available in three sizes: For exact pricing details, see the [pricing page](https://upstash.com/pricing/box). For keep-alive behavior, see [Keep Alive](/docs/box/overall/keep-alive). -# Box Init Commands -Source: https://upstash.com/docs/box/overall/init-commands - -An init command is a startup script stored on the box. Every box can have one, and the box runs it once each time its container starts: after create, after resume, and after a snapshot restore. - -Use it to bring the environment back to a working state without sending the same commands again, for example starting a web server, launching a background process, or warming a cache. - -*** - -## Set an init command at creation - -Pass `initCommand` when creating the box: - - -```typescript box.ts -import { Box } from "@upstash/box" - -const box = await Box.create({ - runtime: "node", - initCommand: "npm install && npm run dev", -}) -``` - -```python box.py -from upstash_box import Box - -box = Box.create( - runtime="node", - init_command="npm install && npm run dev", -) -``` - - -This works on any box. It is not limited to [keep-alive](/docs/box/overall/keep-alive) boxes. - -The command runs from `/workspace/home`, so it assumes a project is already there: clone a repository with [git](/docs/box/overall/git), restore a [snapshot](/docs/box/overall/snapshots), or write the files yourself before the box starts. The end-to-end example below does the last of those. - -A box restored from a snapshot does not inherit the snapshot's init command, so pass it again on the restore: - - -```typescript box.ts -const box = await Box.fromSnapshot("snap_abc123", { - initCommand: "npm run dev", -}) -``` - -```python box.py -box = Box.from_snapshot( - "snap_abc123", - init_command="npm run dev", -) -``` - - -*** - -## Manage the init command later - -Get, set, and delete work on any box, including a paused one. - - -```typescript box.ts -await box.setInitCommand("npm run dev") - -const command = await box.getInitCommand() -console.log(command) // "npm run dev" - -await box.deleteInitCommand() -``` - -```python box.py -box.set_init_command("npm run dev") - -command = box.get_init_command() -print(command) # "npm run dev" - -box.delete_init_command() -``` - - -On a paused box the change is persisted right away and applied the next time the box resumes. The box is not woken up to accept it. - - -```typescript box.ts -await box.pause() - -// Persisted now, applied on the next resume -await box.setInitCommand("npm run start") - -await box.resume() // runs "npm run start" -``` - -```python box.py -box.pause() - -# Persisted now, applied on the next resume -box.set_init_command("npm run start") - -box.resume() # runs "npm run start" -``` - - -*** - -## When it runs - -| Event | Init command runs | -| --- | --- | -| Box created | Yes | -| Box resumed from paused | Yes | -| Box restored from a snapshot | Yes | -| `exec.command()` or an agent run | No | -| Already running container | No, it does not run twice in one container lifetime | - -The init command runs once per container start. It is not re-run on every command you send, and a second trigger within the same container lifetime is a no-op. If you need the command to run again without restarting the box, run it yourself with [`box.exec`](/docs/box/overall/shell). - -The command runs in the background, so box creation and resume do not block on it finishing. A long install keeps running while you send other work to the box. - -*** - -## Pairing with public URLs - -An init command is what makes a paused box useful again behind a [public URL](/docs/box/overall/preview). An incoming HTTP request resumes the paused box, the init command starts the server again, and the request is held until the port is listening. - - -```typescript box.ts -import { Box } from "@upstash/box" - -const box = await Box.create({ runtime: "node" }) - -// The init command runs a file, so the file has to exist first. -await box.files.write({ - path: "server.js", - content: `require("http").createServer((_, res) => res.end("ok")).listen(3000)`, -}) - -await box.setInitCommand("node server.js") - -const publicUrl = await box.getPublicURL(3000, { bearerToken: true }) - -await box.pause() - -// Resumes the box, reruns the init command, then answers. -// Allow for the cold start: the wake wait is bounded at 30 seconds. -const response = await fetch(publicUrl.url, { - headers: { Authorization: `Bearer ${publicUrl.token}` }, - signal: AbortSignal.timeout(60_000), -}) -``` - -```python box.py -import httpx -from upstash_box import Box - -box = Box.create(runtime="node") - -# The init command runs a file, so the file has to exist first. -box.files.write( - path="server.js", - content='require("http").createServer((_, res) => res.end("ok")).listen(3000)', -) - -box.set_init_command("node server.js") - -public_url = box.get_public_url(3000, bearer_token=True) - -box.pause() - -# Resumes the box, reruns the init command, then answers. -# httpx defaults to a 5 second timeout, which a cold start can outlast. -response = httpx.get( - public_url.url, - headers={"Authorization": f"Bearer {public_url.token}"}, - timeout=60.0, -) -``` - - -*** - -## Console - -In the Upstash Console you can set an **Init Command** while creating a box, and change or remove it later from the box settings page. - -*** - -## Notes - -* An init command is capped at 64KB. -* Deleting the init command removes the stored script. Anything it already started keeps running until the container stops. -* Snapshots capture the init command, but a box restored from a snapshot does not inherit it. Pass `initCommand` on the restore, or the stored script is cleared and nothing runs at startup. - # Keep Alive Source: https://upstash.com/docs/box/overall/keep-alive @@ -10021,7 +9829,7 @@ box = Box.create( ``` -Init commands are not a keep-alive feature. Every box can have one, and get, set, and delete work on any box, including a paused one. See [Init Commands](/docs/box/overall/init-commands) for the full behavior. +Init commands are not a keep-alive feature. Every box can have one, and get, set, and delete work on any box, including a paused one. See [Create a Box](/docs/box/overall/quickstart#init-command). *** @@ -10949,7 +10757,7 @@ Public URLs are removed when the box is deleted. An incoming HTTP request to a public URL resumes its box if the box is paused. The request is held while the box comes back and until the app's port is listening, so the caller gets the app's real response instead of an error. If the public URL requires authentication, the credentials are checked before the box is woken. -The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/docs/box/overall/init-commands). +The wait is bounded at 30 seconds. If the port is not listening by then, the request returns `503` with a `Retry-After: 5` header rather than hanging: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box keeps resuming in the background, so a retry usually succeeds. Anything slower than 30 seconds to start, such as a cold `npm install`, should be built into the image or a snapshot instead of left to the [init command](/docs/box/overall/quickstart#init-command). Set a client timeout longer than that window. Both examples below set one explicitly, because some clients default to less than a cold start takes. @@ -10978,7 +10786,7 @@ print(response.status_code) # 200, or 503 if the app is still starting ``` -For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/docs/box/overall/init-commands) does that: the box runs it once every time the container starts, including after a resume. +For the app to answer on its own after a resume, the box needs to start its server again. An [init command](/docs/box/overall/quickstart#init-command) does that: the box runs it once every time the container starts, including after a resume. ### Auto-Resume @@ -11357,9 +11165,31 @@ box = Box.create(runtime="node") Your box is ready to use! You can already use it as a standalone, secure, isolated sandbox with full shell access, git, and filesystem operations. - You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions instead of auto-pausing. See [Keep Alive](/docs/box/overall/keep-alive). Any box, keep-alive or not, can run an `initCommand` each time it starts. See [Init Commands](/docs/box/overall/init-commands). + You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions instead of auto-pausing. See [Keep Alive](/docs/box/overall/keep-alive). +#### Init command + +Pass `initCommand` to run a startup script each time the box starts. It runs once per container start, on create, on resume, and after a snapshot restore, so it is the place to start a server that should come back with the box. + + +```typescript box.ts +const box = await Box.create({ + runtime: "node", + initCommand: "npm install && npm run dev", +}) +``` + +```python box.py +box = Box.create( + runtime="node", + init_command="npm install && npm run dev", +) +``` + + +The command runs from `/workspace/home`, so it assumes a project is already there. You can read, change, or remove it later with `getInitCommand`, `setInitCommand`, and `deleteInitCommand` (`get_init_command`, `set_init_command`, `delete_init_command` in Python), on any box including a paused one. A box restored from a snapshot does not inherit the snapshot's init command, so pass it again on the restore. + *** ### 5. Configure an Agent (optional) diff --git a/llms.txt b/llms.txt index f2b043768..8048b3b60 100644 --- a/llms.txt +++ b/llms.txt @@ -93,7 +93,6 @@ - [Filesystem](https://upstash.com/docs/box/overall/files.md) - [Git](https://upstash.com/docs/box/overall/git.md) - [Box Basics](https://upstash.com/docs/box/overall/how-it-works.md) -- [Box Init Commands](https://upstash.com/docs/box/overall/init-commands.md) - [Keep Alive](https://upstash.com/docs/box/overall/keep-alive.md) - [Live Sessions](https://upstash.com/docs/box/overall/live-sessions.md) - [Network Policy](https://upstash.com/docs/box/overall/network-policy.md)