diff --git a/Applications/Overview.mdx b/Applications/Overview.mdx new file mode 100644 index 00000000..0b5e0d51 --- /dev/null +++ b/Applications/Overview.mdx @@ -0,0 +1,602 @@ +--- +title: 'Applications' +sidebarTitle: 'Overview' +description: 'Deploy long-running services as publicly accessible endpoints with revision management, canary traffic splitting, and custom URLs.' +--- + +Applications are long-running deployed services that run your custom code as publicly accessible endpoints. They are ideal for web servers, APIs, dashboards, and any service that needs to be always-on. + +Applications run on Blaxel's mk3 micro-VM infrastructure and support revision management with canary traffic splitting, custom domain URLs, and deployment from source code, images, or forked sandboxes. + + +Application Runtime is currently in preview. + + +## Key concepts + +- **Application**: A long-running deployment that serves traffic on a public URL. +- **Revision**: An immutable snapshot of the application code, image, environment, and memory configuration. Each deploy creates a new revision (max 5 kept). +- **Traffic splitting**: Route traffic between an active revision and a canary revision using percentage-based splitting. +- **Sticky sessions**: Optional session affinity via `stickySessionTtl` to keep users on the same revision. +- **Custom URLs**: Map verified custom domains to your application. + +## Create an application + +### Using the SDKs + + + +```typescript TypeScript +import { ApplicationInstance } from "@blaxel/core"; + +const app = await ApplicationInstance.create({ + name: "my-app", + image: "my-registry/my-image:latest", + memory: 2048, + region: "us-pdx-1", + envs: [{ name: "NODE_ENV", value: "production" }], +}); +``` + +```python Python +import asyncio +from blaxel.core import ApplicationInstance + +async def main(): + app = await ApplicationInstance.create({ + "name": "my-app", + "image": "my-registry/my-image:latest", + "memory": 2048, + "region": "us-pdx-1", + "envs": [{"name": "NODE_ENV", "value": "production"}], + }) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +```go Go +import blaxel "github.com/blaxel-ai/sdk-go" + +app, err := client.Applications.Create(ctx, blaxel.ApplicationCreateParams{ + Body: blaxel.Application{ + Metadata: blaxel.Metadata{Name: "my-app"}, + Spec: blaxel.ApplicationSpec{ + Enabled: true, + Region: "us-pdx-1", + Revisions: []blaxel.AppRevision{{ + Image: "my-registry/my-image:latest", + Memory: 2048, + Envs: []blaxel.Env{{Name: "NODE_ENV", Value: "production"}}, + }}, + }, + }, +}) +``` + + + +### Using the CLI + +Deploy from a project directory containing a `blaxel.toml`: + +```bash +bl deploy --type application +``` + +Example `blaxel.toml`: + +```toml +name = "my-app" +type = "application" +workspace = "my-workspace" + +[env] +NODE_ENV = "production" + +[deploy] +region = "us-pdx-1" +memory = 2048 +``` + +To deploy a pre-built image, set the `image` field: + +```toml +name = "my-app" +type = "application" +workspace = "my-workspace" +image = "my-registry/my-image:latest" +``` + +### Using the HTTP API + +```bash +curl -X POST https://api.blaxel.ai/v0/applications \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "metadata": { "name": "my-app" }, + "spec": { + "enabled": true, + "region": "us-pdx-1", + "revisions": [{ + "image": "my-registry/my-image:latest", + "memory": 2048, + "envs": [{ "name": "NODE_ENV", "value": "production" }] + }] + } + }' +``` + +## Retrieve an application + + + +```typescript TypeScript +import { ApplicationInstance } from "@blaxel/core"; + +const app = await ApplicationInstance.get("my-app"); +console.log(app.status); +``` + +```python Python +from blaxel.core import ApplicationInstance + +app = await ApplicationInstance.get("my-app") +print(app.status) +``` + +```go Go +app, err := client.Applications.Get(ctx, "my-app", blaxel.ApplicationGetParams{}) +``` + + + +CLI: + +```bash +bl get applications +``` + +HTTP API: + +```bash +curl https://api.blaxel.ai/v0/applications/my-app \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" +``` + +## List applications + + + +```typescript TypeScript +import { ApplicationInstance } from "@blaxel/core"; + +const apps = await ApplicationInstance.list(); +``` + +```python Python +from blaxel.core import ApplicationInstance + +apps = await ApplicationInstance.list() +``` + +```go Go +apps, err := client.Applications.List(ctx, blaxel.ApplicationListParams{}) +``` + + + +CLI: + +```bash +bl get applications +``` + +HTTP API: + +```bash +curl https://api.blaxel.ai/v0/applications \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" +``` + +## Update an application + +Updating an application with a new image or configuration creates a new revision. + + + +```typescript TypeScript +import { ApplicationInstance } from "@blaxel/core"; + +const app = await ApplicationInstance.get("my-app"); +const updated = await app.update({ + image: "my-registry/my-image:v2", + memory: 4096, +}); +``` + +```python Python +from blaxel.core import ApplicationInstance + +app = await ApplicationInstance.get("my-app") +updated = await app.update({ + "image": "my-registry/my-image:v2", + "memory": 4096, +}) +``` + +```go Go +updated, err := client.Applications.Update(ctx, "my-app", blaxel.ApplicationUpdateParams{ + Body: blaxel.Application{ + Metadata: blaxel.Metadata{Name: "my-app"}, + Spec: blaxel.ApplicationSpec{ + Enabled: true, + Region: "us-pdx-1", + Revisions: []blaxel.AppRevision{{ + Image: "my-registry/my-image:v2", + Memory: 4096, + }}, + }, + }, +}) +``` + + + +HTTP API: + +```bash +curl -X PUT https://api.blaxel.ai/v0/applications/my-app \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "metadata": { "name": "my-app" }, + "spec": { + "enabled": true, + "region": "us-pdx-1", + "revisions": [{ + "image": "my-registry/my-image:v2", + "memory": 4096 + }] + } + }' +``` + +## Delete an application + + + +```typescript TypeScript +import { ApplicationInstance } from "@blaxel/core"; + +await ApplicationInstance.delete("my-app"); +``` + +```python Python +from blaxel.core import ApplicationInstance + +await ApplicationInstance.delete("my-app") +``` + +```go Go +err := client.Applications.Delete(ctx, "my-app") +``` + + + +CLI: + +```bash +bl delete application my-app +``` + +HTTP API: + +```bash +curl -X DELETE https://api.blaxel.ai/v0/applications/my-app \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" +``` + +## Revisions + +Each deploy creates a new revision. A maximum of 5 revisions are kept per application. Revisions contain the image, environment variables, memory allocation, and port configuration. + +### List revisions + + + +```typescript TypeScript +import { ApplicationInstance } from "@blaxel/core"; + +const app = await ApplicationInstance.get("my-app"); +const revisions = await app.listRevisions(); +``` + +```python Python +from blaxel.core import ApplicationInstance + +app = await ApplicationInstance.get("my-app") +revisions = await app.list_revisions() +``` + +```go Go +revisions, err := client.Applications.ListRevisions(ctx, "my-app") +``` + + + +HTTP API: + +```bash +curl https://api.blaxel.ai/v0/applications/my-app/revisions \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" +``` + +### Traffic splitting + +Control how traffic is distributed between revisions using the `traffic` parameter on deploy: + +- `traffic=100` (default): the new revision receives 100% of traffic (full deployment). +- `traffic=1-99`: canary deployment. The new revision receives this percentage, and the active revision receives the rest. +- `traffic=0`: staging deployment. The new revision is deployed but receives no traffic. + +To deploy a canary revision via the HTTP API: + +```bash +curl -X PUT https://api.blaxel.ai/v0/applications/my-app \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "metadata": { "name": "my-app" }, + "spec": { + "enabled": true, + "region": "us-pdx-1", + "revision": { + "traffic": 20 + }, + "revisions": [{ + "image": "my-registry/my-image:v3", + "memory": 2048 + }] + } + }' +``` + +### Sticky sessions + +Enable sticky sessions to keep users routed to the same revision for the duration of the TTL: + +```json +{ + "spec": { + "revision": { + "stickySessionTtl": 3600 + } + } +} +``` + +Setting `stickySessionTtl` to `0` disables sticky sessions. + +## Custom URLs + +By default, applications are accessible at a generated URL. You can configure custom URLs using verified custom domains in your workspace. + +### Create or update an application with custom URLs + +Pass the `urls` field in the application spec to attach a verified custom domain. + + + +```typescript TypeScript +import { ApplicationInstance } from "@blaxel/core"; + +const app = await ApplicationInstance.create({ + metadata: { + name: "my-app", + }, + spec: { + enabled: true, + region: "us-pdx-1", + urls: [{ domain: "app.example.com" }], + revisions: [{ + image: "my-registry/my-image:latest", + memory: 2048, + }], + }, +}); +``` + +```python Python +from blaxel.core import ApplicationInstance + +app = await ApplicationInstance.create({ + "metadata": {"name": "my-app"}, + "spec": { + "enabled": True, + "region": "us-pdx-1", + "urls": [{"domain": "app.example.com"}], + "revisions": [{ + "image": "my-registry/my-image:latest", + "memory": 2048, + }], + }, +}) +``` + +```go Go +app, err := client.Applications.Create(ctx, blaxel.ApplicationCreateParams{ + Body: blaxel.Application{ + Metadata: blaxel.Metadata{Name: "my-app"}, + Spec: blaxel.ApplicationSpec{ + Enabled: true, + Region: "us-pdx-1", + Urls: []blaxel.AppUrl{{Domain: "app.example.com"}}, + Revisions: []blaxel.AppRevision{{ + Image: "my-registry/my-image:latest", + Memory: 2048, + }}, + }, + }, +}) +``` + + + +To add a custom URL to an existing application, use update: + + + +```typescript TypeScript +import { ApplicationInstance } from "@blaxel/core"; + +const app = await ApplicationInstance.get("my-app"); +const updated = await ApplicationInstance.update("my-app", { + metadata: app.metadata, + spec: { + ...app.spec, + urls: [{ domain: "app.example.com" }], + }, +}); +``` + +```python Python +from blaxel.core import ApplicationInstance + +app = await ApplicationInstance.get("my-app") +updated = await app.update({ + "metadata": app.metadata, + "spec": { + **app.spec, + "urls": [{"domain": "app.example.com"}], + }, +}) +``` + +```go Go +updated, err := client.Applications.Update(ctx, "my-app", blaxel.ApplicationUpdateParams{ + Body: blaxel.Application{ + Metadata: existing.Metadata, + Spec: blaxel.ApplicationSpec{ + Enabled: existing.Spec.Enabled, + Region: existing.Spec.Region, + Urls: []blaxel.AppUrl{{Domain: "app.example.com"}}, + Revisions: existing.Spec.Revisions, + }, + }, +}) +``` + + + +HTTP API: + +```bash +curl -X PUT https://api.blaxel.ai/v0/applications/my-app \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "metadata": { "name": "my-app" }, + "spec": { + "enabled": true, + "region": "us-pdx-1", + "urls": [ + { "domain": "app.example.com" } + ], + "revisions": [{ + "image": "my-registry/my-image:latest", + "memory": 2048 + }] + } + }' +``` + +For wildcard custom domains (e.g. `*.sandbox.example.com`), use the `subdomain` field: + +```json +{ + "spec": { + "urls": [ + { "subdomain": "myapp", "domain": "sandbox.example.com" } + ] + } +} +``` + +### Create a custom domain for an application + +You can also create a custom domain scoped to a specific application using the dedicated endpoint. After creation, configure DNS records and verify domain ownership before it becomes active. + +```bash +curl -X POST https://api.blaxel.ai/v0/applications/my-app/customdomains \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "metadata": { "name": "app.example.com" }, + "spec": { + "type": "applications", + "domain": "app.example.com", + "region": "us-pdx-1" + } + }' +``` + + +The domain must be a verified custom domain in your workspace. Custom domain verification is managed in the [Infrastructure section](/Infrastructure/Custom-domains) of the Blaxel Console. + + +## Environment variables and secrets + +Pass environment variables to your application through the revision's `envs` field. Each entry supports either a plain `value` or a reference to a Blaxel `secretName`. + +```json +{ + "spec": { + "revisions": [{ + "image": "my-registry/my-image:latest", + "memory": 2048, + "envs": [ + { "name": "DATABASE_URL", "value": "postgres://..." }, + { "name": "API_KEY", "secretName": "my-api-key-secret" } + ] + }] + } +} +``` + +## Memory and compute + +Memory allocation is set per revision in megabytes. The default is 2048 MB. CPU resources are allocated proportionally based on memory (CPU = memory / 2048 cores). + +| Memory | CPU cores | +|--------|-----------| +| 2048 MB | 1 core | +| 4096 MB | 2 cores | +| 8192 MB | 4 cores | + +## Application deployment statuses + +During the deployment process, the possible application statuses are: + +- `DEPLOYING`: The application deployment is in progress. +- `DEPLOYED`: The application is running and serving traffic. +- `FAILED`: An error occurred during the build or deployment. + + + + Create snapshots and fork sandboxes into applications or new sandboxes. + + + Manage environment variables and secrets for your deployments. + + diff --git a/Sandboxes/Snapshots-and-fork.mdx b/Sandboxes/Snapshots-and-fork.mdx new file mode 100644 index 00000000..f2f6b897 --- /dev/null +++ b/Sandboxes/Snapshots-and-fork.mdx @@ -0,0 +1,263 @@ +--- +title: 'Snapshots and fork' +sidebarTitle: 'Snapshots & fork' +description: 'Create point-in-time snapshots of sandboxes and fork them into new sandboxes or applications.' +--- + +Snapshots capture the current state of a sandbox at a point in time. Use snapshots to preserve sandbox state before making changes, or as the basis for forking a sandbox into a new sandbox or [application](/Applications/Overview). + +## Create a snapshot + +Create a snapshot of an existing sandbox. The snapshot captures the filesystem, running processes, and memory state. + + + +```typescript TypeScript +import { SandboxInstance } from "@blaxel/core"; + +const sandbox = await SandboxInstance.get("my-sandbox"); +const snapshot = await sandbox.createSnapshot({ name: "before-migration" }); +``` + +```python Python +from blaxel.core import SandboxInstance + +sandbox = await SandboxInstance.get("my-sandbox") +snapshot = await sandbox.create_snapshot({"name": "before-migration"}) +``` + + + +HTTP API: + +```bash +curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/snapshots \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ "name": "before-migration" }' +``` + +## List snapshots + + + +```typescript TypeScript +import { SandboxInstance } from "@blaxel/core"; + +const sandbox = await SandboxInstance.get("my-sandbox"); +const snapshots = await sandbox.listSnapshots(); +``` + +```python Python +from blaxel.core import SandboxInstance + +sandbox = await SandboxInstance.get("my-sandbox") +snapshots = await sandbox.list_snapshots() +``` + + + +HTTP API: + +```bash +curl https://api.blaxel.ai/v0/sandboxes/my-sandbox/snapshots \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" +``` + +## Delete a snapshot + +```bash +curl -X DELETE https://api.blaxel.ai/v0/sandboxes/my-sandbox/snapshots/snap_abc123 \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" +``` + +## Fork a sandbox + +Forking creates a new resource (sandbox or application) from an existing sandbox. The fork inherits the source sandbox's image, memory configuration, environment variables, and port settings. + +### Fork into a new sandbox + + + +```typescript TypeScript +import { SandboxInstance } from "@blaxel/core"; + +const result = await SandboxInstance.fork("my-sandbox", { + targetName: "my-sandbox-copy", + targetType: "sandbox", +}); +``` + +```python Python +from blaxel.core import SandboxInstance + +result = await SandboxInstance.fork("my-sandbox", { + "target_name": "my-sandbox-copy", + "target_type": "sandbox", +}) +``` + + + +CLI: + +```bash +bl fork sbx/my-sandbox sbx/my-sandbox-copy +``` + +HTTP API: + +```bash +curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "targetType": "sandbox", + "targetName": "my-sandbox-copy" + }' +``` + +### Fork into an application + +Create an [application](/Applications/Overview) directly from a running sandbox. The application inherits the sandbox's image, memory, environment variables, and port. + + + +```typescript TypeScript +import { SandboxInstance } from "@blaxel/core"; + +const result = await SandboxInstance.fork("my-sandbox", { + targetName: "my-app", + targetType: "application", + traffic: 100, + port: 8080, +}); +``` + +```python Python +from blaxel.core import SandboxInstance + +result = await SandboxInstance.fork("my-sandbox", { + "target_name": "my-app", + "target_type": "application", + "traffic": 100, + "port": 8080, +}) +``` + + + +CLI: + +```bash +bl fork sbx/my-sandbox app/my-app --traffic 100 --port 8080 +``` + +HTTP API: + +```bash +curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "targetType": "application", + "targetName": "my-app", + "traffic": 100, + "port": 8080 + }' +``` + +### Fork with canary traffic + +Fork into an existing application as a canary revision receiving a percentage of traffic: + +```bash +bl fork sbx/my-sandbox app/my-app --traffic 20 --port 8080 +``` + +### Fork with a custom domain + +Attach a custom domain to the forked application using the `customDomain` parameter: + + + +```typescript TypeScript +import { SandboxInstance } from "@blaxel/core"; + +const result = await SandboxInstance.fork("my-sandbox", { + targetName: "my-app", + targetType: "application", + traffic: 100, + customDomain: "app.example.com", +}); +``` + +```python Python +from blaxel.core import SandboxInstance + +result = await SandboxInstance.fork("my-sandbox", { + "target_name": "my-app", + "target_type": "application", + "traffic": 100, + "custom_domain": "app.example.com", +}) +``` + + + +HTTP API: + +```bash +curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "targetType": "application", + "targetName": "my-app", + "traffic": 100, + "customDomain": "app.example.com" + }' +``` + +### Fork from a snapshot + +Use the `snapshotId` parameter to fork from a specific snapshot instead of the current sandbox state: + +```bash +curl -X POST https://api.blaxel.ai/v0/sandboxes/my-sandbox/fork \ + -H "Authorization: Bearer $BL_API_KEY" \ + -H "X-Blaxel-Workspace: my-workspace" \ + -H "Content-Type: application/json" \ + -d '{ + "targetType": "application", + "targetName": "my-app", + "snapshotId": "snap_abc123" + }' +``` + +## Fork request parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `targetType` | string | Target resource type: `sandbox` or `application` | +| `targetName` | string | Name of the resource to create or update | +| `traffic` | integer | Traffic percentage for the new revision (0-100, applications only) | +| `port` | integer | Port to expose | +| `customDomain` | string | Custom domain for the application | +| `prefix` | string | URL prefix for the application | +| `snapshotId` | string | Fork from a specific snapshot instead of current state | + + + + Deploy long-running services with revision management and traffic splitting. + + + Learn more about sandbox lifecycle and configuration. + + diff --git a/docs.json b/docs.json index c59f80e8..4f780553 100644 --- a/docs.json +++ b/docs.json @@ -66,6 +66,7 @@ }, "Sandboxes/Volumes", "Sandboxes/Templates", + "Sandboxes/Snapshots-and-fork", "Sandboxes/Schedules", "Sandboxes/best-practices" ] @@ -108,6 +109,12 @@ "Jobs/Variables-and-secrets-jobs" ] }, + { + "group": "Applications", + "pages": [ + "Applications/Overview" + ] + }, { "group": "Agents Hosting", "pages": [