> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ascii.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Platform on Box

> Run your users' agents, previews, and workspaces on Box: architectures, costs, and provisioning patterns.

This guide is for builders of Lovable-like, Devin-like, or agent-platform products, where your product spawns a Box for each of your own users.

What such a platform ultimately needs:

* persistent files and context per user or project
* an agent that works inside the workspace and can debug anything
* a private preview URL of what the user is building
* controlled sandbox cost per user

In all cases, you run your own backend and database, and drive Box through the [API](/box/api/v1) or [SDKs](/box/sdks/overview).

## The two rules

1. **Always create user boxes with `--no-env`** (`noEnv: true`). A no-env box receives none of your account's secrets or credentials and cannot act on your account or other boxes. Pass what the box does need explicitly with `--env`. See [No-env boxes](/box/secrets#no-env-boxes).
2. **Tag each box** with per-box environment variables so your backend can identify it.

<CodeGroup>
  ```bash CLI theme={null}
  box new --no-env --no-auto-stop --env TENANT_ID=acme
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"ttlSeconds":null,"noEnv":true,"env":{"TENANT_ID":"acme"}}'
  ```

  ```ts TypeScript theme={null}
  await box.create({
    createBoxRequest: { ttlSeconds: null, noEnv: true, env: { TENANT_ID: "acme" } },
  });
  ```

  ```python Python theme={null}
  box.create(CreateBoxRequest(ttl_seconds=None, no_env=True, env={"TENANT_ID": "acme"}))
  ```
</CodeGroup>

Wait for the box to reach `ready` or `idle` before running commands in it. Commands sent earlier run before your `env` is applied.

## Pattern 1: one always-on box per project

Simplest, perfectly isolated. Spawn a box per user project with `--no-auto-stop`. Clone or copy the user's code in, run their dev server, and expose it:

<CodeGroup>
  ```bash CLI theme={null}
  box ssh <id> "cd ~/project && npm run dev -- --host 0.0.0.0 --port 3000 &"
  box ssh <id> "host 3000 --private"
  # prints https://<subdomain>-3000.on.ascii.dev?_token=...
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/$BOX_ID/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"npm run dev -- --host 0.0.0.0 --port 3000 &","cwd":"project"}'

  curl -sS -X POST "$BOX_API_BASE/boxes/$BOX_ID/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"host 3000 --private"}'
  ```

  ```ts TypeScript theme={null}
  await box.command({
    boxId,
    commandRequest: { command: "npm run dev -- --host 0.0.0.0 --port 3000 &", cwd: "project" },
  });
  const hosted = await box.command({ boxId, commandRequest: { command: "host 3000 --private" } });
  console.log(hosted.stdout); // https://<subdomain>-3000.on.ascii.dev?_token=...
  ```

  ```python Python theme={null}
  box.command(box_id, CommandRequest(
      command="npm run dev -- --host 0.0.0.0 --port 3000 &",
      cwd="project",
  ))
  hosted = box.command(box_id, CommandRequest(command="host 3000 --private"))
  print(hosted.stdout)  # https://<subdomain>-3000.on.ascii.dev?_token=...
  ```
</CodeGroup>

The `_token` URL is private to whoever you give it to. The preview stays up as long as the box runs.

Cost: a box running 24/7 is about \$26/month (\$0.00001 per second). Use this when the project has real traffic or the user pays you enough to cover it.

## Pattern 2: one always-on box per user

Like pattern 1, but one box holds all of a user's projects, each in its own folder on its own port. A small daemon you install in the box multiplexes agent sessions. Works well up to 5 to 10 fullstack projects per user.

Cost: about \$26/month per user instead of per project. Watch isolation: projects of the same user share a machine.

## Pattern 3: stop and resume around usage

Cheapest, isolated, and what most platforms end up with. The box only runs while the user is actively working:

1. User sends a message. If their box is stopped, `box resume` it (a few seconds), relaunch your daemon and the preview in parallel, and send the message to the agent.
2. When the agent finishes, wait a short countdown, then `box stop`. Stop snapshots the filesystem and pauses billing.

Files, installed packages, and enabled systemd services survive stop and resume. Hand-run processes do not; your daemon restarts them, or [run it as an always-on service](/box/long-running-tasks#run-an-always-on-service). See [Snapshots](/box/snapshots).

Cost: \$1 buys about 27 hours of machine time. A typical user costs \$1 to \$5 per month, a power user \$10 to \$20.

Two refinements:

* **Zero preview downtime**: publish successful builds to a static host or CDN. The box is only for the agent and dev preview; production traffic never depends on a box being up.
* **Zero agent latency**: run a tool-less copy of the agent on your own server that answers immediately and stalls while the box resumes. See [optibox](https://github.com/ariana-dot-dev/optibox) for a working implementation.

## Bring your own harness: the daemon pattern

Box ships `box prompt` with Claude Code and Codex, but you do not have to use it. If your harness lives in your own infrastructure, or you want full control over the agent loop, put a small HTTP daemon in the box and talk to it directly:

1. **Install it**: `box scp` the binary in, or bake it into your [template](/box/templates).
2. **Keep it alive**: run it as an [always-on systemd service](/box/long-running-tasks#run-an-always-on-service) so it survives stop, resume, and fork.
3. **Expose it**: `host <port> --private` gives it a stable HTTPS URL gated by a `_token`; treat that token as the daemon's credential and store it in your backend.
4. **Drive it** from your backend over plain HTTPS: your harness sends work, the daemon runs it with full machine access and streams results back.

You can also skip the daemon entirely: [`POST /boxes/{boxId}/commands`](/box/api/reference/agent/execute-box-command) runs any command in the box, file endpoints [read](/box/api/reference/agent/read-box-file) and [write](/box/api/reference/agent/write-box-file) data, and first-class endpoints cover lifecycle, prompts, events, desktop, and snapshots. In-box tools such as `host` and `lux` are used by running their commands through that same endpoint. A daemon earns its place when you need streaming, concurrency, or lower latency than one-shot commands give you.

## Provision from a template

Never install your stack on a fresh box per user. Build it once, then fork:

<CodeGroup>
  ```bash CLI theme={null}
  box new                            # install runtimes, deps, your daemon
  box stop <template-id>             # its snapshot is now your template
  box fork <template-id> --no-env    # per user, a few seconds
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/<template-id>/fork" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"noEnv":true,"env":{"TENANT_ID":"acme"}}'
  ```

  ```ts TypeScript theme={null}
  const forked = await box.fork({
    boxId: templateId,
    forkRequest: { noEnv: true, env: { TENANT_ID: "acme" } },
  });
  ```

  ```python Python theme={null}
  forked = box.fork(template_id, ForkRequest(no_env=True, env={"TENANT_ID": "acme"}))
  ```
</CodeGroup>

Per-fork `env` is API and SDK only; the CLI fork takes `--no-env` alone.

Forks inherit the whole filesystem and are usable in seconds at roughly constant cost, whatever the template holds. To update the template: resume it, change it, stop it again. Forks always take the latest snapshot. See [Template Boxes](/box/templates).

## Sizing

Concurrent boxes track daily active users, not signups. A rough heuristic from platforms running on Box: 10,000 signups is about 100 active users on an average day, so 10 to 100 concurrent boxes, and up to 10x on a launch day. Pick the plan whose active-box cap and starts-per-minute cover your launch-day peak, and upgrade from the dashboard as you grow. See [Billing and limits](/box/billing).

## Related

* [Secrets & Setup](/box/secrets)
* [Billing & Limits](/box/billing)
* [Snapshots](/box/snapshots)
* [Hosting](/box/hosting)
* [FAQ](/box/faq)
