Docs
Host a real, always-on server, a SQLite database, and a terminal under your own account — running on shared infrastructure instead of your own hardware. Every feature, with working examples for both the CLI and the raw API.
Jump to a section
Quickstart
Deploy a real server from the command line in under a minute.
Install the CLI:
shellcurl -fsSL https://luisystemd.pages.dev/install | sh
Create an account:
shelllsd signup
Spin up a project from a template and deploy it:
shellmkdir my-api && cd my-api
lsd init --template flask-api
lsd deploy
That's it — lsd deploy prints a live public URL. Dependencies (requirements.txt/package.json) install automatically before your server starts.
Accounts
Sign up with a username and password at the top of the app, or via lsd signup. Each account gets its own API key (prefixed lsd_...), used to authenticate every request. There's no relationship between a LuisSystemd account and accounts on other Luis products — it's a separate system.
Example — signup via curl
shellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/account/signup \
-H "Content-Type: application/json" \
-d '{"username":"yourname","password":"yourpassword"}'
# -> {"username":"yourname","key":"lsd_..."}
Creating projects
A project is a persistent workspace: files, an optional database, an optional running server, all tied to one directory on the backend. Create one from the Files tab — pick a blank project or start from a template.
CLI
shelllsd init --name "my project"
Templates
Four starting points, available from the new-project menu or lsd init --template <id>:
🐍 flask-api
A Python HTTP API using Flask, with a requirements.txt already set up.
🟢 node-express
A Node.js API using Express, with package.json already set up.
⚡ worker
A Cloudflare-Workers-style worker.js — just a fetch handler, no server boilerplate.
🌐 static-html
A single self-contained HTML page for the Share (static) flow.
shelllsd init --template node-express --name "my api"
lsd templates # list all available templates
Files
Every project has a real file tree on its backend workspace. Create, open, edit, and delete files and folders from the Files tab. .db files are handled specially — tapping one opens the database browser instead of the text editor.
CLI
shell# edit files locally, then:
lsd push
Deploy (real servers)
Deploy runs your project as a genuine, always-on process — not just static files. It looks for an entry point in this order:
main.py→ run withpython3main.js→ run withnodeworker.js→ wrapped in a Workers-style runtime shim (see below)
Your app's port is injected automatically as the PORT environment variable — read it, don't hardcode a port. The result is tunneled out to a real public URL.
Auto-install
If requirements.txt or package.json exists, dependencies install automatically before your server starts — no manual pip/npm step needed. No manifest file at all (a plain stdlib-only script) skips this step entirely rather than erroring.
Auto-restart
Optional per-project setting — if enabled, a crashed server respawns automatically (2s backoff) instead of staying down.
requirements.txt fails (a typo'd package name, a version that doesn't exist), the install step prints the pip error but deploy still proceeds and starts your server with whatever did install successfully.CLI
shelllsd deploy
lsd status # check if it's live, see the URL and process stats
lsd undeploy # stop it
worker.js (Cloudflare-Workers-style)
Instead of writing HTTP server boilerplate, add a worker.js with just a fetch handler:
javascriptmodule.exports = {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/json") {
return Response.json({ ok: true });
}
return new Response("hello", { status: 200 });
}
};
Deploy detects it automatically (checked after main.py/main.js) and wraps it in a small Node shim that adapts Node's http module to that API — you get request.method, request.url, request.headers.get(), request.json()/.text(), and a Response/Response.json() constructor.
Share (static files)
Serves your project's files exactly as they sit on disk — no code runs. Good for plain HTML/CSS/JS with no backend logic. If there's no index.html, there's nothing to show.
CLI
shelllsd share
lsd unshare
Terminal
A real, persistent shell in your project's workspace, available in the advanced panel. Commands run in the same directory Deploy uses, so pip install -r requirements.txt --break-system-packages or npm install run here are visible to your deployed server too — though Deploy already does this automatically for you on every deploy.
Database (SQLite)
Any .db file in your workspace can be browsed and queried directly — tap it in the Files tab to see its tables (name, columns, row counts), tap a table to auto-run SELECT * FROM table LIMIT 100, or write your own SQL.
Example — create a table via curl
shellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/db/query \
-H "Authorization: Bearer $LSD_KEY" -H "Content-Type: application/json" \
-d '{"project_id":"$PID","db_file":"data.db","sql":"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"}'
Environment variables
Plain, non-secret configuration — feature flags, log levels, base URLs. Set them in the Compute Engine tab or via .lsdw. Injected into your process alongside PORT at deploy time.
Secrets
API keys and other sensitive values, encrypted at rest (AES via Fernet) and never shown back in full — only masked (sk_l••••••ijkl). Decrypted only server-side, only into your deployed process's environment.
Tap + Secret for a searchable preset list (OpenAI, DeepSeek, Anthropic, Stripe, GitHub, AWS, and more), or add a custom key. If a secret and an env var share the same name, the secret wins — verified directly.
os.environ["STRIPE_SECRET_KEY"] — the real value is injected outside its context entirely.CLI (value prompted securely, never in shell history)
shelllsd secrets set STRIPE_SECRET_KEY
lsd secrets list
Scripting / CI
shelllsd secrets set STRIPE_SECRET_KEY "$STRIPE_KEY_FROM_CI_SECRET_STORE"
.lsdw manifest
A plain-text project manifest, read by the CLI (and pushed to via lsd push):
text# .lsdw -- LuisSystemd project manifest, plain key=value lines.
project_id=<id> # set automatically by `lsd init` / `lsd link`
auto_restart=true|false # restart the server automatically if it crashes
env.KEY=value # a non-secret environment variable (repeatable)
.lsdw — it's meant to be committed alongside your code. Use lsd secrets set KEY instead.Compute Engine
Real, live stats for your project: CPU load, memory, disk (shared VM-wide, labeled honestly as such), plus per-process stats for your specific deployed server — its actual worker PID, RSS memory, and process uptime. Also shows deploy count, last-deployed time, and both live URLs (server + static) with one-tap copy.
CLI
shelllsd status
Deploy history & rollback
Every deploy snapshots your workspace (a tarball, keeping the last 10) before applying the new deploy. Roll back to restore an older snapshot's files — this only restores files, it doesn't redeploy by itself; tap Deploy again afterward to make the restored version live.
CLI
shelllsd history
lsd rollback <version-id>
lsd deploy --no-push # make the restored version live
Scheduled jobs
Run a shell command in your project's workspace on a repeating interval (minutes) — backups, scrapes, cleanup, anything that doesn't need to be an always-on server. A background scheduler checks for due jobs every 30 seconds.
CLI
shelllsd jobs add "python3 backup.py" --every 60
lsd jobs list
lsd jobs run <job-id>
AI assistant
Describe what you want built — the AI writes files directly into your current project. It's told which env vars and secrets already exist (names only) so it can reference them correctly instead of inventing values or asking you to paste them into chat.
CLI (lsd)
A full command-line client, talking to the same API the web app uses.
| Command | What it does |
|---|---|
lsd login / lsd signup | Authenticate, store the API key locally |
lsd init [--template ID] | Create a project and link this directory |
lsd push | Upload local files, sync .lsdw |
lsd deploy | Push, then deploy as a real server |
lsd status | Live compute status |
lsd logs [-f] | Show or follow deploy logs |
lsd history / lsd rollback <id> | List and restore snapshots |
lsd secrets set/list/delete | Manage encrypted secrets |
lsd jobs add/list/rm/run/enable/disable | Manage scheduled jobs |
lsd share / lsd unshare | Static file hosting |
lsd delete | Delete the linked project |
API reference
Every CLI command and every UI action calls this same REST API. All authenticated endpoints take an Authorization: Bearer <api_key> header.
/api/account/signupshellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/account/signup \
-H "Content-Type: application/json" -d '{"username":"...","password":"..."}'
/api/account/loginshellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/account/login \
-H "Content-Type: application/json" -d '{"username":"...","password":"..."}'
/api/projectsshellcurl https://shoes-predicted-wanted-sets.trycloudflare.com/api/projects -H "Authorization: Bearer $LSD_KEY"
/api/project/createshellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/project/create \
-H "Authorization: Bearer $LSD_KEY" -H "Content-Type: application/json" -d '{"name":"my project"}'
/api/fs/writeshellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/fs/write \
-H "Authorization: Bearer $LSD_KEY" -H "Content-Type: application/json" \
-d '{"project_id":"$PID","path":"main.py","content":"print(1)"}'
/api/deployshellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/deploy \
-H "Authorization: Bearer $LSD_KEY" -H "Content-Type: application/json" -d '{"project_id":"$PID"}'
# -> {"url":"http://bore.pub:...","runtime":"python3","entry":"main.py","install_ran":true}
/api/compute/statusshellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/compute/status \
-H "Authorization: Bearer $LSD_KEY" -H "Content-Type: application/json" -d '{"project_id":"$PID"}'
/api/project/secrets/setshellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/project/secrets/set \
-H "Authorization: Bearer $LSD_KEY" -H "Content-Type: application/json" \
-d '{"project_id":"$PID","secrets":{"STRIPE_SECRET_KEY":"sk_..."}}'
/api/jobs/createshellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/jobs/create \
-H "Authorization: Bearer $LSD_KEY" -H "Content-Type: application/json" \
-d '{"project_id":"$PID","command":"python3 backup.py","interval_minutes":60}'
Troubleshooting
Things that came up while stress-testing every feature end-to-end — real behavior, not hypotheticals.
Template id typos fail with a generic error
lsd init --template workers (plural) fails with error: Unknown template — the real id is worker (singular). Run lsd templates first if unsure.
lsd delete needs an interactive terminal
It asks for a typed yes confirmation, so it can't be scripted or piped non-interactively. For automation, call the API endpoint directly instead:
shellcurl -X POST https://shoes-predicted-wanted-sets.trycloudflare.com/api/project/delete \
-H "Authorization: Bearer $LSD_KEY" -H "Content-Type: application/json" \
-d '{"id":"$PID"}'
id, not project_id — inconsistent with most other project endpoints. Sending project_id silently resolves to an empty id and fails with {"error": "Only the owner can delete this project"} even when you are the owner.lsd logs -f can print nothing while lsd logs works fine
If -f looks stuck, fall back to polling with plain lsd logs:
shellwatch -n 2 lsd logs
Secrets vs. env vars: secrets always win
If .lsdw sets env.SOME_KEY=... and a secret named SOME_KEY also exists, the deployed process only ever sees the secret's value.