Skip to main content
Sandbox templates allow you to create customized & reusable sandbox environments. They define the tools, languages, frameworks, and configurations that will be available when you spawn a new sandbox instance. Templates are particularly useful for teams who need standardized environments or for creating many specialized sandboxes for repeated use cases (codegen agent, Git PR reviews agent, etc.).

What are sandbox templates?

Sandbox templates are pre-configured images that serve as blueprints for creating sandboxes. Each template includes:
  • Base environment: Operating system and runtime configurations
  • Tools: Languages, frameworks, and development tools
  • Configuration: Environment variables, startup scripts, …
  • Resources: Memory allocated
When you create a sandbox from a template, Blaxel provisions a new instance with all the specifications defined in that template.

How sandbox templates work

  1. Initial setup: Follow this guide to create your sandbox template for the first time. This process also creates your first sandbox instance using this template.
  2. Build phase: Your Dockerfile is used to create a container with all required tools and configurations.
  3. Initialization phase: The sandbox API is injected and startup commands are executed.
  4. Instantiation: New sandboxes can be spawned from this template in seconds.
You cannot directly use “library” container images (such as those hosted on Docker Hub and other registries) as sandbox templates. Instead, you must create one or more custom template images for your sandboxes using Dockerfiles and ensure that each template image includes Blaxel’s sandbox API binary. This is necessary for sandbox functionality like process management and file operations.

Create a sandbox template

You can create a sandbox template using the Blaxel CLI or REST API.

Blaxel CLI

1. Initialize a template

Start by creating a new sandbox template using the Blaxel CLI:
bl new sandbox mytemplate
This creates a new directory with the essential template files:
mytemplate/
├── blaxel.toml        # Template configuration
├── Makefile           # Build commands
├── Dockerfile         # Defines the sandbox environment
└── entrypoint.sh      # Initialization script

2. Customize the Dockerfile

The Dockerfile is the heart of your template. It defines what will be available in your sandbox environment.
# Choose a base image
FROM node:22-alpine

# Set working directory
WORKDIR /app

# Copy sandbox API (required)
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api

