diff --git a/box/overall/how-it-works.mdx b/box/overall/how-it-works.mdx index 218de5639..6cb8b0e4f 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/quickstart#init-command), 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/quickstart#init-command), 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/keep-alive.mdx b/box/overall/keep-alive.mdx index 684016d75..3e26b5f23 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 [Create a Box](/box/overall/quickstart#init-command). --- @@ -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..cb20d10eb 100644 --- a/box/overall/preview.mdx +++ b/box/overall/preview.mdx @@ -208,8 +208,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 }, +// { id: "{BOX_ID}-8080", port: 8080, url: "https://{BOX_ID}-8080.preview.box.upstash.com", +// created_at: 1789990512, basic_auth: false, bearer_token: true }, // ] ``` @@ -272,9 +274,44 @@ 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 resumes it. See [Waking a Paused Box](#waking-a-paused-box). + +Public URLs are removed when the box is deleted. + +### Waking a Paused Box + +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. + +After container resume and configuration finish, the proxy waits up to 30 additional seconds for the app to become ready. The total HTTP request can take longer than 30 seconds. If the app is still not ready when that wait expires, the request returns `503` with a `Retry-After: 5` header: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box is already resumed, and the app can continue starting in the background. Move slow dependency installation, such as a cold `npm install`, into the image or a snapshot so the [init command](/box/overall/quickstart#init-command) only needs to start the app. + +Set a client timeout that allows for both container resume and the app readiness wait. Both examples below use 90 seconds; increase it if your environment needs more time. Some clients, including httpx, default to a shorter timeout than a cold start takes. + + +```typescript box.ts +const publicUrl = await box.getPublicURL(3000) + +await box.pause() + +// This request resumes the box and returns the app's response +const response = await fetch(publicUrl.url, { signal: AbortSignal.timeout(90_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) + +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=90.0) +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/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 7dd439688..32526d587 100644 --- a/box/overall/quickstart.mdx +++ b/box/overall/quickstart.mdx @@ -77,9 +77,33 @@ 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). +#### 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: `node -e 'require("http").createServer((_, res) => res.end("Hello from Box!")).listen(3000)'`, +}) +``` + +```python box.py +box = Box.create( + runtime="node", + init_command="""node -e 'require("http").createServer((_, res) => res.end("Hello from Box!")).listen(3000)'""", +) +``` + + +This example uses Node.js's built-in HTTP server, so it works in a fresh box without installing packages. The command runs from `/workspace/home`. For an existing app, prepare its project files and dependencies before setting its startup command. + +You can read, change, or remove the command 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-full.txt b/llms-full.txt index 92e4bcdf4..8daa41181 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/quickstart#init-command), 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/quickstart#init-command), 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. @@ -9805,7 +9809,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 +9829,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 [Create a Box](/docs/box/overall/quickstart#init-command). *** @@ -9864,7 +9839,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) *** @@ -10708,8 +10683,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 }, +// { id: "{BOX_ID}-8080", port: 8080, url: "https://{BOX_ID}-8080.preview.box.upstash.com", +// created_at: 1789990512, basic_auth: false, bearer_token: true }, // ] ``` @@ -10772,9 +10749,44 @@ 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 resumes it. See [Waking a Paused Box](#waking-a-paused-box). + +Public URLs are removed when the box is deleted. + +### Waking a Paused Box + +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. + +After container resume and configuration finish, the proxy waits up to 30 additional seconds for the app to become ready. The total HTTP request can take longer than 30 seconds. If the app is still not ready when that wait expires, the request returns `503` with a `Retry-After: 5` header: a browser gets a page that refreshes itself, and any other client gets a plain `503` to retry. The box is already resumed, and the app can continue starting in the background. Move slow dependency installation, such as a cold `npm install`, into the image or a snapshot so the [init command](/docs/box/overall/quickstart#init-command) only needs to start the app. + +Set a client timeout that allows for both container resume and the app readiness wait. Both examples below use 90 seconds; increase it if your environment needs more time. Some clients, including httpx, default to a shorter timeout than a cold start takes. + + +```typescript box.ts +const publicUrl = await box.getPublicURL(3000) + +await box.pause() + +// This request resumes the box and returns the app's response +const response = await fetch(publicUrl.url, { signal: AbortSignal.timeout(90_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) + +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=90.0) +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/quickstart#init-command) does that: the box runs it once every time the container starts, including after a resume. ### Auto-Resume @@ -11153,9 +11165,33 @@ 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). +#### 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: `node -e 'require("http").createServer((_, res) => res.end("Hello from Box!")).listen(3000)'`, +}) +``` + +```python box.py +box = Box.create( + runtime="node", + init_command="""node -e 'require("http").createServer((_, res) => res.end("Hello from Box!")).listen(3000)'""", +) +``` + + +This example uses Node.js's built-in HTTP server, so it works in a fresh box without installing packages. The command runs from `/workspace/home`. For an existing app, prepare its project files and dependencies before setting its startup command. + +You can read, change, or remove the command 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)