Build a Lovable clone in one day with Lizard Sandboxes
A Lovable clone needs somewhere to run the code it generates. The model writes files, installs dependencies, checks builds, and starts a preview server. Each customer project needs its own workspace. With Lizard (lizard
A Lovable clone needs somewhere to run the code it generates. The model writes files, installs dependencies, checks builds, and starts a preview server. Each customer project needs its own workspace.
With Lizard (lizard.build) Sandboxes, your backend creates that workspace through Lizard SDK, sends it files, runs commands, and returns a private preview URL. You can start without buying servers or reserving a fleet of machines.
For a developer who knows React and Node.js and starts with an app template, one day is a reasonable target for a small MVP: enter a prompt, generate a React app, open its preview, and ask for changes. Payments, team access, and a complete deployment product need more work.
What we tested on Lizard
On October 15, 2026, we ran the execution workflow in Small Firecracker Sandboxes in us-east-1 using Lizard SDK 0.1.69.
| Check | Result |
|---|---|
| Install dependencies and build the React app | Passed |
| Open the private HTTPS preview in a browser | Passed |
| Filter sample members with a React button | Passed |
| Request the preview without an access token | HTTP 401 |
| Change source files, rebuild, and reload the preview | Passed |
| Save the edited source and rebuild it in a new sandbox | Passed |
The first successful run took about 22 seconds from creating the sandbox to receiving a working preview. That included installing Node.js and npm, uploading the starter, installing dependencies, and building the app. This is one smoke test, not a startup benchmark.
The test also caught two errors in the first draft. The sandbox object exposes its ID as sandbox.sandboxId. And the interpreter sandbox we received did not contain Node.js or npm. The code below installs a fixed Node.js version before running the build.
We tested the execution and preview workflow. The one-day schedule below is a build plan; we did not time the creation of a complete SaaS product or test an external model's output quality.
Keep the first version small
Start with one React and Vite template. Let users create simple dashboards, landing pages, or internal tools with sample data.
The first release needs sign-in, a project list, a chat box, build output, and a working preview. Save source files so users can return later.
Keep the dependency list fixed. Give the model a known set of packages and let it edit a few source files. This makes failed builds easier to fix.
A useful first request is:
Build a dashboard for a small gym. Show monthly revenue,
active members, and upcoming classes. Use sample data.
After the preview works, send a follow-up request to add or change a membership filter. That gives you a concrete path to test.
Keep your backend in charge
Your backend owns the model connection, the Lizard API key, and the mapping between users and projects.
User prompt
|
Your backend: sign-in, project access, model calls
|
Lizard SDK
|
Sandbox: source files, dependencies, build, preview
|
Private preview URL returned to the project owner
Store the owner, sandbox ID, conversation, and saved file version in your database. Check ownership before every operation, including reading logs and opening a preview.
Keep platform credentials on your backend. Generated code does not need your Lizard API key or model provider key.
For the first version, ask the model to return structured output with appTsx and appCss. Validate both strings and set a response size limit. Your backend writes them to fixed paths, src/App.tsx and src/App.css. It controls package files, the lockfile, and build scripts.
Tell the model which imports the starter supports. If a build fails, return the relevant error output and allow at most two repair attempts. Then show the error to the user.
Create a sandbox and open its preview
Install the tested SDK version in your Node.js backend:
npm install @lizard-build/[email protected]
Create a key limited to the Lizard project your builder uses:
lizard keys create lovable-builder --project YOUR_PROJECT_ID --json
Set LIZARD_API_KEY and LIZARD_PROJECT_ID in the backend environment. The SDK does not read your Lizard CLI login or load an .env file by itself.
Prepare a trusted React and Vite starter with a lockfile and a working npm run build script. Our test used React 19.2.0, Vite 7.1.12, TypeScript 5.9.3, and Python 3.11.16. Keep dependencies current in your own starter and retest when you update them.
The function below accepts that starter as a map of relative file paths to file contents. Load it from files you control. Do not accept starterFiles from the model or an unauthenticated request.
The setup step targets the Linux x64 sandbox used in this test. It downloads Node.js 22.23.3 from nodejs.org and checks the archive against the release checksum. Run setup before passing the workspace to generated code.
import { Lizard } from '@lizard-build/sdk'
import { setTimeout as delay } from 'node:timers/promises'
const project = process.env.LIZARD_PROJECT_ID
if (!project) throw new Error('LIZARD_PROJECT_ID is required')
const lizard = new Lizard({ project })
export async function createPreview(
starterFiles: Record<string, string>,
appTsx: string,
appCss: string,
) {
const sandbox = await lizard.create('interpreter', {
size: 'small',
timeoutMs: 10 * 60 * 1000,
metadata: { purpose: 'lovable-article-smoke-20261015' },
})
try {
const info = await sandbox.getInfo()
if (info.runtime !== 'firecracker') {
throw new Error('This workflow requires Firecracker')
}
const setup = await sandbox.process.exec(['/bin/bash', '-lc', `
set -euo pipefail
version=v22.23.3
archive=node-$version-linux-x64.tar.xz
if [ "$(node --version 2>/dev/null || true)" != "$version" ] || ! command -v npm >/dev/null; then
cd /tmp
curl -fsSLO "https://nodejs.org/dist/$version/$archive"
curl -fsSL "https://nodejs.org/dist/$version/SHASUMS256.txt" | grep " $archive$" | sha256sum -c -
tar -xJf "$archive" -C /usr/local --strip-components=1
fi
node --version
npm --version
python --version
`], { timeoutMs: 120_000 })
if (setup.exitCode !== 0) throw new Error(setup.stderr || setup.stdout)
for (const [path, content] of Object.entries(starterFiles)) {
await sandbox.fs.write(`/workspace/app/${path}`, content)
}
await sandbox.fs.write('/workspace/app/src/App.tsx', appTsx)
await sandbox.fs.write('/workspace/app/src/App.css', appCss)
for (const command of [['npm', 'ci'], ['npm', 'run', 'build']]) {
const result = await sandbox.process.exec(command, {
workdir: '/workspace/app',
timeoutMs: 120_000,
})
if (result.exitCode !== 0) {
throw new Error(result.stderr || result.stdout)
}
}
const server = await sandbox.process.exec(
['/bin/bash', '-lc', 'nohup python -m http.server 3000 --bind 0.0.0.0 ' +
'--directory dist > /tmp/preview.log 2>&1 < /dev/null &'],
{ workdir: '/workspace/app' },
)
if (server.exitCode !== 0) throw new Error(server.stderr)
const route = await sandbox.exposePort(3000)
for (let attempt = 0; attempt < 15; attempt++) {
const response = await fetch(`https://${route.hostname}`, {
headers: { 'X-Lizard-Access-Token': route.accessToken },
signal: AbortSignal.timeout(3_000),
}).catch(() => null)
const ready = response?.ok
await response?.body?.cancel()
if (ready) {
return { sandboxId: sandbox.sandboxId, previewUrl: route.url }
}
await delay(500)
}
throw new Error('Preview did not become ready')
} catch (error) {
await sandbox.kill()
throw error
}
}
Call createPreview(starterFiles, result.appTsx, result.appCss) after validating the model response. Save the returned sandboxId against the user's project and return previewUrl only after checking access.
The URL includes an access token. Keep it out of public logs and analytics. It grants access to the sandbox's exposed ports, so treat it as a credential.
This example serves the built frontend with Python's HTTP server. Use it for a private development preview. Keep the initial app on one route or use hash routing. Production hosting should be a separate deployment.
The Lizard SDK reference covers commands, files, private ports, and sandbox lifecycle operations.
Handle follow-up changes and saved projects
Reconnect to the project sandbox through lizard.connect(sandboxId). Read the approved source files, send them with the user's next request to the model, validate the response, write the changes, and run the build again.
Our test changed the heading and added a sample member. After rebuilding and reloading the same preview URL, the browser showed the new version and its filter worked.
Save source files after each successful build. Use a private bucket to store source bundles in Managed Object Storage and keep project records in Managed Postgres. Those backend services run separately from the preview workspace.
A user returning after the sandbox expires needs a new sandbox. Restore the saved source and starter, then rebuild. We verified this by loading the edited source into a second sandbox and opening the resulting app in a browser.
Persistent Volumes are another option for retaining workspace files. Each volume attaches to one sandbox at a time. Keep an external copy of valuable files because the storage lives on one node. Sandbox storage details
Isolate generated code with Firecracker
Lizard Sandboxes support Firecracker microVMs with their own kernel. The example checks the runtime before installing dependencies or running the app.
Older volumes or snapshots can use the container runtime. If your product requires Firecracker, keep the runtime check when adding restore or volume support. Runtime details
Create a separate sandbox for each active customer project. Keep production database credentials and platform keys outside that workspace. A sandbox can make network requests, so generated code may send out any secret you put inside it.
Serve previews on a separate browser origin from your main app. For an MVP, opening the private preview in a new tab keeps the integration simple. Review iframe permissions and validate messages if you embed previews later.
Before inviting users, test with two accounts. Changing a project ID must not let one account read the other's files, logs, or preview URL. Firecracker handles the execution boundary; your application handles user access.
What does a Lovable clone cost to run?
Small costs $0.0162 per hour for 2 vCPU, 4 GB RAM, and 5 GB local disk. Lizard bills running time by the second.
At ten minutes per session:
| Sessions | Sandbox compute |
|---|---|
| 1 | $0.0027 |
| 100 | $0.27 |
| 1,000 | $2.70 |
| 10,000 | $27.00 |
These are compute estimates. Lizard Pro costs $19 per month with $19 of usage included; usage above that is pay as you go. The seven-day trial includes $5 of usage and requires a card. Prices checked October 15, 2026. Current pricing
Model tokens, backend hosting, databases, and persistent storage also affect your total. Track model usage and sandbox time per project before setting your own prices.
You avoid buying servers or reserving capacity before finding users. Running idle time still costs money. Pause or end unused sessions; retained storage can still incur charges.
The example sets a ten-minute lifetime. Commands do not extend that deadline. Add an idle-session policy and a Stop button to your backend, and call sandbox.kill() when a session ends after saving its source. Lifecycle reference
A one-day MVP plan
This schedule assumes you already have a web app starter with authentication.
| Time | Deliverable |
|---|---|
| Hour 1 | Project page, prompt input, and ownership checks |
| Hours 2β3 | Model response validation, sandbox creation, file writes, and builds |
| Hour 4 | Private preview and visible build errors |
| Hour 5 | Follow-up edits and saved project versions |
| Hour 6 | Session expiry, retry limits, and per-user quotas |
| Hour 7 | Tests with two accounts and several different prompts |
| Hour 8 | Deploy the builder and invite a small test group |
Check the full user journey before inviting testers: create an app, request a change, close the session, and reopen the saved project. A failed build should produce a useful error and release its sandbox.
Start with one template and a working preview. Lizard Sandboxes and Lizard SDK provide the execution, file, and lifecycle controls for that first release.
Originally published on the Lizard blog.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.