# Install system dependencies
RUN apk update && apk add --no-cache \
	git curl python3 make g++ netcat-openbsd \
  && rm -rf /var/cache/apk/*

# Copy and set up entrypoint
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
Always include the sandbox-api binary from the Blaxel base image. This is required for sandbox functionality like process management and file operations.

3. Configure template settings

The blaxel.toml file defines your template’s runtime configuration:
name = "mytemplate"
type = "sandbox"
description = "Full-stack development environment with Node.js and Python"

[runtime]
generation = "mk3"
memory = 8192             # 8GB RAM

# Define exposed ports
[[runtime.ports]]
name = "dev-server"
target = 3000
protocol = "tcp"
[[runtime.ports]]
name = "another-api"
target = 8888
protocol = "tcp"

# Set environment variables
[env]
NODE_ENV = "development"
PYTHON_ENV = "development"
Currently, it is not possible to add or update environment variables for a sandbox after it is created. Ensure that any required environment variables are defined in your Dockerfile, your blaxel.toml file, or at sandbox creation time using the Blaxel SDKs.

4. Define initialization

The entrypoint.sh script runs when a sandbox is created from your template:
#!/bin/sh

# Start the sandbox API (required)
/usr/local/bin/sandbox-api &

# Wait for sandbox API to be ready
echo "Waiting for sandbox API..."
while ! nc -z 127.0.0.1 8080; do
  sleep 0.1
done

echo "Sandbox API ready"

# Initialize your environment
echo "Setting up development environment..."

# Example: Start a development server in the background
if [ -f /app/package.json ]; then
    cd /app
    npm install
    # Execute curl command, we execute it through the sandbox-api so that you can access logs,
    # process status and everything you can do with the sandbox api
    echo "Running Next.js dev server..."
    curl http://127.0.0.1:8080/process -X POST -d '{"workingDir": "/app", "command": "npm run dev", "waitForCompletion": false}' -H "Content-Type: application/json"
fi

# Keep the container running
wait

5. Build and test locally

Before creating the template on Blaxel, test it locally:
# Build the Docker image
make build

# Run locally to test
make run

# Access your sandbox-api on exposed ports
# e.g., http://127.0.0.1:8080
# Example: curl http://127.0.0.1:8080/process

6. Deploy the template

Once satisfied with your configuration, create the template on Blaxel:
bl deploy
This will:
  1. Build your Docker image
  2. Push it to Blaxel’s registry (private to your workspace)
  3. Return an image ID you can use for creating sandboxes
  4. Create a first sandbox with that template. It is completely fine to delete the sandbox afterwards, the template will be kept.
You can monitor the template creation:
bl get sandbox mytemplate --watch
A first sandbox with that template is automatically created on Blaxel upon bl deploy, for you to test in prod-like conditions. You can safely delete the sandbox and keep using the template for new sandboxes.

Blaxel API

Although less common, it is also possible to create a sandbox template and sandbox by directly interacting with the Blaxel API. Ensure that you have the following:

1. Create a ZIP archive

Create a directory with the following project contents:
  • Dockerfile (required) - Defines your custom sandbox image and must include sandbox-api
  • Any custom scripts (e.g., entrypoint.sh for initialization logic)
  • Configuration files or data files as needed
  • Additional dependencies or binaries your sandbox requires
Here is an example of the expected project structure:
mytemplate/
├── Dockerfile             # Required - defines your image
└── entrypoint.sh          # Optional - for custom initialization
  • The Dockerfile is the heart of your template. It defines what will be available in your sandbox environment. See an example.
  • The entrypoint.sh script runs when a sandbox is created from your template. See an example.
The Dockerfile can reference and use any files included in the ZIP archive. Everything gets extracted and built together as a Docker image. Once the files are ready, create a ZIP archive containing the files:
(cd mytemplate && zip -r ../mytemplate.zip .)

2. Create a sandbox resource

Set your Blaxel API key and workspace as environment variables:
export BL_API_KEY=YOUR_API_KEY
export BL_WORKSPACE=YOUR_WORKSPACE_NAME
Make an HTTP POST request to create or update your sandbox resource. This will return an upload URL for the ZIP archive.
curl -v -X POST "https://api.blaxel.ai/v0/sandboxes?upload=true" \
  -H "Authorization: Bearer $BL_API_KEY" \
  -H "X-Blaxel-Workspace: $BL_WORKSPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "blaxel.ai/v1alpha1",
    "kind": "Sandbox",
    "metadata": {
      "name": "my-sandbox"
    },
    "spec": {
      "runtime": {
        "memory": 2048
      },
      "region": "us-pdx-1"
    }
  }'
Note the upload=true query parameter in the request, which indicates intent to upload custom code. The Blaxel API returns a JSON response. The response contains an x-blaxel-upload-url response header, containing the target URL to use when uploading your template. The URL is in the response headers, not the JSON body. Example response:
HTTP/2 200
x-blaxel-upload-url: https://controlplane-prod-build-sources...
Refer to the documentation on sandbox configuration parameters for more information on the body of the POST request.

3. Upload ZIP archive

Use an HTTP PUT request to upload the ZIP file to the upload URL. Replace the placeholder URL in the command below with the value of the x-blaxel-upload-url response header received earlier.
export UPLOAD_URL="https://controlplane-prod-build-sources..."
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/zip" \
  --data-binary @mytemplate.zip
The upload is successful when you receive a 200 OK status code.

4. Monitor deployment status

After uploading, poll the sandbox status endpoint to track the build and deployment progress. Make a GET request to https://api.blaxel.ai/v0/sandboxes/<SANDBOX-NAME>, where SANDBOX-NAME is the metadata.name specified in the initial POST request.
watch -n 1 'curl -s -X GET https://api.blaxel.ai/v0/sandboxes/my-sandbox -H "Authorization: Bearer $BL_API_KEY" -H "X-Blaxel-Workspace: $BL_WORKSPACE" | jq -r ".status"'
The status field of the response will progress through these values:
StatusDescription
UPLOADINGCode archive is being uploaded
BUILDINGDocker image is being built
DEPLOYINGContainer is being deployed to the cluster
DEPLOYEDSandbox is ready to use
FAILEDDeployment failed (check build logs)
DEACTIVATEDSandbox has been deactivated
Continue polling every 3-5 seconds until the status reaches DEPLOYED or FAILED. A first sandbox with that template is automatically created on Blaxel once deployment succeeds. You can safely delete the sandbox and keep using the template for new sandboxes.

Example script

Here’s a complete example script that performs all the steps above:
#!/bin/bash

# Real deployment script that executes the documented API workflow
set -e

# Configuration
SANDBOX_NAME="my-custom-sandbox-$(date +%s)"
SOURCE_DIR="mytemplate"
ZIP_FILE="mytemplate.zip"
BASE_URL="https://api.blaxel.ai/v0"

# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Cleanup function
cleanup() {
  if [ -f "$ZIP_FILE" ]; then
    rm -f "$ZIP_FILE"
    echo -e "\n${GREEN}✓ Cleaned up temporary files${NC}"
  fi
}

# Set trap to cleanup on exit
trap cleanup EXIT

# Validate credentials
if [ -z "$BL_API_KEY" ]; then
  echo -e "${RED}Error: BL_API_KEY not set${NC}"
  echo "Get your API key from workspace settings and set it with:"
  echo "  export BL_API_KEY='your-api-key'"
  exit 1
fi

if [ -z "$BL_WORKSPACE" ]; then
  echo -e "${RED}Error: BL_WORKSPACE not set${NC}"
  echo "Set your workspace name with:"
  echo "  export BL_WORKSPACE='your-workspace-name'"
  exit 1
fi

# Check if source directory exists
if [ ! -d "$SOURCE_DIR" ]; then
  echo -e "${RED}Error: $SOURCE_DIR directory not found${NC}"
  exit 1
fi

# Create ZIP archive
echo -e "${BLUE}Creating ZIP archive from $SOURCE_DIR...${NC}"
cd "$SOURCE_DIR"
zip -q -r "../${ZIP_FILE}" .
cd ..

FILE_SIZE=$(wc -c < "$ZIP_FILE" | tr -d ' ')
echo -e "${GREEN}✓ ZIP archive created: $ZIP_FILE ($FILE_SIZE bytes)${NC}\n"

# Step 1: Create sandbox and get upload URL
echo -e "${BLUE}[1/4] Creating sandbox '$SANDBOX_NAME'...${NC}"

# Create temporary file for headers
HEADERS_FILE=$(mktemp)

CREATE_RESPONSE=$(curl -s -D "$HEADERS_FILE" -w "\n%{http_code}" -X POST "$BASE_URL/sandboxes?upload=true" \
  -H "Authorization: Bearer $BL_API_KEY" \
  -H "X-Blaxel-Workspace: $BL_WORKSPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "blaxel.ai/v1alpha1",
    "kind": "Sandbox",
    "metadata": {
      "name": "'"$SANDBOX_NAME"'"
    },
    "spec": {
      "runtime": {
        "memory": 2048
      },
      "region": "us-pdx-1"
    }
  }')

HTTP_CODE=$(echo "$CREATE_RESPONSE" | tail -n 1)
RESPONSE_BODY=$(echo "$CREATE_RESPONSE" | sed '$d')

if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
  echo -e "${RED}✗ Failed to create sandbox (HTTP $HTTP_CODE)${NC}"
  echo "$RESPONSE_BODY" | jq . 2>/dev/null || echo "$RESPONSE_BODY"
  rm -f "$HEADERS_FILE"
  exit 1
fi

# Extract upload URL from response headers
UPLOAD_URL=$(grep -i "x-blaxel-upload-url:" "$HEADERS_FILE" | cut -d' ' -f2- | tr -d '\r\n')

if [ -z "$UPLOAD_URL" ]; then
  echo -e "${RED}✗ No upload URL received in response headers${NC}"
  rm -f "$HEADERS_FILE"
  exit 1
fi

rm -f "$HEADERS_FILE"

echo -e "${GREEN}✓ Sandbox created${NC}"
echo ""

# Step 2: Upload ZIP file
echo -e "${BLUE}[2/4] Uploading code archive ($FILE_SIZE bytes)...${NC}"
UPLOAD_RESPONSE=$(curl -s -w "%{http_code}" -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/zip" \
  --data-binary "@$ZIP_FILE")

HTTP_CODE="${UPLOAD_RESPONSE: -3}"
if [ "$HTTP_CODE" != "200" ]; then
  echo -e "${RED}✗ Upload failed with status $HTTP_CODE${NC}"
  exit 1
fi

echo -e "${GREEN}✓ Upload completed${NC}"
echo ""

# Step 3: Monitor deployment status
echo -e "${BLUE}[3/4] Monitoring deployment status...${NC}"
MAX_WAIT=900  # 15 minutes
START_TIME=$(date +%s)
LAST_STATUS=""

while true; do
  # Check timeout
  CURRENT_TIME=$(date +%s)
  ELAPSED=$((CURRENT_TIME - START_TIME))
  if [ $ELAPSED -gt $MAX_WAIT ]; then
    echo -e "${RED}✗ Deployment timed out after $MAX_WAIT seconds${NC}"
    exit 1
  fi

  # Get current status
  STATUS_RESPONSE=$(curl -s -X GET "$BASE_URL/sandboxes/$SANDBOX_NAME" \
    -H "Authorization: Bearer $BL_API_KEY" \
    -H "X-Blaxel-Workspace: $BL_WORKSPACE")

  STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.status // empty')

  if [ -z "$STATUS" ]; then
    echo -e "${YELLOW}Warning: Could not get status, retrying...${NC}"
    sleep 3
    continue
  fi

  # Log status changes
  if [ "$STATUS" != "$LAST_STATUS" ]; then
    echo "  Status: $STATUS"
    LAST_STATUS=$STATUS
  fi

  # Check terminal states
  if [ "$STATUS" = "DEPLOYED" ]; then
    IMAGE=$(echo "$STATUS_RESPONSE" | jq -r '.spec.runtime.image // empty')
    echo ""
    echo -e "${GREEN}🎉 Deployment complete!${NC}"
    echo "Sandbox: $SANDBOX_NAME"
    echo "Image: $IMAGE"
    echo ""

    # Step 4: Show how to use it
    echo -e "${BLUE}[4/4] How to use your sandbox:${NC}"
    echo ""
    echo "Run a command:"
    echo "  bl run sandbox $SANDBOX_NAME"
    echo ""
    echo "Get sandbox details:"
    echo "  bl get sandbox $SANDBOX_NAME"
    echo ""
    echo "View logs:"
    echo "  bl logs sandbox $SANDBOX_NAME"
    echo ""
    echo "Delete sandbox:"
    echo "  bl delete sandbox $SANDBOX_NAME"
    echo ""
    exit 0
  elif [ "$STATUS" = "FAILED" ]; then
    echo ""
    echo -e "${RED}✗ Deployment failed${NC}"
    echo ""
    echo "Check build logs with:"
    echo "  curl -X GET '$BASE_URL/sandboxes/$SANDBOX_NAME/build-logs' \\"
    echo "    -H 'Authorization: Bearer \$BL_API_KEY' \\"
    echo "    -H 'X-Blaxel-Workspace: \$BL_WORKSPACE'"
    exit 1
  elif [ "$STATUS" = "DEACTIVATED" ] || [ "$STATUS" = "DEACTIVATING" ] || [ "$STATUS" = "DELETING" ]; then
    echo ""
    echo -e "${RED}✗ Unexpected status: $STATUS${NC}"
    exit 1
  fi

  # Wait before next poll
  sleep 3
done
The script accepts a source directory path containing the Dockerfile and related code and uses it to deploy a sandbox on Blaxel. It monitors the deployment status until completion and also cleans up temporary files. To use this script, first export your API key and credentials as below:
export BL_API_KEY=YOUR-API-KEY
export BL_WORKSPACE=YOUR-WORKSPACE-NAME
Create a sandbox directory named mytemplate with your custom Dockerfile and entrypoint script:
mkdir -p mytemplate
Save and run the script as deploy-sandbox.sh (Shell), index.ts (TypeScript) or main.py (Python):
chmod +x deploy-sandbox.sh
./deploy-sandbox.sh

Use a template

Once your template is created, spawn new sandboxes instantly by using the image ID:
# Retrieve your IMAGE_ID
bl get sandboxes mytemplate -ojson | jq -r '.[0].spec.runtime.image'
import { SandboxInstance } from "@blaxel/core";

// Create a new sandbox
const sandbox = await SandboxInstance.create({
  name: "my-sandbox-from-template",
  image: "IMAGE_ID",
  memory: 4096,
  region: "us-pdx-1",
  ports: [{ name: "nextjs-dev", target: 3000 }, { name: "another-api", target: 8888 }]
});

Update a template

To update an existing template:
  1. Modify your Dockerfile or configuration
  2. Rebuild locally to test changes
  3. Deploy the new version:
bl deploy
The new revision becomes available while the old one remains accessible.

Best practices

1. Optimize for cold starts

  • Use smaller base images (Alpine when possible)
  • Minimize layers in Dockerfile
  • Pre-install only essential packages
  • Defer optional installations to runtime

2. Cache dependencies

# Good: Cache package installations
COPY package*.json ./
RUN npm ci --only=production
COPY . .

# Bad: Invalidates cache on any file change
COPY . .
RUN npm install

3. Security considerations

  • Don’t include secrets in templates
  • Use Blaxel’s secrets management for sensitive data
  • Keep base images updated
  • Scan for vulnerabilities regularly

4. Resource optimization

Choose appropriate resources for your use case:
Use CaseMemory
Light development2GB
Small web application4GB
Full-stack web8GB