Setting up Vite with Blaxel Sandboxes
Last updated: January 19, 2026
This guide explains how to run a Vite (React + TypeScript) application inside a Blaxel Sandbox and expose it securely using Blaxel Previews.
Overview
The guide covers:
Building a sandbox image for Vite
Creating or reusing a Blaxel sandbox
Configuring Vite to work behind Blaxel previews
Launching Vite and accessing it through a preview URL
Prerequisites
Before starting, ensure you have:
Blaxel CLI installed and authenticated (
bl login)Node.js 18+ installed
@blaxel/corepackage installed in your project (npm install @blaxel/core)
Architecture Overview
Running Vite inside a Blaxel sandbox requires a few adjustments compared to local development:
Vite normally binds to localhost
Blaxel exposes services via preview URLs
Vite must be configured to accept external connections and allow all hosts
This is solved by:
Configuring
vite.config.tswithhost: '0.0.0.0'andallowedHosts: trueExposing the Vite dev server via a Blaxel Preview
Sandbox Image for Vite
Dockerfile
FROM node:22-alpine
RUN apk update && apk add --no-cache \
git curl bash \
&& rm -rf /var/cache/apk/*
WORKDIR /app
# Install Bun
RUN curl -fsSL https://bun.sh/install | bash && \
ln -s /root/.bun/bin/bun /usr/local/bin/bun && \
ln -s /root/.bun/bin/bunx /usr/local/bin/bunx
RUN bun create vite@latest . --template react-ts
# Install patch-package globally first to avoid postinstall script errors
RUN bun install -g patch-package
RUN bun install
COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api
COPY ./vite.config.ts /app/vite.config.ts
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]vite.config.ts
Create a vite.config.ts file that allows external connections:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
host: '0.0.0.0',
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
echo "Running Vite dev server with Bun..."
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
}'
waitblaxel.toml
Create a blaxel.toml file in the same directory as your Dockerfile:
type = "sandbox"
name = "vite-template"
[runtime]
memory = 4096
[[runtime.ports]]
name = "vite-dev"
target = 5173
protocol = "tcp"Deploying the Image
Deploy the image by running:
bl deployCreating or Reusing a Sandbox
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-vite-sandbox";
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "vite",
},
image: "vite-template:latest",
memory: 4096,
ports: [
{ name: "preview", target: 5173, protocol: "HTTP" },
],
});Configuring CORS for Vite Preview
Vite 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.
Creating the Blaxel Preview
Vite runs on port 5173, so we expose that port via a preview:
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "dev-server-preview" },
spec: {
responseHeaders,
public: false,
port: 5173,
},
});Generating 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);Starting the Dev Server
If not using the entrypoint script, you can start the dev server programmatically:
async function startDevServer(sandbox: SandboxInstance) {
console.log("Starting Vite dev server...");
await sandbox.process.exec({
name: "dev-server",
command: "bun run dev",
workingDir: "/app",
waitForPorts: [5173],
restartOnFailure: true,
maxRestarts: 25,
});
}Streaming Vite Logs
To monitor the Vite 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();Accessing the Vite App
Once everything is running, the app is available at:
${preview.spec?.url}?bl_preview_token=${token.value}Complete Example
Here is a full runnable example combining all the steps:
import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-vite-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: [5173],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function main() {
try {
// Create or reuse the sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "vite",
},
image: "vite-template:latest",
memory: 4096,
ports: [
{ name: "preview", target: 5173, protocol: "HTTP" },
]
});
// Create preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 5173,
},
});
// 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(`Vite 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();Summary
This setup allows you to:
Run Vite fully inside Blaxel sandboxes
Securely expose the dev server using Blaxel previews
Support HMR (Hot Module Replacement) correctly through the preview URL
Use Bun for faster dependency installation and dev server performance
This approach is ideal for preview environments, internal demos, and AI-powered coding workflows built on Blaxel.