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

# Introduction

> Create long-running Linux VMs with persistent disks, interactive shells, services, and forks.

Archil's persistent sandboxes make it simple to create long-running machines that simplify access to your Archil disk.
They are ideal for starting network services (such as web hosting, git hosting, or databases), interactive development
environments, or running untrusted AI-generated code for longer than an individual bash tool.

Persistent sandboxes can keep running by resetting their [timeout](#setting-a-time-to-live), and can be forked so you can run multiple copies of
the same sandbox in parallel. Persistent sandboxes are built on full-featured Linux microVMs that are colocated with your
Archil disk data for maximum security and performance.

If you instead need the ability to run one-off bash, Python, or Node code against your Archil disk, consider using
[serverless execution](/compute/serverless-execution) instead.

<Note>
  Persistent sandboxes are available in preview in Archil's AWS regions. During the preview period,
  sandboxes may be pre-empted. Planned maintenance pauses them so they can be resumed. If a host fails
  unexpectedly, its sandboxes are marked `failed` and lose in-memory state; start them again to boot from disk.
</Note>

You can easily create a persistent sandbox using the TypeScript SDK (or the [Python SDK](/sdks/python#persistent-sandboxes)):

```typescript theme={null}
import * as archil from "disk";

const sandbox = await archil.createSandbox({
  // Sandbox name, for finding later
  name: "agent-workspace",

  // Supports public OCI images
  baseImage: "python:3.13",

  // Select the amount of vCPU and memory
  vcpuCount: 2,
  memSizeMiB: 4096,
});

const execution = await sandbox.run("python --version && pwd");
const result = await execution.wait();
console.log(result.stdout);
```

Archil sandboxes run on x86-64 (`amd64`) and support between 1 and 32 vCPU cores and between 0.25 and 64 GiB of memory. Sandboxes run as the root
Linux user (uid: 0, gid: 0).

## Network egress

To prevent platform abuse, network egress is limited to the Team plan and above. On the Developer plan, all network egress is denied. Once upgraded, network egress can also be configured for the [sandbox firewall](/compute/sandboxes/firewall). Sandboxes created on the Developer plan keep denying egress after an upgrade until you [replace their network policy](/compute/sandboxes/firewall#dynamic-updates).

## Attaching an Archil disk

Pass `mounts` when creating a sandbox to give it access to files on an existing Archil disk. The disk must belong
to your account and be in the same region as the sandbox:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Archil } from "disk";

    const client = new Archil({
      apiKey: process.env.ARCHIL_API_KEY,
      region: "aws-us-east-1",
    });
    const sandbox = await client.sandboxes.create({
      baseImage: "ubuntu:24.04",
      mounts: [{ disk: "dsk-0123456789abcdef", path: "/mnt/data" }],
    });
    console.log((await sandbox.exec("ls /mnt/data")).stdout);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from archil import Archil, SandboxMountSpec

    with Archil(region="aws-us-east-1") as client:
        sandbox = client.sandboxes.create(
            base_image="ubuntu:24.04",
            mounts=[SandboxMountSpec(disk="dsk-0123456789abcdef", path="/mnt/data")],
        )
        print(sandbox.exec("ls /mnt/data").stdout)
    ```
  </Tab>

  <Tab title="REST">
    ```bash theme={null}
    curl --request POST \
      'https://control.green.us-east-1.aws.prod.archil.com/api/sandboxes?wait=true' \
      --header "Authorization: $ARCHIL_API_KEY" \
      --header 'Content-Type: application/json' \
      --data '{
        "base_image": "ubuntu:24.04",
        "mounts": [{ "disk_id": "dsk-0123456789abcdef", "path": "/mnt/data" }]
      }'
    ```

    If the response is still `pending`, poll [Get Sandbox](/api-reference/sandboxes/get-sandbox) until it is `running`
    before executing commands. If it becomes `failed`, inspect `exit_reason`.
  </Tab>
</Tabs>

Archil handles mount authentication automatically. Deleting a sandbox removes its root filesystem.
Mounted disks and their data are preserved.

With a single mount, `path` is optional and defaults to `/mnt/archil`. To mount more disks, add an entry for each
one with a separate path, such as `/mnt/data` and `/mnt/models`.
Use `subdirectory` to expose only part of a disk. See [Create Sandbox](/api-reference/sandboxes/create-sandbox)
for the full mount options and path restrictions.

By default, a mount takes exclusive access to the disk. To share it across sandboxes, use `readOnly` (`read_only`
in Python and REST) for readers or `conditional` for writers. Coordinate concurrent writes in your application
to avoid overwriting each other's changes. See [Sharing Disks](/concepts/sharing-disks) for how these modes work.

Sandboxes keep their mounts across stop/start and pause/resume. To change them, stop the sandbox and pass a new
`mounts` list to [Start Sandbox](/api-reference/sandboxes/start-sandbox). Omit `mounts` to keep the saved list,
or pass `[]` to remove all mounts without deleting their disks. For a paused sandbox, `resume()` keeps its mounts;
use `start()` to change them, which boots from disk and discards the paused memory state.

## Passing environment variables

Environment variables can be set for the whole sandbox, for a single command, or both. Variables passed when the
sandbox is created apply to every command that runs inside it:

```typescript theme={null}
const sandbox = await archil.createSandbox({
  baseImage: "python:3.13",
  env: {
    DATABASE_URL: "postgres://localhost/app",
    LOG_LEVEL: "info",
  },
});
```

Individual processes can add or override their own variables by passing `env` to `run`:

```typescript theme={null}
// Runs with DATABASE_URL from the sandbox, and LOG_LEVEL=debug
const migration = await sandbox.run("python manage.py migrate", {
  env: { LOG_LEVEL: "debug" },
});
await migration.wait();
```

## Starting and stopping sandboxes

A sandbox runs until you shut it down or its [TTL](#setting-a-time-to-live) expires. There are two ways to suspend one,
and they differ in how much state they keep.

Stopping a sandbox sends `SIGTERM` to every running process and shuts down the VM, leaving the disk intact:

```typescript theme={null}
await sandbox.stop();
```

Starting it again boots a fresh VM from that same disk:

```typescript theme={null}
await sandbox.start();
```

Everything written to disk survives, but memory does not, so your application handles shutdown and startup the same way
it would on any machine. You can instead pause a VM to snapshot memory alongside the disk, so a resumed sandbox
picks up exactly where it left off, with running processes intact:

```typescript theme={null}
// Preserve running application state for resume
await sandbox.pause();

// Resume the sandbox from the paused state
await sandbox.resume();
```

Deleting a sandbox releases all of its resources and cannot be undone. A sandbox has to be stopped before it can be
deleted, unless it has already exited or failed:

```typescript theme={null}
await sandbox.stop();
await sandbox.delete();
```

## Forking a sandbox

In some situations, you may want to fork a sandbox into multiple branches from a single point in time. This makes it simple to,
for example, start sandboxes from a template without needing to incur startup latency for a slow-running program or run parallel
experiments on sandbox state from some point in the past.

A sandbox without additional disk mounts can be forked while running, paused, or stopped:

```typescript theme={null}
const newSandbox = await sandbox.fork();
```

After forking, `newSandbox` will be able to run commands independently of the original sandbox, starting from the exact state
of the original sandbox. Forking a running sandbox pauses it while the snapshot is taken and resumes it once the fork is
accepted, so attached process connections close; reconnect with `attach`. A paused or stopped sandbox is left
as it is, and a fork of a stopped sandbox boots from its disk.

A sandbox with mounted disks must be stopped before forking. The fork gets its own root filesystem but mounts
the same additional disks as its parent, so changes to those disks are shared. To run both sandboxes at once,
use a shared mount mode or give each sandbox separate disks. Separate disks also keep their data isolated.

During the preview period, all child sandboxes forked from a parent must be deleted before the parent sandbox is deleted.

## Hosting network services

Archil sandboxes support the ability to host network services, including creating per-sandbox preview URLs for CI-like
workflows.

There are two ways to expose a port for a network service, depending on whether you are inside or outside the sandbox.

Outside the sandbox, you can specify a list of ports to expose when creating the sandbox or dynamically expose a port later.

```typescript theme={null}
const sandbox = await archil.createSandbox({
  ports: [3000]
});
const ports = await sandbox.listPorts();
const hostnames = ports.map((port) => port.hostname);
console.log(hostnames);

const hostname = await sandbox.exposePort(3001);

await sandbox.unexposePort(3001);
```

Exposed ports are public: they accept any TCP protocol over TLS with SNI, without authentication.

For private access, leave the port unexposed and create a port token. Send the token in the `X-Archil-Token` header;
requests without a valid token get `401`. Tokens are checked when a connection opens, so revoking or expiring a token
blocks new connections while open ones stay open. Token access supports HTTP/1.1 over HTTPS only, and publicly exposed
ports ignore tokens.

```typescript theme={null}
const access = await sandbox.createPortToken(8080, {
  // optionally specify a TTL, up to "8760h"; omit for no expiry
  ttl: "5m",
});

// pass X-Archil-Token when making requests to the hostname
const response = await fetch(`https://${access.hostname}`, {
  headers: {
    "X-Archil-Token": access.token,
  }
});

await sandbox.deletePortToken(access);
```

Inside the sandbox, `archil services create` runs a command as a supervised service: it starts immediately and restarts if
it exits. Service ports are private by default, so reach them by exposing the port or with a port token. To let services
publish their own hostnames, create the sandbox with `enable_service_ingress: true` through the
[Create Sandbox API](/api-reference/sandboxes/create-sandbox).

```typescript theme={null}
const serviceProcess = await sandbox.run(
  "archil services create web --tcp-port <port> -- <server run command>",
);
const { stdout } = await serviceProcess.wait();

const service = JSON.parse(stdout);
console.log("Server available at: ", service.hostname);
```

For example, in a sandbox with service ingress enabled, the following runs `python3 -m http.server 8080` and returns a
stable HTTPS URL that proxies traffic to local port 8080. Without service ingress, the service still runs, but its
`hostname` is not routed until you expose the port.

```bash theme={null}
$ archil services create web --tcp-port 8080 -- python3 -m http.server 8080
{
  "after": [],
  "command": ["python3", "-m", "http.server", "8080"],
  "enabled": true,
  "env": {},
  "hostname": "8080-<redacted>.prod-aws-us-west-2-green.archil.app",
  "name": "web",
  "state": {
    "exit_status": null,
    "pid": null,
    "restart_count": 0,
    "result": null,
    "status": "activating"
  },
  "tcp_port": 8080,
  "working_dir": null
}
```

Traffic to an exposed port or service hostname, or a request with a valid port token, automatically starts a stopped
sandbox or resumes a paused one, including one paused by its TTL. All sandbox ports are served over TLS.

<Warning>
  Exposed ports and service hostnames are publicly accessible. Use port tokens, or include authentication/authorization
  in your service, if you want to expose a private service.
</Warning>

## Choosing a base image

Use `baseImage` to start from a public Linux OCI image, such as `python:3.13`. The default is `ubuntu:26.04`.
For private images, build with your registry credentials first, then pass the returned `imageId` when creating
the sandbox. See [Sandbox images](/compute/sandboxes/images) for examples and how to reuse builds.

## Setting a time to live

A sandbox has a hard timeout and an optional idle timeout. Both **pause** the sandbox, preserving its disk, memory, and
processes for `resume()`. Attached process connections close when it pauses; reconnect after resuming. If the pause
snapshot fails, the sandbox is marked `failed`. Stop a paused sandbox before deleting it.

| Setting | Range | Default |
| - | - | - |
| `maxTtlSeconds` | 60–86,400 seconds | 86,400 seconds (24 hours) |
| `idleTtlSeconds` | 0–86,400 seconds | 0 (disabled) |

```typescript theme={null}
const sandbox = await archil.createSandbox({
  maxTtlSeconds: 3600,
  idleTtlSeconds: 300,
});

await sandbox.setTimeout(7200);
await sandbox.setTimeout({ timeoutSeconds: 3600, idleTtlSeconds: 60 });
await sandbox.setTimeout({ idleTtlSeconds: 0 });
```

Updating the hard timeout resets its deadline from now, capped at 24 hours after the current session started.
An idle-only update leaves that deadline unchanged and resets the idle countdown if no process connection is open.
Omitted settings stay unchanged. Updates to a paused or stopped sandbox apply to its next powered-on session.
Starting or resuming begins a new session with a fresh timeout.

Open direct process connections prevent idle expiry. Detached processes and service-port traffic do not keep the idle
timer alive. Leave idle timeout disabled for unattended jobs or services that must stay running between connections.
Activity does not extend the hard timeout. `setTimeout()` can extend it only within the session's 24-hour limit.
For REST, use [Set sandbox timeouts](/api-reference/sandboxes/set-sandbox-timeout).

## Opening interactive shells

By default, processes run without allocating a pseudo-terminal (pty). This is usually desirable, since it signals
to the process that it is not in an interactive terminal. However, some commands, such as those that emit color, full-screen
tools like `vim` and `top`, or tools that need interactive input should instead be launched in a pseudo-terminal.

Provide terminal dimensions to launch a process in a pseudo-terminal. The process can then interactively receive input
and stream output to the caller:

```typescript theme={null}
const shell = await sandbox.run("/bin/sh", {
  terminal: { cols: 120, rows: 40 },
  onOutput: ({ data }) => process.stdout.write(data),
});

await shell.sendInput("ls -la\n");
await shell.sendInput("exit\n");
const { exitCode } = await shell.wait();
```

## Next steps

* [TypeScript SDK](/sdks/typescript#persistent-sandboxes)
* [Python SDK](/sdks/python#persistent-sandboxes)
* [Sandbox images](/compute/sandboxes/images)
* [Create a sandbox](/api-reference/sandboxes/create-sandbox)
* [Create a process connection](/api-reference/sandboxes/create-sandbox-connection)
* [Fork a sandbox](/api-reference/sandboxes/fork-sandbox)
* [Sandbox lifecycle endpoints](/api-reference/sandboxes/pause-sandbox)
* [Sandbox firewall](/compute/sandboxes/firewall)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.