Running software on Videan
Videan is a cloud for small software. You push code, it comes back as a live URL with TLS, sign-in enforced at the edge, and — if you asked — a Postgres database, in about a minute. This page is the whole platform: how deploys work, what your app must do, and every command, method and endpoint.
Introduction
Videan exists for the software that never justified a platform team: the internal dashboard, the client portal, the API one team needs. Traditionally that meant wiring hosting, a database, version history and access control out of three or four products. Here it is one deploy — through the CLI, the TypeScript SDK, or a plain HTTP API your agent can call.
Three ideas carry everything else:
- Every push is an immutable snapshot. The deploy history is the version history — inspect any version, diff between them, restore one. No git required.
- Production is a pointer. Deploying moves the pointer to a new snapshot; rolling back moves it to an old one. Neither rebuilds anything, and the current version keeps serving until its replacement is up.
- Auth lives at the edge, not in your app. Visitors sign in once at the platform; your code receives their identity as request headers and never renders a login page.
Each app is isolated: its own runtime (a container or a serverless function), its own database schema, its own secrets scope. Apps cannot see each other.
Quickstart
You need an account (sign up) and Node 18+. videan login opens your browser to sign in — password, Google or SSO, whatever your account uses — and stores a CLI key. In scripts, skip login and set VIDEAN_API_KEY (keys start with vdn_; create one in the dashboard).
$ npm install -g @videan/cli $ videan login ✓ logged in · via your browser $ videan apps create --name notes --postgres ✓ created notes · https://softrocket.videan.xyz $ cd my-project && videan deploy -m "first deploy" ✓ pushed 23 files · image built · postgres provisioned ● live at https://softrocket.videan.xyz
videan deploy packages the current directory (build output and node_modules are excluded automatically), pushes it, builds it, waits, and prints two URLs: the production URL and a preview URL pinned to exactly this version.
The hostname is assigned, not derived from the name. Apps get a readable generated slug (softrocket, lushlife, …). Read it from the create response rather than predicting it. Renaming an app never moves its URL.
The contract
Your app has one obligation:
Listen for HTTP on port 3000. Log to stdout. Don't daemonize. That is the entire requirement.
- The runtime is detected from the files you push — no manifest, no YAML, no Dockerfile needed (though your own Dockerfile is honored if you ship one).
- If the app has a managed database,
DATABASE_URLarrives as an environment variable. Just read it. - Everything written to stdout/stderr becomes the app's searchable logs.
- TLS, routing, health checks and identity are the platform's problem, not yours.
Runtimes
The builder inspects the pushed files and picks a runtime. Detection is automatic; there is nothing to configure.
| Runtime | Detected by | How it runs |
|---|---|---|
| Next.js | next in package.json | next build, then next start on port 3000 |
| Node | package.json | npm install, then the start script (or node server.js) |
| Python | requirements.txt / main.py | pip install, then your entry point on port 3000 |
| Go | go.mod | Compiled to a static binary, run on port 3000 |
| Dockerfile | A Dockerfile at the root | Built exactly as written — you own the contract |
Your own Dockerfile wins over detection when both are possible. Custom Dockerfiles are a container-mode feature — serverless apps use generated runtimes only.
Execution modes
Every app has a mode: container or serverless. The contract is identical either way — same code, same port, same URL, same gate, same snapshots and rollback. Only the machine underneath differs, and only the bill follows from it.
serverless
Runs as a function that scales to zero: it costs nothing while nobody is visiting and scales up with traffic. Choose it when the app answers HTTP requests and nothing else — a dashboard, a JSON API, a webhook receiver, a form handler, a Next.js site. On the Free plan, new apps default to serverless.
Know these four things before picking it:
- Cold starts. A request arriving after an idle period waits for the function to boot. Fine for internal tools; think twice when first-paint latency is the product.
- Secrets and env vars are capped at 4 KB total, baked in at deploy time. Over the cap is a 400 that names the problem.
- Requests are limited to 60 seconds.
- With a managed database, concurrency is capped at 10 — each concurrent invocation opens its own Postgres connection. Plenty for an internal tool; use
containerfor real concurrent load.
container
An always-on container — what every app ran on before serverless existed. Choose it (or just ask for it) when any of these is true:
- The app needs cron schedules or background jobs (serverless refuses both).
- It serves WebSockets or any other connection upgrade.
- It ships its own Dockerfile.
- A single request can take longer than 60 seconds.
- It keeps state in process between requests — an in-memory cache, a warm ML model, a queue it drains itself.
Switching is one PATCH /v1/apps/:id with mode (or videan apps set --mode). It applies on the next deploy, start or restart, and is refused while the app has cron schedules or jobs configured.
An always-on container is a metered machine. Even a small one runs to roughly $16/month of usage — several times the Free plan's $3 monthly allowance. On Free, keep HTTP-only apps serverless; a container there is a deliberate, metered choice.
Deployments & versions
The pipeline has three stages, each observable:
- Push. Your files become an immutable snapshot. Always pass a message (
videan deploy -m "…") — it is this platform's commit message, and versions without one are unreadable to the humans reviewing them later. - Build. The snapshot becomes a container image. Status:
building. Build failures surface their logs atGET /v1/deployments/:id/build-logs. - Deploy. The image launches, passes health checks, and is promoted. Status:
deploying→running. The version it replaces keeps serving until the new one is up, then retires — deploys are zero-downtime by construction.
A deployment that ends failed never served traffic. One that ends crashed started, then exited — read the runtime logs. Either way the fix is the same: fix the code and push again. Pushes are cheap and versioned; there is no state to clean up.
videan pull [snapshot] restores any version's files to disk — useful when the agent that wrote version 12 is gone and a human needs the source.
Previews & rollback
A preview per deploy
Every deployment gets its own URL — d-<label>.videan.xyz — pointing at exactly that version's instances. Verify the new version on its preview URL while the old one serves production. Preview URLs die when their version is retired.
Rollback is a pointer switch
Production is a pointer to a snapshot. videan rollback (or POST /v1/apps/:id/rollback) re-launches the image a previous version was built from — no rebuild, no surprises, and the current version serves until the old one is healthy. Name a specific deployment to roll to it; omit it for the previous one.
Images are kept for the app's most recent 100 builds. Rolling back to something older fails loudly rather than serving the wrong code — push the old files again instead (videan pull gets them back).
Access & identity
Every app has a visibility, enforced at the platform edge before a request ever reaches your code:
| Tier | Who gets in |
|---|---|
private | Org admins and people the app is explicitly shared with |
org default | Members of the app's organization, plus shares |
authenticated | Anyone signed in to Videan, plus shares |
public | The internet — no gate, no sign-in |
Each tier is a strict superset of the one above it, and a share admits its holder at every tier. Shares are per person, as viewer or editor — give a client view access and a contractor edit access on day one, no accounts to provision.
Organizations can also wire up SAML SSO (Okta, Entra, Shibboleth, …) in org settings: claim your email domains and members sign in through your identity provider — first-time sign-ins join the org automatically as members. Password, Google and SSO accounts are interchangeable everywhere, the gate and the CLI included.
Gated apps: do not build a login page
Behind the gate, every request arrives with the visitor's identity injected as headers:
X-User-Id: usr_… X-User-Email: dana@example.com X-User-Roles: member
If the app wants its own user records (roles, profiles), find-or-create a local user from X-User-Email and start your session from that — and never render a login form when the header is present. The visitor already signed in once, at the edge.
Public apps and webhooks
A public app gets no gate and no headers — bring your own auth or none. External services (Stripe or GitHub webhooks, say) cannot pass the gate, so an app that must receive them needs visibility: public and its own shared-secret check on the receiving route.
Managed Postgres
One flag — --postgres on create, postgres_enabled: true over the API — provisions a real PostgreSQL database for the app:
- Isolated per app: its own schema and its own database role. An app can only ever see its own tables.
- Injected, not configured:
DATABASE_URLarrives as a secret environment variable. TLS is preconfigured in the runtime images. - Stable: the credentials survive every deploy. Backups run through the platform and are restorable from the dashboard.
The SQL console
The dashboard's Database tab (and POST /v1/apps/:id/sql) runs SQL against the app's database as the app's own role — read-only by default, one statement at a time. Handy for a quick look at production data without wiring a client.
Secrets
Secrets are encrypted at rest, scoped to one app, and injected as environment variables. Keys must be UPPERCASE_WITH_UNDERSCORES.
$ videan secrets set STRIPE_KEY=sk_live_… $ videan secrets list $ videan secrets remove STRIPE_KEY
A secret change applies on the app's next deploy, start or restart — not immediately. Serverless apps additionally live under a 4 KB total cap on secrets plus env vars, enforced with a clear 400 at deploy time.
Object storage
--storage gives the app a private object-storage prefix. The app (and you) read and write through presigned URLs — ask the API for a URL, then PUT/GET the bytes directly, so large files never squeeze through the platform.
$ videan storage put ./report.pdf reports/q3.pdf $ videan storage ls reports/ $ videan storage url reports/q3.pdf # a time-limited download link
Quota is per plan (see Plans & usage); stored bytes meter as storage_mb.
Cron & background jobs
Schedules and queues don't get a separate runtime — they call your app's own HTTP routes. Same code, same secrets, same database, and testable with curl before you schedule anything.
- Cron: a schedule plus a path. At each tick the platform requests that path on your app directly (not through the public gate).
- Jobs: enqueue a payload against a path (
videan jobs enqueue); the platform delivers it, records the outcome, and retries failures.videan jobs list / get / retry / cancelmanage the queue.
Cron and jobs are container-mode only. A serverless app refuses both with a 400, and an app with schedules or jobs configured refuses to switch to serverless until they are removed.
Custom domains
Any app can serve on your own domain (Pro plan and above). Add the domain in the app's Domains tab, create the two DNS records it shows you — one CNAME to validate the certificate, one to route traffic — and the platform provisions and renews the TLS certificate automatically. The *.videan.xyz URL keeps working alongside.
App email
--email lets the app send mail through the platform: POST /v1/apps/:id/email with a recipient, subject and body. Mail is sent from app-<slug>@ the platform's domain — the sender address is fixed and cannot be spoofed by the caller, which is what keeps every app's mail deliverable. Sends meter as emails_sent; videan email quota shows where you stand.
Logs & metrics
Logs
Everything the app writes to stdout/stderr is queryable — with a time window, a search term, and follow mode:
$ videan logs -f # follow live output $ videan logs --since 2h -q error $ videan logs --since 2026-09-01 --until 2026-09-02
Build logs are separate (they explain a failed deploy): GET /v1/deployments/:id/build-logs.
Metrics
The Metrics tab (and GET /v1/apps/:id/metrics) charts requests, 4xx and 5xx counts, latency (average and p95), and — for container apps — CPU and memory, measured at the platform edge so an app doesn't have to instrument itself.
Rate limiting
Per-app request caps are enforced at the edge (--rate-limit, requests per minute per client). Over the cap is a 429 with a Retry-After header.
CLI reference
npm install -g @videan/cli. Every prompt has a flag or an environment variable — the CLI refuses to prompt when stdin is not a TTY rather than hanging, so it is safe in scripts and agents.
| Command | What it does |
|---|---|
videan login / logout | Sign in and store a CLI key. Opens your browser by default (works for password, Google and SSO accounts); --key vdn_… stores an existing key, --no-browser prompts for email and password |
videan init | Link the current directory to an app |
videan deploy -m "msg" | Push the directory, build, wait, print production + preview URLs |
videan pull [snapshot] | Restore a version's files to disk |
videan logs [-f] [--since] [-q] | Runtime logs: follow, window, search |
videan rollback [-a app] | Return production to the previous version |
videan status | The app's current deployment and URL |
videan metrics | Requests, errors and latency in the terminal |
videan secrets set/list/remove | Manage the app's secrets |
videan apps list/create/set/delete | Manage apps — set changes access, size, instances, mode, rate limit, stack |
videan orgs list/create/use/members/invite | Organizations and membership |
videan keys create/list/revoke | API keys — scopable to one app, with optional expiry |
videan storage ls/put/get/rm/url/usage | Object storage |
videan jobs enqueue/list/get/retry/cancel | Background jobs |
videan email send/quota | Send app mail, check the quota |
videan usage / usage plans | This month's metered usage; the plan table |
Environment variables for non-interactive use: VIDEAN_API_KEY, VIDEAN_ORG_ID, VIDEAN_APP_ID, VIDEAN_API_URL.
SDK reference
npm install @videan/sdk — typed, fetch-based, no dependencies. What the dashboard does, the SDK does.
import { CloudClient } from "@videan/sdk"; const cloud = new CloudClient({ apiKey }); const app = await cloud.apps.create({ name: "notes", orgId, postgres: true, mode: "serverless" }); // read app.slug / app.url from the response const snap = await cloud.apps.push(app.id, files, { message: "first deploy" }); const dep = await cloud.apps.deploy(app.id, snap.id); // poll cloud.apps.checkDeployment(dep.id) until status === "running"
Errors throw CloudError with status and code. A status of 0 means the request never got an answer (network) — retrying is reasonable, unlike a 4xx.
HTTP API
Base URL https://api.videan.xyz. Every request takes Authorization: Bearer <API key>. Any language that can speak HTTP can run the whole platform — the CLI and SDK are conveniences over exactly these endpoints.
| Endpoint | What it does |
|---|---|
POST /v1/apps | Create an app: name, org_id, optional postgres_enabled, visibility, mode, stack |
POST /v1/apps/:id/push | Push files (files as strings, binary_files as base64, message) → a snapshot |
POST /v1/apps/:id/deploy | Deploy a snapshot (snapshot_id) → a deployment |
POST /v1/deployments/:id/status | Poll a deployment (this also advances the build — call it, don't just wait). When running: url, previewUrl |
GET /v1/apps/:id/logs | Runtime logs — ?since=15m&q=error |
GET /v1/deployments/:id/build-logs | Why a build failed |
POST /v1/apps/:id/rollback | Previous version, or name a deployment |
POST /v1/apps/:id/secrets | Set a secret: { key, value } |
POST /v1/apps/:id/sql | Query the managed database: { query, read_only } |
PATCH /v1/apps/:id | Change name, visibility, mode, size, instances, rate limit, stack |
GET /v1/plans | The plan table — unauthenticated, good as a health check |
Metrics, storage, cron, jobs, domains, email, backups, usage and the audit log all live under /v1/apps/:id/* and /v1/orgs/:id/* with the same bearer auth. Most responses use snake_case; the SDK normalizes everything to camelCase.
Plans & usage
Plans bound spend, not activity: the limits are on the knobs that scale the bill (app count, instance count, task size). There is deliberately no limit on requests, builds or deploys — an app that gets popular should get a bill, not an outage.
| Free | Pro — $20/mo | Scale — $200/mo | |
|---|---|---|---|
| Included usage | $3/mo | $20/mo | $200/mo |
| Apps | 3 | 50 | 500 |
| Instances per app | 1 | 4 | 10 |
| Max task size | 0.5 vCPU / 1 GB | 4 vCPU / 8 GB | 16 vCPU / 120 GB |
| Object storage | 1 GB | 100 GB | 1 TB |
| Managed Postgres | ✓ | ✓ | ✓ |
| Custom domains | — | ✓ | ✓ |
What usage costs
Usage is metered per app and counted against the plan's included allowance first. The meters: container compute (vCPU-seconds and GB-seconds), serverless compute (GB-seconds and invocations), requests, build time, object storage, managed-Postgres days, and emails sent. Bandwidth out is measured and shown but not currently charged. The dashboard's Usage page and videan usage show the running total; GET /v1/plans returns the current rates.
The Free plan is free while it sleeps. A serverless app that nobody visits meters nothing, so the $3 allowance goes a long way. An always-on container runs to ~$16/month on its own — which is why Free-plan apps default to serverless.
Troubleshooting
- Deployment ends
failed→ read the build logs. The usual causes: a missing dependency inpackage.json, listening on a port other than 3000, a start script that exits. Fix and push again. - Status
crashed→ the app started, then exited. Read the runtime logs. An app that needs the database should fail fast whenDATABASE_URLis missing — that turns a mystery 500 into a clear failed deploy. - The URL 404s right after a deploy → you caught the retiring version during promotion; wait a few seconds. A persistent 404 → check the app's status.
- 429 from the API → back off; the
Retry-Afterheader says how long. - curl against a gated app redirects to sign-in → that is the gate working. Verify deploys on the preview URL, or in a browser with a platform session.
Deploy failures and crashes also email the organization's admins, so the person who can fix infrastructure hears about it without watching a dashboard.
For agents
Videan is built to be called, not clicked: everything above is a plain HTTP API, keys can be scoped to a single app with an expiry, and failures come back as readable text an LLM can act on. The platform never bills you tokens — bring your own agent.
The agent-facing version of this page lives at videan.xyz/llms.txt — the same facts, phrased as operating instructions. Point your agent at it, or paste it into context. Signed-in users get a ready-made setup block (key, org, instructions) on the dashboard's Apps page.