Documentation Index
Fetch the complete documentation index at: https://docs.blaxel.ai/llms.txt
Use this file to discover all available pages before exploring further.
This tutorial explains how to run an Astro application inside a Blaxel sandbox and expose it securely using sandbox preview URLs.
Prerequisites
Before starting, ensure you have:
- Blaxel CLI installed and authenticated (
bl login)
- Node.js 18+ installed
@blaxel/core package installed in your project (npm install @blaxel/core)
Architecture Overview
Running Astro inside a Blaxel sandbox requires a few adjustments:
- Configuring
astro.config.mjs with host: '0.0.0.0' and allowedHosts: true
- Exposing the Astro dev server via a Blaxel preview URL
Create a base sandbox image
Dockerfile
FROM oven/bun:alpine
RUN apk update && apk add --no-cache \
git \
curl \
netcat-openbsd \
nodejs \
npm \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
# Create Astro project with npx (more reliable for template downloads), then use bun for deps
RUN npx create-astro@latest /app --template basics --no-install --no-git --yes \
&& bun install
COPY ./astro.config.mjs /app/astro.config.mjs
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
astro.config.mjs
Create an astro.config.mjs file that allows external connections:
// @ts-check
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
server: {
host: '0.0.0.0',
port: 4321,
allowedHosts: true
}
});
entrypoint.sh
Create an entrypoint script that starts the sandbox API and the dev server:
#!/bin/sh
# Set environment variables
export PATH="/usr/local/bin:$PATH"
# Start sandbox-api in the background
/usr/local/bin/sandbox-api &
# Function to wait for port to be available
wait_for_port() {
local port=$1
local timeout=30
local count=0
echo "Waiting for port $port to be available..."
while ! nc -z localhost $port; do
sleep 1
count=$((count + 1))
if [ $count -gt $timeout ]; then
echo "Timeout waiting for port $port"
exit 1
fi
done
echo "Port $port is now available"
}
# Wait for port 8080 to be available
wait_for_port 8080
# Execute curl command to start Astro dev server
echo "Running Astro dev server..."
curl http://localhost:8080/process \
-X POST \
-H "Content-Type: application/json" \
-d '{
"name": "dev-server",
"workingDir": "/app",
"command": "bun run dev",
"waitForCompletion": false,
"restartOnFailure": true,
"maxRestarts": 25
}'
wait
blaxel.toml
Create a blaxel.toml file in the same directory as your Dockerfile:
type = "sandbox"
name = "astro-template"
[runtime]
memory = 4096
[[runtime.ports]]
name = "astro-dev"
target = 4321
protocol = "tcp"
Deploy the sandbox image
Deploy the image by running:
Create or reuse a sandbox
Create a sandbox from the base image:
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-astro-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "astro",
},
image: "astro-template:latest",
memory: 4096,
});
Astro dev servers work well with permissive CORS headers when accessed through a preview URL:
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
Alternatively, you can use custom domains to expose previews on your own domain.
Create the preview URL
Astro runs on port 4321, so we expose that port via a preview URL:
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
public: false,
port: 4321,
},
});
Generate a preview token
To securely access the preview, a token is required:
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24); // 1 day
const token = await preview.tokens.create(expiresAt);
Start the dev server
If not using the entrypoint script, you can start the dev server programmatically:
async function startDevServer(sandbox: SandboxInstance) {
console.log("Starting Astro dev server...");
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [4321],
restartOnFailure: true,
maxRestarts: 25,
});
}
Stream logs
To monitor the Astro dev server output in real-time:
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log);
},
});
// When done monitoring, close the stream:
logStream.close();
Access the Astro application
Once everything is running, the Astro application will be available at https://<PREVIEW-URL>?bl_preview_token=<TOKEN>
Complete example
Here is a full example combining all the steps:
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-astro-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [4321],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function main() {
try {
// Create or reuse the sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "astro",
},
image: "astro-template:latest",
memory: 4096,
});
// Create preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 4321,
},
});
// Generate preview token
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
// Start dev server if not already running
const processes = await sandbox.process.list();
if (!processes.find((p) => p.name === "dev-server")) {
await startDevServer(sandbox);
}
// Print access URL
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
console.log(`Astro Preview URL: ${webUrl}`);
// Stream logs
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log)
},
});
// Keep running until interrupted
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();