--- title: "Lizard documentation" description: "Deploy apps, workers and AI agents to Lizard from Claude Code, Codex, Cursor or the CLI, then add Managed Postgres, Redis, Object Storage or Sandboxes." url: https://lizard.build/docs/ updated: 2026-10-12 --- # Lizard documentation ## What is Lizard? Lizard is a cloud platform for deploying apps, background workers and AI agents. You deploy from Claude Code, Codex, Cursor or a terminal with Lizard CLI: `lizard up` uploads the current folder, and `lizard add -r owner/repo` deploys a GitHub repository. Lizard builds the image on its servers and gives each web service an HTTPS address on `onlizard.com`. You do not need Docker on your machine. Add Managed Postgres, Managed Redis or S3-compatible Managed Object Storage with the same CLI. Connect them to services through references such as `${{postgres.DATABASE_URL}}`. Sandboxes let AI agents run code in Firecracker microVMs, each with its own kernel. Create them from your code with Lizard SDK for TypeScript or Python. Lizard is not LizardFS, the distributed file system, and not `lizard`, the Python tool that measures code complexity. ## Choose a task | I want to… | Start here | |---|---| | Deploy my first app | [App quickstart](https://lizard.build/docs/getting-started.md) | | Deploy Next.js, React, Vue, or a Python app | [Framework guides](https://lizard.build/docs/framework-guides.md) | | Deploy from Claude Code or another coding agent | [Coding agent guide](https://lizard.build/docs/guides/deploy-from-coding-agent.md) | | Deploy an app that has a Dockerfile | [Deploy with a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md) | | Host a remote MCP server | [Remote MCP guide](https://lizard.build/docs/guides/deploy-mcp-server.md) | | Keep a Telegram or Discord bot running | [Telegram guide](https://lizard.build/docs/guides/telegram-bot.md) or [Discord guide](https://lizard.build/docs/guides/discord-bot.md) | | Connect a database | [Managed Postgres](https://lizard.build/docs/addons/postgres.md) | | Run generated code in a separate environment | [Sandboxes quickstart](https://lizard.build/docs/sandboxes/quickstart.md) | | Run Claude Code inside a sandbox | [Claude Code in a sandbox](https://lizard.build/docs/sandboxes/guides/claude-code.md) | | Keep files after a sandbox ends | [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md) | | Edit a page visually with Claude Code or ChatGPT | [Lizard Studio](https://lizard.build/docs/studio/getting-started.md) | ## Deploy an app Install Lizard CLI, sign in and start the 7-day Pro trial, then deploy a repository you can access: ```bash curl -fsSL https://lizard.build/install.sh | bash lizard login lizard billing start # opens Stripe Checkout; add a card, nothing is charged during the trial lizard add -r your-org/your-app ``` On Windows or with npm, see [other ways to install](https://lizard.build/docs/getting-started.md#1-install-the-cli). Lizard builds the image on its servers, so this path does not need local Docker. [lizardpack](https://lizard.build/docs/concepts/build-pipeline.md) detects supported projects; projects with special build needs can use explicit commands or a Dockerfile. Check the build logs and verify the application URL after deployment. ## Understand your resources A **workspace** groups members and projects. A **project** groups services, databases, and Sandboxes. A **service** runs an application or worker. Managed Postgres, Managed Redis, and Managed Object Storage provide data services that applications connect to through environment references. Apps and Sandboxes have different runtime and storage rules. Read [limits](https://lizard.build/docs/platform/limits.md) and [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md) before depending on a resource's size, lifetime, or data retention. ## Find the right level of detail - [Guides](https://lizard.build/docs/guides.md) cover a task from setup to a check of the result. - [Framework guides](https://lizard.build/docs/framework-guides.md) give build commands, adapters, ports, and checks for each stack. - [Deploy](https://lizard.build/docs/deploy.md) covers GitHub and local sources, workers, scaling, and regions. - [Core concepts](https://lizard.build/docs/concepts.md) explain projects, builds, and deployments. - [Observability](https://lizard.build/docs/observability.md) covers logs, metrics, cost, and deploy history. - [Lizard CLI reference](https://lizard.build/docs/cli.md) lists commands, flags, and exit codes. - [Troubleshooting](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md) helps diagnose a failed start. - [Known issues](https://lizard.build/docs/platform/known-issues.md) records limits and recovery cases to check before a release. --- title: "App quickstart: deploy your first app" description: "Deploy your first app to Lizard in a few minutes: install Lizard CLI, sign in, start the 7-day Pro trial, then deploy a GitHub repo or a local folder." url: https://lizard.build/docs/getting-started/ updated: 2026-10-12 --- # App quickstart This quickstart deploys an app to Lizard in a few minutes. Install Lizard CLI, sign in, start the 7-day Pro trial, then run `lizard add -r owner/repo` for a GitHub repository or `lizard up` for a local folder. Lizard builds the app on its servers, with no local Docker, and returns an HTTPS URL on `onlizard.com`. Lizard creates your account the first time you run `lizard login`. ## 1. Install the CLI On macOS or Linux, run the install script: ```bash curl -fsSL https://lizard.build/install.sh | bash ``` It puts the `lizard` binary in `~/.lizard/bin`, adds that folder to your `PATH` in `~/.zshrc` or `~/.bashrc`, and links `lizard` into a folder that is already on your `PATH`, so it works in this terminal right away. The binary updates itself from then on, except in CI. If the script says `lizard` may not be found, open a new terminal. Check the version: ```bash lizard --version ``` On Windows, run this in PowerShell: ```powershell irm https://lizard.build/api/cli/install.ps1 | iex ``` **Or install from npm** (needs Node.js 18+): ```bash npm install -g @lizard-build/cli ``` > **Don't use `npx`.** Always use the globally installed `lizard` binary. Running `npx @lizard-build/cli` downloads a temporary copy whose version may differ from the platform. To upgrade an npm install later, run `npm install -g @lizard-build/cli@latest`. > **Permission errors (EACCES)?** Don't use `sudo`. Use the install script above, which needs no admin rights, or point npm at a user-owned prefix: > ```bash > mkdir -p ~/.npm-global > npm config set prefix ~/.npm-global > echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc && source ~/.zshrc > npm install -g @lizard-build/cli > ``` ## 2. Log in ```bash lizard login ``` This opens your browser to authenticate, then returns to your terminal after you confirm. In CI or other non-interactive environments, authenticate with a token instead: ```bash export LIZARD_TOKEN=liz_xxx # or lizard login --token liz_xxx ``` API keys start with `liz_`. Create one with `lizard keys create `. `LIZARD_API_KEY` works in place of `LIZARD_TOKEN`. Check who you are at any time: ```bash lizard whoami ``` ## 3. Start the Pro trial A new account has no plan, so creating anything returns HTTP 402 with a Checkout link. Start the 7-day Pro trial first: ```bash lizard billing start ``` This opens Stripe Checkout, where you add a card. The trial includes $5 of usage, and nothing is charged until it ends. See [Billing](https://lizard.build/docs/platform/billing.md) and [`lizard billing`](https://lizard.build/docs/cli/billing.md). ## 4. Deploy Pick the path that matches where your code lives. ### Option A — Deploy a GitHub repo (recommended) If your code is on GitHub, create a service straight from the repo. Lizard clones it, auto-detects the stack, builds it, and returns a live URL: ```bash lizard add -r your-org/your-app ``` Pushes to the tracked branch will auto-redeploy from then on. For private repos, connect the GitHub App first with [`lizard git`](https://lizard.build/docs/cli/git.md): ```bash lizard git connect ``` ### Option B — Deploy local code From inside your project directory, upload and deploy the current folder (respects `.gitignore`): ```bash lizard up ``` If the directory isn't linked to a project yet, `up` will create or select one interactively. In CI, link explicitly first with `lizard init --name my-project`. Either way, when the build finishes you'll get a generated URL like `https://your-app-x1y2z.onlizard.com`. Lizard detects most stacks on its own. For the exact build command, start command and port of your framework, see the guide for [Next.js](https://lizard.build/docs/framework-guides/nextjs.md), [React](https://lizard.build/docs/framework-guides/react.md), [Vue](https://lizard.build/docs/framework-guides/vue.md), [Astro](https://lizard.build/docs/framework-guides/astro.md), [SvelteKit](https://lizard.build/docs/framework-guides/sveltekit.md), [Node.js](https://lizard.build/docs/framework-guides/nodejs.md), [Django](https://lizard.build/docs/framework-guides/django.md), [FastAPI](https://lizard.build/docs/framework-guides/fastapi.md), [Rails](https://lizard.build/docs/framework-guides/rails.md) or [Go](https://lizard.build/docs/framework-guides/go.md). An app with its own Dockerfile follows [Deploy with a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md). ## 5. Watch it build and run ```bash lizard ps # services in the project, with status + URL lizard logs # last runtime log lines lizard logs --build # the most recent build's logs lizard open # open the project in the dashboard ``` [`lizard open`](https://lizard.build/docs/cli/open.md) shows the same project in the [dashboard](https://lizard.build/docs/dashboard.md). For metrics, cost and deploy history, see [Observability](https://lizard.build/docs/observability.md). ## 6. Add a database (optional) Provision Managed Postgres and reference it from your service: ```bash lizard add postgres lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service your-app lizard redeploy --service your-app ``` Lizard resolves `${{postgres.DATABASE_PRIVATE_URL}}` at deploy time so the service connects over the project's private network. It matches the addon's current name, so update the reference if you rename the addon. Credentials do not rotate on their own; a service picks up new values on its next deploy. See [Managed Addons](https://lizard.build/docs/addons.md). ## Next steps - **[Core Concepts](https://lizard.build/docs/concepts/architecture.md)**: understand projects, services, and the build pipeline. - **[Deploying from GitHub](https://lizard.build/docs/deploy/github.md)**: branches, monorepos, and auto-redeploys. - **[Deploy](https://lizard.build/docs/deploy.md)**: every deploy path, plus workers, scaling, and regions. - **[Variables & Secrets](https://lizard.build/docs/variables.md)**: scoping and precedence. - **[Networking](https://lizard.build/docs/networking.md)**: attach your own hostname with automatic TLS. - **[Deploying an app your AI IDE built](https://lizard.build/blog/google-antigravity-how-to-deploy-your-app-to-production)**: the same steps handed to an agent instead, for an app written in Google Antigravity. ## Hosting costs Pro costs $19 a month with $19 in monthly credits. Usage above that is pay as you go at the published rates. Pro starts with a 7-day trial and $5 in trial credits; you add a card to start it. See [Billing](https://lizard.build/docs/platform/billing.md) and [pricing](https://lizard.build/pricing), and check your [limits](https://lizard.build/docs/platform/limits.md) before deploying. --- title: "Networking: private networking, domains and ports" description: "Private networking between services at .lizard.internal, generated onlizard.com domains with TLS, custom domains, DNS checks and container ports." url: https://lizard.build/docs/networking/ updated: 2026-10-12 --- # Networking On Lizard, services in the same project reach each other over a private network at `.lizard.internal`, on any port, with no public domain. Every web service also gets a generated `*.onlizard.com` domain with automatic TLS when it deploys. You can also attach your own hostname. Managed Postgres and Managed Redis join the same private network. Jump to [private networking](#private-networking), [custom domains](#attach-a-custom-domain) or [ports](#change-the-port-your-service-listens-on). ## Generated domains Lizard creates a default domain for each service in the form `-.onlizard.com`. Older services keep `..onlizard.com`. Show it: ```bash lizard domain # show the service's current domain lizard domain generate # claim a *.onlizard.com subdomain if the service has none ``` `lizard domain generate` returns the existing generated domain when there is one, and fails while the service has a custom domain. Files served from [Managed Object Storage](https://lizard.build/docs/addons/storage.md) use a gateway host instead: `s3-eu-west-1.onlizard.com` or `s3-us-east-1.onlizard.com`. ## Attach a custom domain Pass the hostname as a positional argument. There is no `add` subcommand: ```bash lizard domain app.example.com --service web ``` Lizard returns the DNS records to create (a `CNAME` for the hostname and a `TXT` record for verification). Add them at your DNS provider. ## Verify Once DNS has propagated, activate the domain: ```bash lizard domain verify app.example.com --service web ``` Lizard checks the `TXT` record, activates the domain and provisions TLS. ## Remove a domain ```bash lizard domain delete app.example.com --service web lizard domain rm app.example.com --service web --yes # alias, skip confirmation ``` ## Change the port your service listens on Domains route to the service's container port. If your service listens on another port, change the container port and redeploy: ```bash lizard port 8080 --service web lizard redeploy --service web ``` ## Worker services A [worker-mode service](https://lizard.build/docs/deploy/workers.md) (`containerPort=0`) may still show a generated domain, but it serves nothing: there's no listener and no load-balancer route. Don't attach a custom domain to a worker. ## Private networking Services in the same project reach each other at `.lizard.internal`, on any port the service listens on, without a public domain. Reference the hostname as `${{api.LIZARD_PRIVATE_DOMAIN}}`. Managed Postgres and Managed Redis expose `DATABASE_PRIVATE_URL` and `REDIS_PRIVATE_URL` on the same network; see the [Postgres connection string](https://lizard.build/docs/addons/postgres.md#postgres-connection-string). These names resolve only inside the project, and services in other projects cannot reach these addresses. Traffic stays inside Lizard's network, so plain `http://` is fine between services. ```bash lizard secrets set API_URL='http://${{api.LIZARD_PRIVATE_DOMAIN}}:8080' --service web ``` ## Manage in the dashboard Open a service in the dashboard (`lizard open`) and go to **Settings → Networking**. It shows each domain's verification and DNS status and lets you add or remove hostnames. ## See also - [`lizard domain`](https://lizard.build/docs/cli/domain.md): the full command reference. - [Regions](https://lizard.build/docs/deploy/regions.md): co-locating services and addons for latency. - [From prototype to a SaaS people pay for](https://lizard.build/blog/google-antigravity-how-to-deploy-your-app-to-production#verify-a-real-production-flow): checks for a generated app before accepting customers. --- title: "App dashboard" description: "What the Lizard dashboard adds to the CLI: service status, deploy timeline, streaming logs, metrics, data browsers, team, and billing." url: https://lizard.build/docs/dashboard/ updated: 2026-10-10 --- # App dashboard The Lizard dashboard is the visual companion to Lizard CLI. Everything you do with `lizard …` is reflected here, plus things that are easier with a UI — streaming logs, charts, the data browsers, Sandboxes, and team/billing management. If you would rather not open a terminal at all, let the agent run the commands and watch the result here — the route in [deploying an Antigravity app to production](https://lizard.build/blog/google-antigravity-how-to-deploy-your-app-to-production#deploy-from-the-antigravity-chat), where one prompt drives the deploy. Open it from the CLI: ```bash lizard open # open the current project lizard docs # open this documentation ``` ## What's in the dashboard ### Services The project's home view lists every service with its status badge, generated domain, and a quick path to logs, metrics, secrets, and settings. Create new services (from a GitHub repo or empty) right from here. ### Deployments A timeline of every deploy, with per-deploy build logs and a detail drawer (commit, status, replicas). This is the visual equivalent of `lizard events` + `lizard logs --build`. ### Logs Live runtime and build log streaming with search and level filters — the UI counterpart to [`lizard logs`](https://lizard.build/docs/observability/logs.md). ### Metrics & Usage CPU, memory, network, and disk charts per service, plus a **Usage** view that breaks down consumption and cost across the project. Mirrors [`lizard metrics --cost`](https://lizard.build/docs/observability/metrics.md). ### Variables & Secrets Manage service- and project-scoped secrets, with masked values and reference (`${{…}}`) support. See [Variables & Secrets](https://lizard.build/docs/variables.md). ### Domains Add custom domains, view DNS records, and track verification and TLS status. See [Networking](https://lizard.build/docs/networking.md). ### Data browsers Each managed addon ships a data browser: - **Managed Postgres** — a SQL/table editor to query and edit data. - **Managed Redis** — a key/value browser. - **Managed Object Storage** — a bucket and object browser with upload/download and bucket access settings. ### Sandboxes The **Sandboxes** page has tabs for sandboxes, Persistent Volumes, templates and agents. Create a sandbox with **New Sandbox**; the count next to it shows how many run against your limit. See [Sandboxes quickstart](https://lizard.build/docs/sandboxes/quickstart.md). ### GitHub integration Connect the GitHub App, pick repositories, and manage which repos Lizard can build. See [Deploy from GitHub](https://lizard.build/docs/deploy/github.md). ### Settings, team & billing Project and workspace settings, member invites and roles, and resource usage. The **Billing** page in account settings shows your plan, trial, credits used this month, invoices and card. See [Billing](https://lizard.build/docs/platform/billing.md). ## CLI ↔ Dashboard | Task | CLI | Dashboard view | |------|-----|----------------| | Deploy history | `lizard events` | Deployments | | Stream logs | `lizard logs` | Logs | | Resource charts | `lizard metrics` | Metrics / Usage | | Manage secrets | `lizard secrets` | Variables | | Custom domains | `lizard domain` | Domains | | Query Managed Postgres, Managed Redis or Managed Object Storage | `lizard run --service -- ` | Data browsers | | Invite teammates | — | Team / Settings | Use whichever fits the moment — they operate on the same projects and services. --- title: "Coding agents" description: "Let Claude Code, Codex or Cursor deploy on Lizard: install Lizard Skill, read command schemas with --json, or connect an MCP client to Lizard MCP." url: https://lizard.build/docs/agents/ updated: 2026-10-12 --- # Coding agents Claude Code, Codex, Cursor and other AI coding agents deploy and manage apps on Lizard through Lizard CLI. Install Lizard Skill once with `npx skills add lizard-build/skill -g`; the agent then loads the guide built into the CLI with `lizard skills get core --json` and reads any command's flags with `--help --json`. Clients that speak MCP, such as ChatGPT and Claude, can use [Lizard MCP](#lizard-mcp) instead. For a full deploy run from an agent, see [Deploy from Claude Code, Codex or Cursor](https://lizard.build/docs/guides/deploy-from-coding-agent.md). ## The embedded skill The CLI includes a usage guide for the installed version. An agent reads it with: ```bash lizard skills get core --json ``` This returns `{ name, frontmatter, content, … }`: `content` is the complete guide (build pipeline, env precedence, addons, discovery, exit codes). Related subcommands: ```bash lizard skills list # available embedded skills lizard skills get core # the core guide lizard skills path # where skills are stored ``` Because the guide is bundled with the binary, `lizard upgrade` updates the agent's instructions too. ## Self-describing commands Agents discover exact flag shapes at runtime instead of relying on memorized syntax: ```bash lizard --help --json # full command tree + exit codes lizard --help --json # a specific command's schema ``` See [JSON & Automation](https://lizard.build/docs/cli/json.md). ## Bootstrapping in an agent A typical agent flow: 1. **Load the guide:** `lizard skills get core --json` → read `content`. 2. **Check auth optimistically:** run the user's task; on exit code `2`, run `lizard login`, hand the printed URL to the user, then retry. 3. **Start the trial if asked:** a new account has no plan, so its first create fails with HTTP 402 (`code: "PAYMENT_REQUIRED"`, exit code `1`). Run `lizard billing start --json`, hand the Checkout `url` to the user, then retry once they have added a card. 4. **Resolve context before mutating:** `lizard status` (cwd link) and `lizard ps --json` (services). 5. **Act** using the guide: `add`, `up`, `secrets`, `domain`, etc., always with `--json`. If the `lizard` binary isn't present, install it first. On macOS or Linux, use the install script: ```bash curl -fsSL https://lizard.build/install.sh | bash ``` It needs no Node.js or `sudo`, and it links `lizard` into a folder that is already on the `PATH`, so the agent can call it in the same session. If the script says `lizard` may not be found, call `~/.lizard/bin/lizard` instead. On Windows, run this in PowerShell: ```powershell irm https://lizard.build/api/cli/install.ps1 | iex ``` Or install from npm on any system: ```bash npm install -g @lizard-build/cli ``` ## Conventions agents should follow - **Always pass `--json`** on non-interactive calls. - **Confirm destructive actions** (delete service, drop addon, overwrite a project-wide secret, prod restart) with the user: the CLI's own prompts only fire on a TTY. - **Scope secrets to the consuming service** by default; reserve `--global` for provably-public values. See [Variables & Secrets](https://lizard.build/docs/variables.md#scoping). - **Don't write Dockerfiles unsolicited**: lizardpack auto-detects most stacks. Try a deploy first. See [Build Pipeline](https://lizard.build/docs/concepts/build-pipeline.md). - **Don't use `lizard up` to switch a git-backed service to upload**: use `service set` + `redeploy`. ## Editor integrations Lizard Skill gives editor agents and assistants the instructions to install and use Lizard CLI. Install it once for all your agents: ```bash npx skills add lizard-build/skill -g ``` It works with Claude Code, Cursor, Codex, Copilot, Gemini CLI and other agents that read the Agent Skills format. For clients that speak MCP instead of running shell commands, use [Lizard MCP](#lizard-mcp). An AI IDE can write and test an app; Lizard hosts the result. See [deploying an app built in Google Antigravity](https://lizard.build/blog/google-antigravity-how-to-deploy-your-app-to-production#deploy-from-the-antigravity-chat) for a deploy started from the editor's chat. To let Claude Code or ChatGPT edit the page you have open in Chrome, use [Lizard Studio](https://lizard.build/docs/studio/getting-started.md). ## Lizard MCP Lizard MCP lets ChatGPT, Claude and other MCP clients manage your Lizard projects: create services and addons, redeploy, read logs and metrics, set secrets, scale replicas and attach domains. It is a hosted server that speaks Streamable HTTP at: ```text https://employ-woman-g1q2.us-east-1.onlizard.com/mcp ``` A client that supports OAuth signs you in on first use. Other clients send a `liz_` API key in the `Authorization: Bearer` header; create one with `lizard keys create `. Add it to Claude Code: ```bash claude mcp add --transport http lizard https://employ-woman-g1q2.us-east-1.onlizard.com/mcp ``` For Cursor and other clients configured by JSON: ```json { "mcpServers": { "lizard": { "url": "https://employ-woman-g1q2.us-east-1.onlizard.com/mcp", "headers": { "Authorization": "Bearer liz_your_key" } } } } ``` Starting the trial, paying and cancelling Pro happen on the Billing page. When the account has to pay before it can create something, the tool error carries `PAYMENT_REQUIRED` and the link to open. ## Hosting your own MCP server An agent using Lizard CLI and an application exposing MCP are separate workflows. The CLI commands above do not provide an MCP transport. To deploy your own server with Streamable HTTP, follow the [remote MCP guide](https://lizard.build/docs/guides/deploy-mcp-server.md). ## See also - [`lizard skills`](https://lizard.build/docs/cli/skills.md): the full command reference. - [JSON & Automation](https://lizard.build/docs/cli/json.md): `--json` output and schema discovery. - [Variables & Secrets](https://lizard.build/docs/variables.md#scoping): secret-scoping conventions agents should follow. - [Deploy your app from Claude Code](https://lizard.build/blog/deploy-from-claude-code#a-prompt-that-defines-the-deployment-task): installation steps and a prompt for the deploy. --- title: "Install Lizard Studio for Claude Code and ChatGPT" description: "Install Lizard Studio in Chrome, connect Claude Code or ChatGPT to a local project, and make your first website edit. Follow the setup guide." url: https://lizard.build/docs/studio/getting-started/ updated: 2026-09-14 --- # Install Lizard Studio Lizard Studio is a free Chrome extension that connects Claude Code or ChatGPT to the page you are building. Open a chat beside the page, select an element and ask the agent to edit the files in your local project. The extension includes page inspection, annotations, rulers, a color picker and browser debugging tools. This guide takes you from installation to one source edit that survives a page refresh. For an example you can download, see [the Claude Code visual editing guide](https://lizard.build/blog/claude-code-visual-editor). ## What you need - Google Chrome on macOS, Windows or Linux. - Node.js and npm. The local host declares Node.js 18 or later; your chosen agent can have a higher requirement. Use a Node.js version that both support. - The local CLI for Claude Code or ChatGPT, signed in with your own account. - A local project folder. To see source edits in the browser, run that project's development server and open its URL. The extension and host are free and MIT licensed. Model use follows your provider's account requirements, limits and charges. You do not need a Lizard account to use Lizard Studio. ## 1. Add the Chrome extension Open [Lizard Studio in the Chrome Web Store](https://chromewebstore.google.com/detail/kgbaeoalmkabpoglpjcdmppdmcipfdeh) and choose **Add to Chrome**. Pin the extension if you want to reach it from the toolbar. The toolbar provides the design tools; the Chrome side panel holds your chats. Installation from the store does not require Developer mode. ## 2. Install your agent Choose the agent you want to use. You can add the other one later. For Claude Code, follow the [official setup guide](https://code.claude.com/docs/en/setup). Check the installation in a terminal: ```bash claude --version ``` For ChatGPT in Lizard Studio, install the Codex CLI: ```bash npm install -g @openai/codex codex --version ``` Lizard Studio calls this agent **ChatGPT** in its interface. The package and terminal command use the name **Codex**. This is a local coding agent connected to your project; it does not embed the chatgpt.com website. Open your chosen agent and complete its sign-in flow before continuing. See [the Lizard Studio README](https://github.com/lizard-build/lizard-studio#setup) for the supported connection paths. ## 3. Install the local host Run this command once in a terminal: ```bash npx @lizard-build/lizard-studio-host install ``` The host connects the Chrome extension to the agent on your computer. The installer copies the host into `~/.lizard-studio/host` and registers it with the browser. Keep Node.js installed: the browser uses it to start the host. Reopen the extension's side panel after installation. If you updated an existing host, reopen the panel before starting a new chat. ## 4. Connect the page to its project Start your project's development server using its usual command. Open the URL that it prints in Chrome, then open Lizard Studio on that tab. Start a chat, choose **Claude Code** or **ChatGPT**, and choose the folder that contains this project's source code. Each chat keeps its own project folder and permissions. A tab showing the right page does not by itself tell the agent which local repository should change. Before asking for an edit, check the connection with a read-only request: ```text Read this page's title and inspect its main heading. Tell me which local project folder you are using. Do not change any files yet. ``` If the agent names the wrong folder, correct the selection before continuing. ## 5. Select an element and make one edit Choose **Selector** in the page toolbar and click the element you want to change. The selection attaches context to the chat. For a visual issue that spans several elements, use **Annotate** to mark the area and add the screenshot to the chat. Try a request with a measurable result: ```text Find the source for the selected card's action row. Set the gap between its buttons to 24px in the project files. Keep the button labels and colors unchanged. Show the diff, then inspect the rendered row to check its computed gap. ``` Read the proposed file changes and use the permission controls to approve the work you want. Your permission mode determines which actions need a separate prompt. When your development server reloads the page, inspect the result. If it does not reload automatically, refresh Chrome. ## 6. Check that the edit persists A temporary DOM edit can look correct until you refresh. A source edit changes the files that your development server serves. Check the file diff in your editor or run `git diff` from the project folder. Refresh the page and inspect the same element again. For the example above, its computed gap should still be `24px`. Review both desktop and narrow layouts before keeping the change. Editing a local project does not update a deployed website. Deploy separately when you want the change to go live. ## If the connection fails | What you see | What to check | |---|---| | The side panel cannot reach the host | Run the host installer, confirm Node.js is available, then reopen the panel. | | The agent cannot start | Check `claude --version` or `codex --version` in a terminal and complete that agent's login. | | The agent sees the page but cannot find its code | Choose the correct local folder and confirm the tab uses that project's development server. | | An edit disappears on refresh | Check the diff. Ask for a source change if the agent only changed the live DOM. | | The toolbar does not appear on a Chrome settings page | Chrome blocks content scripts on `chrome://` pages, the New Tab page and the Chrome Web Store. Open a normal website. | On macOS, the host location in `~/.lizard-studio/host` also avoids the restrictions Chrome can encounter when starting a host under Desktop, Documents or Downloads. See the [README's host notes](https://github.com/lizard-build/lizard-studio#2-the-local-host-one-time) for more detail. ## Where your data goes The extension and host run on your computer. The selected model provider receives the requests you send through its agent. Lizard Studio does not use a Lizard chat server or collect telemetry. Local installation does not mean that a cloud model processes your prompts locally. Read the [Lizard Studio privacy policy](https://github.com/lizard-build/lizard-studio/blob/main/PRIVACY.md) before using it with a project that has data restrictions. ## Related guides - [Select an element and fix its CSS](https://lizard.build/blog/claude-code-visual-editor) - [Use ChatGPT to edit a local website](https://lizard.build/studio/chatgpt) - [Compare Lizard Studio and Claude in Chrome](https://lizard.build/studio/compare/claude-in-chrome) - [Choose a Claude Code GUI](https://lizard.build/blog/claude-code-gui) Setup checked against the Lizard Studio source on September 14, 2026. --- title: "Framework guides" description: "Deploy Next.js, Express, FastAPI, Django, Streamlit, Go, Rails, Astro, Nuxt, SvelteKit and static site generators on Lizard: build commands, ports and checks." url: https://lizard.build/docs/framework-guides/ updated: 2026-10-12 --- # Framework guides Deploy a framework app on Lizard from source. Choose your framework and rendering mode below, prepare its production build, then deploy with Lizard CLI or connect a GitHub repository. Server apps run a process; static sites serve the generated files through nginx. ## Choose your framework These settings describe the standard project layouts in the linked guides. Commands assume npm for JavaScript projects. Custom output paths, adapters, and build overrides can change the result. To bring your own image instead, see [Deploy a Docker container from a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md). | Framework | Build | Runtime or output | Service port | |---|---|---|---| | [Next.js](https://lizard.build/docs/framework-guides/nextjs.md) | `npm run build` | `next start` through the start script | `3000` | | [Next.js static export](https://lizard.build/docs/framework-guides/nextjs/static-export.md) | `npm run build` | `out/` through a Dockerfile | `80` | | [React with Vite](https://lizard.build/docs/framework-guides/react.md) | `npm run build` | `dist/` | `80` | | [Vue with Vite](https://lizard.build/docs/framework-guides/vue.md) | `npm run build` | `dist/` | `80` | | [Astro](https://lizard.build/docs/framework-guides/astro.md) | `npm run build` | `dist/`, or the standalone Node adapter | `80` static; `3000` server | | [Nuxt](https://lizard.build/docs/framework-guides/nuxt.md) | `npm run build` | `node .output/server/index.mjs` | `3000` | | [SvelteKit](https://lizard.build/docs/framework-guides/sveltekit.md) | `npm run build` | `node build` with adapter-node | `3000` | | [Node.js (Express)](https://lizard.build/docs/framework-guides/nodejs.md) | `build` script, if any | `start` script | `3000` | | [FastAPI](https://lizard.build/docs/framework-guides/fastapi.md) | Install Python dependencies | `uvicorn main:app --host 0.0.0.0 --port 8000` | `8000` | | [Django](https://lizard.build/docs/framework-guides/django.md) | Install Python dependencies | Gunicorn with your WSGI module | `8000` | | [Streamlit](https://lizard.build/docs/framework-guides/streamlit.md) | Install Python dependencies | `streamlit run app.py` from a Procfile | `8501` | | [Go](https://lizard.build/docs/framework-guides/go.md) | `go build` to a static binary | `/app/server` | `8080` | | [Ruby on Rails](https://lizard.build/docs/framework-guides/rails.md) | The Dockerfile Rails generates | Thruster and Puma, or Puma alone | `8080` on Rails 8; `3000` on Rails 7.1 and 7.2 | | [Docusaurus](https://lizard.build/docs/framework-guides/docusaurus.md) | `npm run build` | `build/` | `80` | | [VitePress](https://lizard.build/docs/framework-guides/vitepress.md) | `npm run docs:build` | `docs/.vitepress/dist/` in this guide | `80` | | [Hugo](https://lizard.build/docs/framework-guides/hugo.md) | `hugo --minify` | `public/` | `80` | ## Prepare the project Run commands from the application directory. Commit source files, configuration, and the package lockfile. Exclude `.env`, local dependencies, and local build output from uploads. Node projects can select the Node major version in `.nvmrc`; the current default is `22`. This selects a major version, not an exact patch release. [Runtime versions](https://lizard.build/docs/deploy/runtime-versions.md) lists the version files for Node.js, Python, Go and Ruby. Install Lizard CLI and sign in once: ```bash curl -fsSL https://lizard.build/install.sh | bash lizard login ``` On Windows, run `irm https://lizard.build/api/cli/install.ps1 | iex` in PowerShell. A new account starts on no plan: until you start the 7-day Pro trial (card required), `lizard add` and `lizard up` return a Checkout link. `lizard billing start` opens it. Each guide uses a new project and a service named `web` or `api`. Create that service with `lizard add --service web` (or `api`) before the first `lizard up --service` call. `up --service` selects an existing service; it does not create a missing named service. For an existing project, check `lizard status --json` and `lizard ps --json` before deploying. The port must match the process inside the container, which must listen on `0.0.0.0`. Lizard CLI 0.3.95 and later exclude macOS archive metadata during uploads. No `COPYFILE_DISABLE` setting is needed. Update older CLI versions before following these guides. ## Choose GitHub or local source The guide commands use `lizard up` to upload the current folder. That command changes an existing service to upload source. To keep git push to deploy, [connect GitHub](https://lizard.build/docs/deploy/github.md) instead and use the guide's build settings in the repository. Configure the service port from the table: nginx for a Vite, Astro, SvelteKit, Docusaurus or VitePress build listens on the port you set, or on `3000` if you set none. ## Keep build settings consistent These guides use lizardpack detection unless they call for a Dockerfile. Existing `buildCommand` or `startCommand` overrides take precedence over detection and the repository Dockerfile; a set `dockerfilePath` takes precedence over both. Check [build decision order](https://lizard.build/docs/concepts/build-pipeline.md#build-decision-order) before switching build methods. Set scripts in `package.json` when a guide tells you to; adding a CLI override is a different build path. ## Check a release Read build logs, runtime logs, and the active URL. Test an inner route, a missing route, and any API or form action. A process that opens its port can still serve a broken page. For generated sites, use [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) to avoid returning the home page with HTTP 200 for every unknown URL. Check shared caches, persistent local files and framework versions separately from runtime support. See [limits](https://lizard.build/docs/platform/limits.md), [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md), and [known issues](https://lizard.build/docs/platform/known-issues.md) when your app relies on those features. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy Next.js on Lizard" description: "Deploy Next.js on Lizard as a Node.js server. Configure next build, next start, port 3000 and environment variables, then check pages, APIs and Server Actions." url: https://lizard.build/docs/framework-guides/nextjs/ updated: 2026-10-12 --- # Deploy Next.js on Lizard Run Next.js on Lizard as a Node.js server with `next build` and `next start`. The server handles request-time rendering, Route Handlers, and Server Actions. If every route can be built ahead of time, follow [Next.js static export](https://lizard.build/docs/framework-guides/nextjs/static-export.md) instead. ## Build settings | Setting | Value | |---|---| | Project root | Directory containing `package.json` and the Next.js configuration | | Build script | `next build` | | Start script | `next start --hostname 0.0.0.0 --port 3000` | | Build output | `.next/` | | Service port | `3000` | | Runtime | Node.js | ## Prepare the app Keep `next`, `react`, and `react-dom` in your dependencies and commit your lockfile. Merge these scripts into `package.json`: ```json { "scripts": { "dev": "next dev", "build": "next build", "start": "next start --hostname 0.0.0.0 --port 3000" } } ``` Use the standard Next.js output for this guide. `output: 'export'` needs a static server, and `output: 'standalone'` needs its own `server.js` launch and asset layout. Neither uses this `next start` recipe unchanged. Select a supported Node major version in `.nvmrc`, such as `22`. Exclude `.next/`, `node_modules/`, and `.env*` from the uploaded source; keep a sample environment file free of secrets if needed. ## Test the production build locally ```bash npm ci npm run build npm run start ``` In another terminal, open `http://localhost:3000` and test an inner route. If the app has an API or Server Action, test it too. Passing the development server check alone does not prove that the production build works. ## Deploy After [installing Lizard CLI and signing in](https://lizard.build/docs/framework-guides.md#prepare-the-project), run from the app directory: ```bash lizard init --name nextjs-app lizard add --service web lizard up --service web --port 3000 lizard logs --build --service web --json lizard logs --service web --json lizard ps --json ``` Lizard detects the `next` dependency and runs the build and start scripts. These commands assume the service has no existing build or start overrides. Use the URL in the deploy output to repeat the local checks. ## Environment variables and data `NEXT_PUBLIC_*` values become part of the browser bundle during the build. With `lizard up`, Lizard passes no variables to the build: commit public values in `.env.production` (the `.gitignore` from `create-next-app` can exclude `.env*`; allow that file). On a [GitHub service](https://lizard.build/docs/deploy/github.md), Lizard writes the service's `NEXT_PUBLIC_*` variables into the build; see [build-time variables](https://lizard.build/docs/concepts/build-pipeline.md#build-time-variables). Keep credentials in server-only variables and configure them for the service through [variables and secrets](https://lizard.build/docs/variables.md). No other service variables reach the build, so code that fetches data while building cannot read them. A runtime restart does not change already generated HTML or JavaScript. Local cache files and uploaded files do not form a shared store across replicas. Review Next.js cache and Server Action requirements before scaling beyond one replica. Put durable app data in a database or object storage, and read [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md). ## Troubleshooting | Symptom | Check | |---|---| | `Missing script: start` | Add the production start script above; `next dev` is for local development. | | App never becomes healthy | Match `--port 3000` to the start command and bind to `0.0.0.0`. | | Public API URL still has its old value | Change it in `.env.production` and upload again, or change the variable on a GitHub service so it rebuilds. | | Build cannot reach a database | Check whether a route fetches data at build time and whether that dependency is reachable then. | | Static export fails under `next start` | Follow the separate static export guide. | The [Next.js self-hosting guide](https://nextjs.org/docs/app/guides/self-hosting) covers framework-level cache, image, and multi-instance behavior. For deployment errors, see [service never healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md). See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy a Next.js static export" description: "Deploy a Next.js static export on Lizard with output export, an nginx Dockerfile, port 80 and real 404s. Learn which features still need a Node.js server." url: https://lizard.build/docs/framework-guides/nextjs/static-export/ updated: 2026-10-12 --- # Deploy a Next.js static export A Next.js static export produces HTML, JavaScript, and assets in `out/`. Deploy that directory on Lizard with an nginx Dockerfile. Use this path for pages that can be built ahead of time; use the [Node.js server guide](https://lizard.build/docs/framework-guides/nextjs.md) for request-time server features. ## Configure the export Merge these options into `next.config.mjs`: ```js /** @type {import('next').NextConfig} */ const nextConfig = { output: 'export', trailingSlash: true, images: { unoptimized: true }, }; export default nextConfig; ``` Keep `"build": "next build"` in `package.json`. This example uses plain exported images; an external image loader is another option. Generate all required dynamic route parameters during the build. Request-time cookies, Server Actions, and other features that need a running Next.js server cannot run in this static container. Check the [Next.js static export reference](https://nextjs.org/docs/app/guides/static-exports) against the features your app uses. ## Add a complete Dockerfile The Next.js auto-detection path expects a Node server. Setting the output to `out/` does not switch the build to nginx. Add this Dockerfile at the app root; it assumes npm and a committed `package-lock.json`: ```dockerfile FROM node:22-slim AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --from=build /app/out /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 ``` Create `nginx.conf` beside it: ```nginx server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ =404; } error_page 404 /404.html; location = /404.html { internal; } } ``` The trailing-slash export creates route directories with `index.html`. The nginx rule serves those directories and returns HTTP 404 for missing paths. Add `node_modules`, `.next`, `out`, `.git`, and `.env*` to `.dockerignore` and exclude local build files from uploads. ## Build and deploy ```bash npm ci npm run build ``` Check that `out/index.html` and an expected inner route exist. If you have Docker, test the server locally: ```bash docker build -t nextjs-static . docker run --rm -p 8080:80 nextjs-static ``` After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), deploy to a new service: ```bash lizard init --name nextjs-static lizard add --service web lizard up --service web --port 80 lizard logs --build --service web --json lizard ps --json ``` The complete Dockerfile includes an npm build step that lizardpack can recognize. On an existing service, inspect and clear conflicting build/start overrides before selecting a Dockerfile; see [build decision order](https://lizard.build/docs/concepts/build-pipeline.md#build-decision-order). ## Verify routes and updates Request the live home page, an exported inner route, a JavaScript asset, and a made-up path. The made-up path must return HTTP 404, not the home page with HTTP 200. Use the checks in [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md#verify-http-responses). All exported content changes require a new build. Runtime environment variables cannot change values already written into `out/`. For public build variables, write them as `ENV` lines in the build stage before `RUN npm run build`. Lizard passes no build args except `COMMIT_SHA`, so an `ARG` stays empty. Never place private credentials in the exported bundle. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy React with Vite on Lizard" description: "Deploy a React app built with Vite on Lizard. Build dist, serve it on port 80, configure client routes and public API variables, and check the deployed app." url: https://lizard.build/docs/framework-guides/react/ updated: 2026-10-10 --- # Deploy React with Vite on Lizard Lizard builds a React app that uses Vite and serves its `dist/` directory through nginx on port `80`. This guide covers a browser-rendered app. For React pages that need a server, follow the [Next.js guide](https://lizard.build/docs/framework-guides/nextjs.md) or provide a production server for your chosen React framework. ## Prepare the build Use an existing React and Vite project with a committed lockfile. Its `package.json` needs a production build script: ```json { "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" } } ``` If your scaffold runs TypeScript checks before `vite build`, keep those checks. Keep Vite's default `dist` output directory. A custom `build.outDir` needs a matching Dockerfile because the standard detection path copies `dist`. | Setting | Value | |---|---| | Detection | `vite` in dependencies or dev dependencies | | Build | `npm run build` | | Output | `dist/` | | Production server | nginx; no Node start script needed | | Service port | `80` | ## Test locally ```bash npm ci npm run build npm run preview ``` Open the local preview URL and test a page that calls your API. The preview command checks the build locally; do not set `vite preview` as the production start command. See [Vite deployment](https://vite.dev/guide/static-deploy.html). ## Deploy the source After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), run from the directory containing `package.json`: ```bash lizard init --name react-app lizard add --service web lizard up --service web --port 80 lizard logs --build --service web --json lizard ps --json ``` Upload source and the lockfile, excluding `node_modules/`, `dist/`, and secrets. Leave service build/start overrides unset to use the static detection path. The nginx container listens on port 80, the port passed with `--port`, even though the development and preview servers use other ports. ## Connect an API Use a public variable such as `VITE_API_URL` for the API's browser-reachable HTTPS address. Read it as `import.meta.env.VITE_API_URL`. With `lizard up`, Lizard passes no variables to the build: commit the value in `.env.production` (check that `.gitignore` does not exclude it) and upload again when it changes. On a [GitHub service](https://lizard.build/docs/deploy/github.md), set it through [variables and secrets](https://lizard.build/docs/variables.md) and the change rebuilds the app. See [build-time variables](https://lizard.build/docs/concepts/build-pipeline.md#build-time-variables). Never expose a database URL, API credential, or internal-only service address through a `VITE_*` variable. For an API on another origin, configure its allowed origins to include the frontend URL. A private service hostname that works between backend services will not resolve in a visitor's browser. ## Verify routing Open the live app, follow a client route, then reload that URL directly. The default static server falls back to `index.html` so the client router can render an inner route. Add a route for unknown paths in the React app too. That fallback still returns HTTP 200; use [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) to choose a server policy for pages that need real HTTP 404 responses. If an asset returns HTML or the page goes blank, check Vite's `base`, the requested asset URL, and the output directory. If the app never becomes healthy, check that the service port is `80` and that an old start-command override has not selected a different build path. For pages meant to appear in search, inspect the initial HTML response. A browser-rendered shell may not contain the text you expect a search engine or answer engine to read. Choose prerendering or a server framework when you need that text in the response. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy Vue with Vite on Lizard" description: "Deploy a Vue app built with Vite on Lizard. Configure dist output, port 80, Vue Router history mode, public environment variables, and production checks." url: https://lizard.build/docs/framework-guides/vue/ updated: 2026-10-10 --- # Deploy Vue with Vite on Lizard Deploy a Vue app built with Vite as a static web service on Lizard. The build produces `dist/`, and nginx serves the files on port `80`. For Nuxt server rendering and server routes, use the [Nuxt guide](https://lizard.build/docs/framework-guides/nuxt.md). ## Prepare the project Run from the Vue app directory with `package.json`, a lockfile, and the Vite configuration. Keep the scaffold's build script, including `vue-tsc` if it checks your TypeScript code. The build must finish by producing the Vite bundle in `dist/`. | Setting | Value | |---|---| | Build | `npm run build` | | Output | `dist/` | | Start command | None for the static detection path | | Service port | `80` | Keep `base: '/'` for a site at the domain root. If you use a path prefix, align Vite's base with the router base and the URL where the app actually runs. A custom output directory needs a Dockerfile that copies that directory. ## Check the build locally ```bash npm ci npm run build npm run preview ``` Use the local preview URL to check a component that fetches data and a page reached through the router. `vite preview` is for this local check; nginx serves the production files. See [Vite's deployment guide](https://vite.dev/guide/static-deploy.html) for the build output and preview behavior. ## Deploy After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), deploy the current source directory: ```bash lizard init --name vue-app lizard add --service web lizard up --service web --port 80 lizard logs --build --service web --json lizard ps --json ``` Exclude local dependencies, `dist/`, and `.env` files from the upload. With no existing command overrides, lizardpack detects Vite and builds the static image. Do not add `npm run dev` as a service start command. ## Vue Router history mode If your app uses `createWebHistory`, its server must handle direct requests to client routes. The default static image serves `index.html` when it cannot find a file, so `/account` can reach Vue Router on a hard reload. Match the router base to the Vite base, for example with `createWebHistory(import.meta.env.BASE_URL)`. See [Vue Router history modes](https://router.vuejs.org/guide/essentials/history-mode.html). Test both navigation from the home page and opening `/account` in a new tab. Add a catch-all view for missing client routes. The server fallback returns HTTP 200 even for unknown paths; it does not supply search-friendly 404 responses. Read [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) before using this setup for an indexed content site. ## Variables and API requests Vite writes `VITE_*` values into the browser bundle during the build. Use them only for public values, such as your API's public HTTPS URL. With `lizard up`, commit them in `.env.production` (check that `.gitignore` does not exclude it); Lizard variables reach the build only on a [GitHub service](https://lizard.build/docs/deploy/github.md), as described in [build-time variables](https://lizard.build/docs/concepts/build-pipeline.md#build-time-variables). Then check the rebuilt app's actual network request. A runtime-only restart cannot replace a value inside built JavaScript. If requests fail only after deployment, check CORS on the API and confirm that the browser is not calling `localhost` or a private backend hostname. If the HTML loads but assets fail, inspect Vite's base and the asset paths. For pages that need HTML before JavaScript runs, choose Nuxt rendering or an explicit prerendering step. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy Astro on Lizard" description: "Deploy Astro on Lizard as a static site or with the standalone Node adapter. Set output, build commands and ports, then check server routes and missing pages." url: https://lizard.build/docs/framework-guides/astro/ updated: 2026-10-12 --- # Deploy Astro on Lizard Deploy Astro on Lizard in either of two modes: serve a static `dist/` directory on port `80`, or run the standalone Node adapter on port `3000` for on-demand routes. Choose the mode before deploying because the adapter changes both the output and the runtime. ## Choose a mode | Mode | Configuration | Runtime | Port | |---|---|---|---| | Static site | Static output without a server adapter | nginx serves `dist/` | `80` | | Node server | `@astrojs/node` in standalone mode | `node ./dist/server/entry.mjs` | `3000` | Both paths use a `build` script that runs `astro build`. Commit the lockfile and keep the default `dist` output path. Detection reads the Astro config and installed dependencies. An unused Node adapter dependency alone does not select the server path. Use literal `output` and adapter settings; dynamic values, custom output paths, and middleware mode need a Dockerfile. ## Configure a Node server For on-demand pages, add a Node adapter version compatible with your Astro version: ```bash npx astro add node ``` Check `astro.config.mjs`: ```js import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), }); ``` Standalone mode starts its own HTTP server. Middleware mode requires a separate server and does not fit this launch command. Read the [Astro Node adapter guide](https://docs.astro.build/en/guides/integrations-guide/node/) if you need middleware or a mix of prerendered and on-demand routes. ## Test locally ```bash npm ci npm run build ``` For the Node mode: ```bash HOST=0.0.0.0 PORT=3000 node ./dist/server/entry.mjs ``` Request a route that runs on the server and a built asset. For a static site, use `npm run preview` locally and inspect the generated route files in `dist/`. ## Deploy After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), create the project: ```bash lizard init --name astro-app lizard add --service web ``` For the Node adapter: ```bash lizard up --service web --port 3000 ``` For a static site: ```bash lizard up --service web --port 80 ``` Use only the command for your chosen mode. Leave service command overrides unset for lizardpack detection. Read `lizard logs --build --service web --json`, then check runtime logs and the live URL. ## Variables, sessions, and routes Values used to generate static HTML require a new build when they change. Server code can read runtime variables through the mechanisms supported by your Astro version; configure them in [variables and secrets](https://lizard.build/docs/variables.md). Browser-visible values must not contain credentials. If your app uses sessions, choose storage that fits restarts and multiple replicas. Do not rely on the container filesystem as a shared durable store. See [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md). For a static content site, test a missing URL and use [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) to return the proper status. If an SSR deployment exits or serves no routes, verify that `dist/server/entry.mjs` exists, the adapter uses standalone mode, and the service port is `3000`. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy Nuxt on Lizard" description: "Deploy Nuxt on Lizard with Nitro's node-server preset. Add a production start script, run the server on port 3000, and check routes and runtime config." url: https://lizard.build/docs/framework-guides/nuxt/ updated: 2026-10-12 --- # Deploy Nuxt on Lizard Run Nuxt on Lizard with Nitro's `node-server` preset. The build produces `.output/server/index.mjs`, and a Node.js process serves pages and server routes on port `3000`. For a generated site on port `80`, use the static recipe below. ## Configure the production build Use the current Lizard CLI from [Prepare the project](https://lizard.build/docs/framework-guides.md#prepare-the-project). Version 0.3.95 and later exclude macOS archive metadata during uploads. If an older CLI reports a `._*.ts` route error, update the CLI and upload again. Set the preset in `nuxt.config.ts`: ```ts export default defineNuxtConfig({ nitro: { preset: 'node-server' }, }); ``` Merge these scripts into `package.json`, keeping any other scripts your app needs: ```json { "scripts": { "dev": "nuxt dev", "build": "nuxt build", "start": "HOST=0.0.0.0 PORT=3000 node .output/server/index.mjs" } } ``` The start script uses the Linux shell in the deployment container. lizardpack detects the `nuxt` dependency and runs the build and start scripts. A fresh Nuxt project may not include `start`; add it rather than assuming `nuxt dev` will serve the production build. | Setting | Value | |---|---| | Build | `npm run build` | | Server entry | `.output/server/index.mjs` | | Start | `npm run start` | | Service port | `3000` | ## Test the built app ```bash npm ci npm run build npm run start ``` Open `http://localhost:3000`, request an inner page directly, and call one of your `server/api` routes if present. Check that the build reports the Node server preset. A provider-specific Nitro preset can produce a different entry point. ## Deploy After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), run from the Nuxt app directory: ```bash lizard init --name nuxt-app lizard add --service web lizard up --service web --port 3000 lizard logs --build --service web --json lizard logs --service web --json lizard ps --json ``` Keep `.nuxt/`, `.output/`, `node_modules/`, and `.env` files out of the upload. Include source, config, and the lockfile. Existing service build/start overrides bypass the detection used here; see [build decision order](https://lizard.build/docs/concepts/build-pipeline.md#build-decision-order). ## Runtime configuration Declare runtime settings in `runtimeConfig` and configure their matching `NUXT_*` values for the service. Keep secrets outside `runtimeConfig.public`; the public part reaches the browser. Values used to prerender pages still affect the generated build, so check both request-time and prerendered routes after a change. Use [variables and secrets](https://lizard.build/docs/variables.md) to configure the service and [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md) for durable data. Do not treat a local cache or session file as shared storage across replicas. ## Troubleshooting If the process reports a missing `.output/server/index.mjs`, inspect the preset and the build output. If it reports a missing start script, add the one above. If the site never becomes healthy, check the host and port. For a purely generated Nuxt site, serve `.output/public/` with a static Dockerfile and correct route handling. Do not start that output with the Node command above. The [Nuxt deployment guide](https://nuxt.com/docs/4.x/getting-started/deployment) explains the Node and generated outputs; [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) covers the Lizard static server setup. ## Generate a static site For generated HTML, replace the Node preset with explicit prerender settings and change the build script to `nuxt generate`: ```ts export default defineNuxtConfig({ nitro: { prerender: { crawlLinks: true, routes: ['/'] }, }, }); ``` Add unlinked or dynamic routes to `routes` when the crawler cannot discover them. Run `npm run build` and check that `.output/public/index.html` and your inner route files exist. In the test with Nuxt 4.5.2, keeping `preset: 'node-server'` while switching only to `nuxt generate` produced the fallback pages without the site's routes. The build succeeded even though the export lacked the site's routes. Use the Dockerfile and nginx configuration in [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md), with build script `build`, output directory `.output/public`, and service port `80`. The static container does not run Nuxt server routes or read runtime config for already generated pages. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy SvelteKit on Lizard" description: "Deploy SvelteKit on Lizard with adapter-node. Configure the production build, port 3000, ORIGIN, form actions, and the separate adapter-static path." url: https://lizard.build/docs/framework-guides/sveltekit/ updated: 2026-09-09 --- # Deploy SvelteKit on Lizard Use `@sveltejs/adapter-node` to build a SvelteKit server for Lizard. It produces `build/`, which starts with `node build` on port `3000`. Install and configure the adapter explicitly; a scaffold that still uses `adapter-auto` does not establish a Node deployment target. Start with the [complete source example](https://github.com/lizard-build/docs/tree/main/_examples/sveltekit), which includes the configuration and files used by this recipe. ## Configure the adapter ```bash npm install --save-dev @sveltejs/adapter-node ``` In `svelte.config.js`, keep your existing preprocess and other settings, but set `kit.adapter` to the Node adapter: ```js import adapter from '@sveltejs/adapter-node'; export default { kit: { adapter: adapter() }, }; ``` Keep only the deployment adapter you intend to use. In particular, an unused `@sveltejs/adapter-static` dependency can select the static path in the current detector even when your config imports adapter-node. | Setting | Value | |---|---| | Build | `npm run build`, usually `vite build` | | Output | `build/` | | Runtime | `node build` | | Host and port | `HOST=0.0.0.0`, `PORT=3000` | | Service port | `3000` | ## Test the production server ```bash npm ci npm run build HOST=0.0.0.0 PORT=3000 ORIGIN=http://localhost:3000 node build ``` Open a server-rendered page and submit a form action if the app has one. Keep the adapter's default output path for the detection used in this guide. ## Deploy and set the public origin After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project): ```bash lizard init --name sveltekit-app lizard add --service web lizard up --service web --port 3000 lizard ps --json ``` Set `ORIGIN` to the exact public HTTPS origin from the deployment output, without a path. For example, replace this placeholder with your actual URL: ```bash lizard secrets set ORIGIN=https://YOUR_PUBLIC_HOST --service web ``` Use the custom domain instead if that is the origin visitors will use. Recheck forms after the variable change. For several allowed origins or proxy-derived URLs, read the [SvelteKit Node server guide](https://svelte.dev/docs/kit/adapter-node) and configure trusted proxy headers deliberately. ## Verify and troubleshoot Read build and runtime logs with `lizard logs --build --service web --json` and `lizard logs --service web --json`. Open an inner route directly, submit a form, and request a missing route. If forms report a cross-site submission error, check `ORIGIN` before disabling a security check. If `node build` cannot find the server, verify the active adapter and the output path. Leave service command overrides unset to use the lizardpack path. ## Static SvelteKit sites An app that can prerender all required pages can use `@sveltejs/adapter-static`, its default `build/` output, and service port `80`. That output cannot run server actions or request-time endpoints. Configure prerendering for the routes you need; installing the package alone is not enough. Install `@sveltejs/adapter-static` and replace the adapter import in `svelte.config.js`: ```js import adapter from '@sveltejs/adapter-static'; export default { kit: { adapter: adapter() }, }; ``` For a site whose routes can all be generated, add this to `src/routes/+layout.js`: ```js export const prerender = true; export const trailingSlash = 'always'; ``` Remove unused deployment adapters from dependencies. Run `npm run build` and check that `build/` contains each required page. For the default output, leave service command overrides unset and deploy a new service on port `80`. New builds serve the generated HTML and return HTTP 404 for missing pages. Do not reuse the server recipe's port `3000` for nginx. Use [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) to choose between generated HTML routes and an SPA fallback. See the [SvelteKit static adapter](https://svelte.dev/docs/kit/adapter-static) for framework options and limitations. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy a Node.js app (Express)" description: "Host a Node.js or Express app on Lizard: lizardpack reads package.json, picks Node from .nvmrc or engines, runs npm start on port 3000 and sets PORT." url: https://lizard.build/docs/framework-guides/nodejs/ updated: 2026-10-12 --- # Deploy a Node.js app (Express) To host a Node.js app on Lizard, deploy a folder with a `package.json`. lizardpack picks the package manager from your lock file, installs dependencies, runs the `build` script if there is one and starts the app with the `start` script on port `3000`. It uses Node.js 22 unless `.nvmrc` or `engines.node` names another major version. Lizard passes the port in `PORT`; read it in your app's listener. **Test status:** the Express sample below ran locally with Express 5.3.0, and the database route with pg 8.23.1 against PostgreSQL 14. We checked its build plan with lizardpack, the build planner Lizard runs. We have not yet deployed it end to end with this guide. ## Before you start - Node.js and npm on your computer to create the lock file and test locally. - Lizard CLI, signed in with `lizard login`, on Pro or the 7-day Pro trial (card required). See [pricing](https://lizard.build/pricing). Next.js, Nuxt, Astro, SvelteKit and Vite apps have their own guides: see [framework guides](https://lizard.build/docs/framework-guides.md). ## A minimal Express app `package.json`: ```json { "name": "express-app", "private": true, "type": "module", "scripts": { "start": "node server.js" }, "engines": { "node": "22.x" }, "dependencies": { "express": "^5.1.0" } } ``` `server.js`: ```js import express from "express"; const app = express(); const port = Number(process.env.PORT) || 3000; app.get("/", (req, res) => { res.send("Hello from Express on Lizard"); }); app.get("/healthz", (req, res) => { res.json({ status: "ok" }); }); app.listen(port, "0.0.0.0", () => { console.log(`listening on ${port}`); }); ``` Run `npm install` to create `package-lock.json`, commit both files, and add `node_modules` and `.env` to `.gitignore`. Check it locally: ```bash npm start curl http://localhost:3000/healthz ``` ## What lizardpack does with a Node.js app | Step | What happens | |---|---| | Package manager | `bun.lock` or `bun.lockb`: Bun. `pnpm-lock.yaml`: pnpm. `yarn.lock`: Yarn. `package-lock.json`: `npm ci`. No lock file: `npm install`. | | Node.js version | `.nvmrc`, then `.node-version`, then `.tool-versions`, then `engines.node`. Major version only, on `node:-slim`. Default `22`. | | Build | The `build` script, if `package.json` has one, in a separate stage that has your devDependencies | | Start | The `start` script; else `node
`; else the first of `server.js`, `index.js`, `app.js`, `main.js`, `dist/index.js` | | Runtime | `NODE_ENV=production`, port `3000` | Without a `build` script, the image sets `NODE_ENV=production` before it installs packages, so devDependencies are left out. A start script that calls `nodemon`, `tsx` or `ts-node` from devDependencies then fails. For TypeScript, compile in `build` and start the output: ```json { "scripts": { "build": "tsc", "start": "node dist/index.js" } } ``` For version files and parsing rules, see [runtime versions](https://lizard.build/docs/deploy/runtime-versions.md). Leave the service's build and start commands empty: with either one set, Lizard writes its own Dockerfile on `node:22-alpine` and ignores your version files. See the [build pipeline](https://lizard.build/docs/concepts/build-pipeline.md). ## Deploy From the app's root: ```bash lizard init --name node-app lizard add --service web lizard up --service web --port 3000 ``` `lizard up` uploads the folder, builds it on Lizard and prints the URL. Inside a git repository it skips what `.gitignore` lists; outside git it skips `node_modules` but uploads `.env` files. See [deploy from local code](https://lizard.build/docs/deploy/upload.md). For git push to deploy, push the repository to GitHub and connect it with `lizard add -r your-org/your-app -n web`. See [deploy from GitHub](https://lizard.build/docs/deploy/github.md). ## Set variables ```bash lizard secrets set SESSION_SECRET="$(openssl rand -hex 32)" --service web ``` Read values with `process.env.SESSION_SECRET`. A variable change restarts the service without a rebuild. See [variables and secrets](https://lizard.build/docs/variables.md). ## Add Managed Postgres ```bash lizard add postgres lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service web npm install pg ``` The single quotes keep `${{…}}` away from your shell; Lizard resolves the [reference](https://lizard.build/docs/variables/references.md) at deploy time to the database's address on the project's private network. Add a pool and a route to `server.js`: ```js import pg from "pg"; const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }); app.get("/db", async (req, res) => { const { rows } = await pool.query("SELECT now() AS now"); res.json(rows[0]); }); ``` Express 5 passes a rejected promise from a route to its error handler, so a failed query returns HTTP 500 instead of crashing the process. Run migrations from your computer with the database's public URL: ```bash lizard run --service postgres -- npm run migrate ``` For a cache or a job queue, add [Managed Redis](https://lizard.build/docs/addons/redis.md) the same way: `lizard add redis`, then `REDIS_URL='${{redis.REDIS_PRIVATE_URL}}'`. ## Check that it works ```bash lizard logs --build --service web lizard logs --service web --tail 100 lizard ps ``` The runtime log shows `listening on 3000`. Request `/healthz` and `/db` on the URL that `lizard up` printed. ## Common problems | Symptom | Fix | |---|---| | `npm ci` fails because the lock file is out of sync | Run `npm install` locally and commit `package-lock.json`. | | `nodemon: not found` or `tsx: not found` | Those tools are devDependencies and are not installed for start. Add a `build` step, or move the tool to `dependencies`. | | Deploy never becomes healthy | Listen on `process.env.PORT`. A fixed port such as `5000` does not match the service port. See [service never healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md). | | Wrong Node.js version | Put the major version in `.nvmrc`, for example `24`, and deploy again. | | `Cannot use import statement outside a module` | Add `"type": "module"` to `package.json`, or use `require`. | A Node.js process that serves no HTTP, such as a queue consumer or a [Discord bot](https://lizard.build/docs/guides/discord-bot.md), runs as a [background worker](https://lizard.build/docs/deploy/workers.md). ## Related - [Framework guides](https://lizard.build/docs/framework-guides.md) - [Runtime versions](https://lizard.build/docs/deploy/runtime-versions.md) - [Managed Postgres](https://lizard.build/docs/addons/postgres.md) - [Logs](https://lizard.build/docs/observability/logs.md) --- title: "Deploy FastAPI on Lizard" description: "Deploy FastAPI on Lizard with Uvicorn, requirements.txt, a Procfile, and port 8000. Verify a health endpoint and diagnose import, host, and dependency errors." url: https://lizard.build/docs/framework-guides/fastapi/ updated: 2026-09-09 --- # Deploy FastAPI on Lizard Run FastAPI on Lizard with Uvicorn listening on `0.0.0.0:8000`. Include both FastAPI and Uvicorn in the app's dependencies. Importing a file that declares `app = FastAPI()` does not start an HTTP server by itself. ## Example project The [FastAPI example](https://github.com/lizard-build/fastapi-example) includes an app, a Procfile, pinned dependencies, an MIT license, and HTTP tests. Its CI runs the production Uvicorn entry point on Linux. We also deployed commit `df522c1` from GitHub and checked health, OpenAPI, interactive docs, JSON echo, invalid input, and a missing route over public HTTPS on 2026-09-09. ## Prepare an app This example uses `requirements.txt` and a `main.py` at the application root: ```python from fastapi import FastAPI app = FastAPI() @app.get("/health") def health(): return {"status": "ok"} ``` Add `fastapi` and `uvicorn` to your dependency workflow and save the resolved, tested versions in `requirements.txt`. Select your Python minor version in `.python-version`, for example `3.13`, if your dependencies support it. Create a `Procfile` with an explicit import target: ```text web: uvicorn main:app --host 0.0.0.0 --port 8000 ``` `main:app` means the `app` object in `main.py`. For `src/api.py`, use the import path that works from your app root, such as `src.api:app`. The explicit Procfile avoids relying on detection of source-code patterns in custom layouts. | Setting | Value | |---|---| | Install | `pip install -r requirements.txt` | | Start | Procfile `web:` command | | Service port | `8000` | | Build output | Python source and installed dependencies | ## Test locally Use a local virtual environment: ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt uvicorn main:app --host 0.0.0.0 --port 8000 ``` In another terminal: ```bash curl --fail http://localhost:8000/health ``` Expect a successful response containing `{"status":"ok"}`. Keep reload mode for development. The [FastAPI server guide](https://fastapi.tiangolo.com/deployment/manually/) explains the ASGI server and import target. ## Deploy After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), upload from the application root: ```bash lizard init --name fastapi-app lizard add --service api lizard up --service api --port 8000 lizard logs --build --service api --json lizard logs --service api --json lizard ps --json ``` Exclude `.venv/`, `__pycache__/`, and `.env` files. Leave service build/start overrides unset so lizardpack reads the Procfile. The requirements-based path here does not need a separate compile command. Request `/health` on the returned HTTPS URL, then test a real API action. A TCP health probe only establishes that the process opens its port; your health endpoint can add checks that matter to the app. ## Data and common failures Set credentials through [variables and secrets](https://lizard.build/docs/variables.md). Add [Managed Postgres](https://lizard.build/docs/addons/postgres.md) when the app needs a database, and use a service-scoped connection reference. Container-local files are not durable application storage; see [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md). For `No module named uvicorn`, check the installed requirements. For an ASGI import error, run the exact Procfile command locally from the same root. For a service that never becomes healthy, check port `8000` and the bind host. If the process exits with no server logs, confirm that it runs Uvicorn rather than just `python main.py`. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy Django on Lizard" description: "Deploy Django on Lizard with Gunicorn, an explicit WSGI module, port 8000, production settings, database migrations, and a plan for static and uploaded files." url: https://lizard.build/docs/framework-guides/django/ updated: 2026-10-10 --- # Deploy Django on Lizard Run Django on Lizard with Gunicorn and your project's WSGI module on port `8000`. Prepare production settings, database access, and static-file serving before relying on the deployed app. `manage.py runserver` is a development server. ## Set the start command This guide assumes `manage.py`, `requirements.txt`, and a project package named `config` containing `wsgi.py`. Replace `config` with your actual package name. Include Django, Gunicorn, and the database driver your app uses in your tested requirements. Add a `Procfile`: ```text web: gunicorn config.wsgi:application --bind 0.0.0.0:8000 ``` The explicit module avoids ambiguity when settings live in nested packages. An ASGI app with WebSockets needs an ASGI server and a different launch command; this guide covers WSGI. | Setting | Value | |---|---| | Install | `pip install -r requirements.txt` | | Runtime | Gunicorn with your WSGI module | | Service port | `8000` | | Working directory | Directory containing `manage.py` | ## Prepare production settings Django expects a `DATABASES` dictionary in `settings.py`. Setting `DATABASE_URL` on the service alone does not connect Django to PostgreSQL. The steps below create [Managed Postgres](https://lizard.build/docs/addons/postgres.md), pass its URL to the app, and turn that URL into Django's connection settings. Add these packages to `requirements.txt`, keeping any other dependencies your app needs. These are the versions used by the [complete example](https://github.com/lizard-build/docs/tree/main/_examples/django): ```text Django==6.1.1 gunicorn==26.2.0 whitenoise==6.12.0 psycopg[binary]==3.3.5 dj-database-url==3.1.2 ``` `psycopg` is the PostgreSQL driver. `dj-database-url` reads the database name, user, password, host, and port from the connection URL. Replace the corresponding settings in your project's `settings.py` with this block; keep your existing apps, middleware, templates, and other settings: ```python import os from pathlib import Path import dj_database_url from django.core.exceptions import ImproperlyConfigured BASE_DIR = Path(__file__).resolve().parent.parent def required_env(name): value = os.environ.get(name, "").strip() if not value: raise ImproperlyConfigured(f"Set the {name} environment variable.") return value def env_list(name): values = [item.strip() for item in required_env(name).split(",") if item.strip()] if not values: raise ImproperlyConfigured(f"Set at least one value in {name}.") return values SECRET_KEY = required_env("SECRET_KEY") debug_value = os.environ.get("DEBUG", "false").strip().lower() if debug_value not in {"true", "false"}: raise ImproperlyConfigured("DEBUG must be true or false.") DEBUG = debug_value == "true" ALLOWED_HOSTS = env_list("ALLOWED_HOSTS") CSRF_TRUSTED_ORIGINS = env_list("CSRF_TRUSTED_ORIGINS") DATABASES = { "default": dj_database_url.parse( required_env("DATABASE_URL"), conn_max_age=60, conn_health_checks=True, ) } ``` Do not leave an older `DATABASES`, `SECRET_KEY`, or `DEBUG` assignment later in the file: it would replace these values. This example requires nonempty `SECRET_KEY`, `DATABASE_URL`, `ALLOWED_HOSTS`, and `CSRF_TRUSTED_ORIGINS` values. Missing or empty values stop startup with the variable's name. `DEBUG` defaults to `false`; set it to `false` on the deployed service. `ALLOWED_HOSTS` takes comma-separated hostnames without a scheme or path, such as `app.example.com,www.example.com`. `CSRF_TRUSTED_ORIGINS` takes comma-separated origins with a scheme, such as `https://app.example.com`. Include a port for local origins that use one. The example requires an explicit origin list; Django itself permits an empty list for apps that do not need extra trusted origins. Trust only origins that should submit forms to this app. These settings do not replace CSRF tokens. The URL comes from a service [secret or reference](https://lizard.build/docs/variables.md), not from a value committed to source. Do not use container-local SQLite as the durable database for a deployed app. See [dj-database-url usage](https://pypi.org/project/dj-database-url/) for URL parsing and connection options. With your local environment variables set and PostgreSQL reachable, run: ```bash python -m pip install -r requirements.txt python manage.py check --deploy gunicorn config.wsgi:application --bind 0.0.0.0:8000 ``` Review Django's [deployment checklist](https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/) for settings relevant to your app. The small example does not configure every production security setting. Verify proxy and HTTPS handling with the actual public URL before enabling secure cookies, HSTS, or redirects. Only trust forwarded HTTPS headers when the proxy controls them. ## Plan static files and migrations Gunicorn alone does not serve Django's static files. Choose static-file middleware configured in the app or a separate static host. Collect assets into the path that setup serves. lizardpack runs `python manage.py collectstatic --noinput` during a Django build, but a failure there only prints a warning, for example when settings need runtime secrets. The Dockerfile below gives `collectstatic` build-only values and fails the build if it errors. For WhiteNoise, keep `django.contrib.staticfiles` in `INSTALLED_APPS` and insert `whitenoise.middleware.WhiteNoiseMiddleware` immediately after Django's `SecurityMiddleware`. Add or update these settings: ```python STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "staticfiles" STORAGES = { "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, "staticfiles": { "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", }, } ``` Keep your existing `STORAGES["default"]` backend if the app already stores uploads elsewhere. WhiteNoise serves collected static assets, not user uploads. Use this Dockerfile at the project root: ```dockerfile FROM python:3.13-slim WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY . . RUN SECRET_KEY=build-only-placeholder \ DATABASE_URL=postgresql://build:build@127.0.0.1:1/build \ ALLOWED_HOSTS=localhost CSRF_TRUSTED_ORIGINS=http://localhost \ python manage.py collectstatic --noinput EXPOSE 8000 CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"] ``` The values on the `RUN` line exist only for `collectstatic`. The database URL is a placeholder pointing at an unused local port; no database runs there. Loading settings parses the URL, but asset collection does not need a database connection. Do not query the database from module imports or `AppConfig.ready()`. The example also passes `collectstatic` with container networking disabled. These assignments do not become runtime environment variables. Set a fresh `SECRET_KEY` and the real database reference on the service before startup. Do not pass production secrets to the Docker build. Create `.dockerignore` and exclude the same paths from [source uploads](https://lizard.build/docs/cli/up.md): ```text .env* .venv/ venv/ .git/ __pycache__/ *.pyc *.sqlite3 staticfiles/ ``` Keep user uploads in durable storage, such as [Managed Object Storage](https://lizard.build/docs/addons/storage.md), rather than the container's static directory. Review [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md). Treat database migrations as a separate release step. Back up data as needed, make migrations safe to retry, and apply them before requests require the new schema. A pre-deploy command is not a substitute for checking concurrency and failure behavior. ## Deploy and verify After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), connect a [GitHub service](https://lizard.build/docs/deploy/github.md), configure its secrets and production settings, and deploy with container port `8000`. For a new upload-based project: ```bash lizard init --name django-app lizard add --service web ``` Create [Managed Postgres](https://lizard.build/docs/addons/postgres.md) in this project. If you already have an instance, skip `add postgres` and use its name in the reference: ```bash lizard add postgres --name postgres ``` Set the app's [service secrets](https://lizard.build/docs/cli/secrets.md). The `postgres` in `${{postgres.DATABASE_URL}}` names the database service, not a Django database alias. Lizard resolves the [reference](https://lizard.build/docs/variables/references.md) at deploy time and injects the resulting URL into the `web` process. The single quotes keep your shell from expanding the reference. The `settings.py` block above then turns the URL into `DATABASES["default"]`. ```bash DJANGO_SECRET_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')" lizard secrets set SECRET_KEY="$DJANGO_SECRET_KEY" \ DATABASE_URL='${{postgres.DATABASE_URL}}' DEBUG=false \ ALLOWED_HOSTS=localhost CSRF_TRUSTED_ORIGINS=http://localhost \ --service web lizard up --service web --port 8000 lizard ps --json ``` `localhost` is a temporary host setting that lets the process start while you obtain its public hostname; public requests will return 400. If you already know the hostname, set it before the first upload. Otherwise, replace these placeholders with the hostname and HTTPS origin returned for your service: ```bash lizard secrets set ALLOWED_HOSTS=YOUR_PUBLIC_HOST \ CSRF_TRUSTED_ORIGINS=https://YOUR_PUBLIC_HOST --service web ``` A missing reference target or key can resolve to an empty string. The required-value check then stops Django with `Set the DATABASE_URL environment variable.` Check the database service name and its `DATABASE_URL` key; `lizard logs --build --service web` warns about every reference that resolved to an empty value. Do not print the connection URL into logs. The variable change restarts the process. Exclude local virtual environments, secrets, and local databases from uploads. Apply migrations from your computer. `lizard run --service postgres` runs a local command with the database's connection values, including its public `DATABASE_URL`. Pass the other settings this example requires inline; only the local command uses them: ```bash python -m pip install -r requirements.txt SECRET_KEY=local-migrate ALLOWED_HOSTS=localhost CSRF_TRUSTED_ORIGINS=http://localhost \ lizard run --service postgres -- python manage.py migrate --noinput ``` Run the command again to check that no migrations remain. Do not treat a successful port check as proof that tables exist. Check the app URL, a database-backed page, a form submission, and a static asset. If the app uses Django admin, also check its CSS. Read `lizard logs --service web --json` for import or settings errors. A 400 response often means `ALLOWED_HOSTS` is wrong; missing CSS usually means static assets were not collected or served. Check the actual error before changing settings. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. ## Reproduce this example The [Django example](https://github.com/lizard-build/docs/tree/main/_examples/django) includes the settings, Dockerfile, requirements, a database-backed form, and a test runner. Copy it into a standalone directory and run `python3 tests/verify.py` with Docker running. It builds the image, starts local PostgreSQL, checks migrations and HTTP behavior, and stops its test containers. It does not create Lizard services. See its README for the checks and retained Docker resources. The September 14, 2026 check covers this revised example in local Linux containers. The earlier cloud results linked above cover the September 9 guide; the revised settings have not had a new cloud deployment yet. --- title: "Deploy a Streamlit app" description: "Deploy a Streamlit app on Lizard: a Procfile runs streamlit run app.py on 0.0.0.0:8501, requirements.txt pins Streamlit, secrets come from variables." url: https://lizard.build/docs/framework-guides/streamlit/ updated: 2026-10-12 --- # Deploy a Streamlit app Lizard runs a Streamlit app with the command you use locally, `streamlit run app.py`. Put it in a Procfile `web:` line with `--server.address 0.0.0.0` and a fixed port such as `8501`, then deploy with `lizard up --port 8501`. Without the Procfile, lizardpack starts `python app.py`, which prints a warning and exits instead of serving the app. **Test status:** the sample below ran locally with Streamlit 1.65.0, and the database version with psycopg 3.3.5 against PostgreSQL 14. We checked its build plan with lizardpack, the build planner Lizard runs. We have not yet deployed it end to end. The browser keeps a WebSocket open to Streamlit, and we have not yet checked that connection through Lizard's routing. ## Before you start - Python 3 on your computer to test the app. - Lizard CLI, signed in with `lizard login`, on Pro or the 7-day Pro trial (card required). See [pricing](https://lizard.build/pricing). ## A minimal app `app.py`: ```python import streamlit as st st.title("Hello from Streamlit on Lizard") name = st.text_input("Your name", "world") st.write(f"Hello, {name}!") ``` `requirements.txt`: ```text streamlit==1.65.0 ``` `.python-version`: ```text 3.13 ``` `Procfile`: ```text web: streamlit run app.py --server.port 8501 --server.address 0.0.0.0 ``` Run it locally: ```bash python -m venv .venv && . .venv/bin/activate pip install -r requirements.txt streamlit run app.py ``` Streamlit opens on `http://localhost:8501`. Add `.venv/`, `.env` and `.streamlit/secrets.toml` to `.gitignore`. ## What lizardpack builds | Step | What happens | |---|---| | Detection | `requirements.txt`, `pyproject.toml`, `Pipfile` or any `.py` file | | Python version | `.python-version`, then `.tool-versions`, then `requires-python` in `pyproject.toml`; major and minor only, on `python:-slim`. Default `3.13`. | | Install | `pip install -r requirements.txt`; uv, Poetry or Pipenv when their lock or config file is present | | Start | The Procfile `web:` line | | Runtime | `PYTHONUNBUFFERED=1`, so `print` output reaches the logs at once | Set the service port with `--port` to match `--server.port` in the Procfile. See [runtime versions](https://lizard.build/docs/deploy/runtime-versions.md) for the version rules and their edge cases. ## Deploy From the app's folder: ```bash lizard init --name streamlit-app lizard add --service web lizard up --service web --port 8501 ``` `lizard up` uploads the folder, builds it on Lizard and prints the URL. Inside a git repository it skips what `.gitignore` lists; outside git it uploads `.env` files. For git push to deploy, [connect a GitHub repository](https://lizard.build/docs/deploy/github.md) and set the port with `lizard port 8501 --service web`. ## Secrets and settings `st.secrets` reads `.streamlit/secrets.toml`, a file you should not upload. On Lizard, store secrets as service variables and read them from the environment: ```bash lizard secrets set OPENAI_API_KEY=sk-... --service web ``` ```python import os api_key = os.environ["OPENAI_API_KEY"] ``` A variable change restarts the app without a rebuild. See [variables and secrets](https://lizard.build/docs/variables.md). ## Add Managed Postgres ```bash lizard add postgres lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service web ``` The single quotes keep `${{…}}` away from your shell; Lizard resolves the [reference](https://lizard.build/docs/variables/references.md) at deploy time. Add `psycopg[binary]==3.3.5` to `requirements.txt` and open one connection per server process: ```python import os import psycopg import streamlit as st @st.cache_resource def get_conn(): return psycopg.connect(os.environ["DATABASE_URL"], autocommit=True) with get_conn().cursor() as cur: cur.execute("SELECT now()") st.write("Database time:", cur.fetchone()[0]) ``` `st.cache_resource` keeps the connection across reruns and browser sessions. Keep data in [Managed Postgres](https://lizard.build/docs/addons/postgres.md) or [Managed Object Storage](https://lizard.build/docs/addons/storage.md): files the app writes inside the container are lost on the next deploy. ## Check that it works ```bash lizard logs --service web --tail 100 lizard domain --service web curl https:///_stcore/health ``` The log shows `You can now view your Streamlit app in your browser`, and the health URL answers `ok`. Open the app's URL and change the text box: the greeting should update. ## Common problems | Symptom | Fix | |---|---| | Log says `to view this Streamlit app on a browser, run it with the following command` | The Procfile is missing or has no `web:` line, so lizardpack ran `python app.py`. | | Deploy never becomes healthy | The service port and `--server.port` differ. Run `lizard port --service web`. See [service never healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md). | | `ModuleNotFoundError` | Add the package to `requirements.txt` and deploy again. | | `KeyError` for a secret | The variable is missing on the `web` service: `lizard secrets list --service web`. | | The page shows a connecting message and never loads | The browser cannot keep its WebSocket to `/_stcore/stream`. Look for that failed request in the browser console, and for errors in `lizard logs`. | ## Related - [Framework guides](https://lizard.build/docs/framework-guides.md) - [Deploy FastAPI](https://lizard.build/docs/framework-guides/fastapi.md) - [Runtime versions](https://lizard.build/docs/deploy/runtime-versions.md) - [Managed Postgres](https://lizard.build/docs/addons/postgres.md) - [Logs](https://lizard.build/docs/observability/logs.md) --- title: "Deploy a Go app" description: "Host a Go app on Lizard: lizardpack finds go.mod, builds a static binary with the Go version from the go line, and runs it on port 8080 with PORT set." url: https://lizard.build/docs/framework-guides/go/ updated: 2026-10-12 --- # Deploy a Go app To deploy a Go app on Lizard, upload or push a module with a `go.mod` file. lizardpack builds it with the Go version from the `go` line, compiles a static binary (`CGO_ENABLED=0`) and runs it on Alpine Linux. The service port is `8080`, and Lizard passes it to your process in `PORT`, so read `PORT` in your app and listen on all interfaces. **Test status:** the sample app below builds and runs locally with Go 1.24, and we checked its build plan with lizardpack, the build planner Lizard runs. We have not yet deployed it end to end with this guide. ## Before you start - A Go module with `go.mod` at the directory you deploy, and `go.sum` if it has dependencies. - Lizard CLI, signed in with `lizard login`, on Pro or the 7-day Pro trial (card required). See [pricing](https://lizard.build/pricing). ## A minimal app `go.mod`: ```text module example.com/hello go 1.24 ``` `main.go`: ```go package main import ( "fmt" "log" "net/http" "os" ) func main() { port := os.Getenv("PORT") if port == "" { port = "8080" } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello from Go on Lizard") }) http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "ok") }) log.Printf("listening on :%s", port) log.Fatal(http.ListenAndServe(":"+port, nil)) } ``` `":"+port` listens on every interface. An address such as `127.0.0.1:8080` only accepts connections from inside the container, and the deploy never becomes healthy. Check it locally: ```bash go run . curl http://localhost:8080/healthz ``` ## What lizardpack builds | Step | What happens | |---|---| | Detection | `go.mod`, `go.work` or any `.go` file. lizardpack checks Go before Node.js and Python. | | Go version | The `go` line in `go.mod`, major and minor only: `go 1.24.3` builds on `golang:1.24-alpine`. Without `go.mod`, Go 1.24. | | Build | `go mod download`, then `CGO_ENABLED=0 GOOS=linux go build -ldflags='-s -w' -o /app/server ` | | Main package | The root directory; `./cmd` if `cmd/main.go` exists; else the first `cmd//main.go` in alphabetical order | | Runtime | `alpine:3.19` with `ca-certificates` and `tzdata`. lizardpack copies the whole source directory to `/app` next to the binary, so relative paths such as `templates/` and `static/` still work. | | Start and port | `/app/server` on port `8080` | The generated Dockerfile: ```dockerfile FROM golang:1.24-alpine AS build WORKDIR /app COPY go.mod go.sum* ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags='-s -w' -o /app/server . FROM alpine:3.19 RUN apk add --no-cache ca-certificates tzdata WORKDIR /app COPY --from=build /app /app EXPOSE 8080 CMD ["/app/server"] ``` Leave the service's build and start commands empty to get this image. With either one set, Lizard writes a different Dockerfile from those commands and ignores the `go` line: a `go build …` command builds on Go 1.24. For other build steps, commit your own `Dockerfile`; see [Deploy a Docker container from a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md). ## Deploy From the module's root: ```bash lizard init --name go-app lizard add --service api lizard up --service api --port 8080 ``` `lizard up` uploads the directory, builds it on Lizard's build machines and prints the URL. You do not need Docker or Go on the machine that runs the command. For git push to deploy, [connect a GitHub repository](https://lizard.build/docs/deploy/github.md) with `lizard add -r your-org/your-app -n api`, then check that `lizard port --service api` prints `8080`. ## Frameworks - **net/http, chi, gorilla/mux:** pass `":" + os.Getenv("PORT")` to `http.ListenAndServe`, as above. - **Gin:** `r.Run()` with no address reads `PORT` and falls back to `:8080`. Set `GIN_MODE=release` on the service. - **Echo, Fiber:** pass the address yourself: `e.Start(":" + os.Getenv("PORT"))` or `app.Listen(":" + os.Getenv("PORT"))`. ## Variables and databases Set configuration on the service and read it with `os.Getenv`: ```bash lizard secrets set GIN_MODE=release API_KEY=change-me --service api ``` For a database, add [Managed Postgres](https://lizard.build/docs/addons/postgres.md) and pass its private URL with a [reference](https://lizard.build/docs/variables/references.md): ```bash lizard add postgres lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service api ``` Then open it with your driver, for example `pgxpool.New(ctx, os.Getenv("DATABASE_URL"))`. A variable change restarts the service without a rebuild. ## Check that it works ```bash lizard logs --build --service api lizard logs --service api --tail 100 lizard ps ``` Request `/healthz` on the URL that `lizard up` printed, then a route that does real work. The build log shows the `golang:` image the build used. ## Common problems | Symptom | Fix | |---|---| | `no Go files in /app` | The `main` package is not at the root or under `cmd/`. Move it, or add a Dockerfile. | | The wrong binary starts | With several `cmd//main.go`, lizardpack builds the first name in alphabetical order. Add a Dockerfile that builds the one you want. | | SQLite fails with `CGO_ENABLED=0` | The build turns cgo off, so `mattn/go-sqlite3` cannot work. Use a pure-Go driver, or [Managed Postgres](https://lizard.build/docs/addons/postgres.md): container files are lost on each deploy. | | Deploy never becomes healthy | The process must listen on `PORT` (or `8080`) on all interfaces. See [service never healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md). | | You need a newer Go | Change the `go` line, for example `go mod edit -go=1.25`, and deploy again. See [runtime versions](https://lizard.build/docs/deploy/runtime-versions.md). | A Go process that serves no HTTP, such as a queue consumer, runs as a [background worker](https://lizard.build/docs/deploy/workers.md) with `--port 0`. ## Related - [Framework guides](https://lizard.build/docs/framework-guides.md) - [Build pipeline](https://lizard.build/docs/concepts/build-pipeline.md) - [Variables and secrets](https://lizard.build/docs/variables.md) - [Networking and domains](https://lizard.build/docs/networking.md) --- title: "Deploy a Rails app" description: "Deploy Ruby on Rails to Lizard with the Dockerfile Rails generates: master key, Managed Postgres, Rails 8 cache, queue and cable databases, port, checks." url: https://lizard.build/docs/framework-guides/rails/ updated: 2026-10-12 --- # Deploy a Rails app Rails 7.1 and later generate a production `Dockerfile`, and Lizard builds it as written: the image installs gems, precompiles assets and runs `bin/rails db:prepare` each time the server starts. Set `RAILS_MASTER_KEY` and the database URLs, then run `lizard up --port 8080` for Rails 8 (with `HTTP_PORT=8080` for Thruster) or `lizard up --port 3000` for Rails 7.1 and 7.2. Without a Dockerfile, lizardpack builds a plain Ruby image on port `3000`. **Test status:** we checked the `Dockerfile`, `bin/docker-entrypoint`, `config/puma.rb` and `database.yml` that Rails generates, Lizard's build and port rules, and lizardpack's plan for a Rails app without a Dockerfile. We have not yet deployed a Rails app end to end with this guide. ## Before you start - A Rails 7.1 or later app that uses PostgreSQL (the `pg` gem) and still has the generated `Dockerfile` and `bin/docker-entrypoint`. An older app can get them from the [dockerfile-rails](https://github.com/fly-apps/dockerfile-rails) gem: `bundle add dockerfile-rails --group development`, then `bin/rails generate dockerfile`. - `config/master.key` on your computer, or the app's `SECRET_KEY_BASE`. - A `Gemfile.lock` that lists the `x86_64-linux` platform. Add it with `bundle lock --add-platform x86_64-linux` and commit the file. - Lizard CLI, signed in with `lizard login`, on Pro or the 7-day Pro trial (card required). See [pricing](https://lizard.build/pricing). ## How Lizard builds a Rails app | Your app has | Lizard builds | Service port | |---|---|---| | The Rails 8 `Dockerfile` (`EXPOSE 80`, `CMD ["./bin/thrust", "./bin/rails", "server"]`) | Your Dockerfile, as written | `8080`, with `HTTP_PORT=8080` | | The Rails 7.1 or 7.2 `Dockerfile` (`EXPOSE 3000`) | Your Dockerfile, as written | `3000` | | No Dockerfile | lizardpack's Ruby image; see [below](#without-a-dockerfile) | `3000` | Leave the service's build and start commands empty. With either one set, Lizard writes its own Dockerfile from the commands instead of using yours, and that image has Node.js, not Ruby. A start command also replaces the image's entrypoint, so `db:prepare` would not run. See the [build decision order](https://lizard.build/docs/concepts/build-pipeline.md#build-decision-order). Rails 8 starts Thruster, which listens on port `80` and runs Puma on port `3000` behind it. The image runs as a non-root user (`USER 1000:1000`), and a non-root process may lack permission to open a port below 1024. `HTTP_PORT=8080` moves Thruster to a port it can always open. ## Create the project and database From the app's root directory: ```bash lizard init --name rails-app lizard add --service web lizard add postgres ``` [Managed Postgres](https://lizard.build/docs/addons/postgres.md) connects as the `postgres` superuser, so `db:prepare` can create the databases Rails asks for. ## Set secrets and deploy (Rails 8) The production `database.yml` from Rails 8 defines four databases: `primary`, `cache`, `queue` and `cable`. `DATABASE_URL` replaces only `primary`. For the others, Rails reads `CACHE_DATABASE_URL`, `QUEUE_DATABASE_URL` and `CABLE_DATABASE_URL`. Point all four at the same Postgres with different database names: ```bash PG='postgresql://postgres:${{postgres.PGPASSWORD}}@${{postgres.LIZARD_PRIVATE_DOMAIN}}:5432' lizard secrets set \ RAILS_MASTER_KEY="$(cat config/master.key)" \ DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' \ CACHE_DATABASE_URL="$PG/rails_cache" \ QUEUE_DATABASE_URL="$PG/rails_queue" \ CABLE_DATABASE_URL="$PG/rails_cable" \ HTTP_PORT=8080 \ SOLID_QUEUE_IN_PUMA=true \ --service web lizard up --service web --port 8080 ``` - The single quotes keep `${{…}}` away from your shell. Lizard resolves these [references](https://lizard.build/docs/variables/references.md) at deploy time and uses the project's private network. - `SOLID_QUEUE_IN_PUMA=true` makes Puma run the Solid Queue worker, so Active Job jobs run inside the web service. Without it, this web service does not process queued jobs. - Use `SECRET_KEY_BASE` instead of `RAILS_MASTER_KEY` only if the app reads no other encrypted credentials. ## Set secrets and deploy (Rails 7.1 and 7.2) These versions use one database: ```bash lizard secrets set \ RAILS_MASTER_KEY="$(cat config/master.key)" \ DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' \ --service web lizard up --service web --port 3000 ``` ## What happens on each deploy `lizard up` uploads the directory. Inside a git repository it skips what `.gitignore` lists, and Rails' `.gitignore` and `.dockerignore` both exclude `config/master.key` and `.env*` files. The build precompiles assets with `SECRET_KEY_BASE_DUMMY=1`, because Lizard passes no variables to a Dockerfile build except `COMMIT_SHA`. At start, `bin/docker-entrypoint` runs `./bin/rails db:prepare`: it creates missing databases, loads the schema into new ones and runs pending migrations. It runs on every container start, including restarts. Keep migrations safe to run against both the old and the new code. For git push to deploy, push the app to GitHub and [connect the repository](https://lizard.build/docs/deploy/github.md) instead of running `lizard up`. Set the same secrets and port (`lizard port 8080 --service web`). The generated Dockerfile has a `bundle install` step, so Lizard uses it as written. ## Check that it works ```bash lizard logs --build --service web lizard logs --service web --tail 100 lizard domain --service web ``` Rails 7.1 and later answer `/up` with HTTP 200 once the app boots. Open `https:///up`, then a page that reads from the database. The runtime log should show `db:prepare` output, then Puma starting. ## Without a Dockerfile lizardpack builds a Ruby app when it finds a `Gemfile`: - Ruby version: `.ruby-version`, else the `ruby` line in `Gemfile`, else `3.4`. Only major and minor count: `3.4.1` builds on `ruby:3.4-slim`. - It installs `build-essential` and `libpq-dev`, then runs `bundle install`. - Start: the Procfile `web:` line, else `bundle exec rails server -b 0.0.0.0` when `config.ru` and `Rakefile` exist. Port `3000`. This image does not set `RAILS_ENV`, precompile assets or prepare the database, so it suits small apps and APIs better than a full Rails site. Write `.ruby-version` as a bare number such as `3.4.1`. `rails new` writes `ruby-3.4.1`, and lizardpack turns that into an image name that does not exist. See [runtime versions](https://lizard.build/docs/deploy/runtime-versions.md). ## Common problems | Symptom | Fix | |---|---| | `bundle install` fails: the bundle only supports other platforms | Run `bundle lock --add-platform x86_64-linux` and commit `Gemfile.lock`. | | `Missing secret_key_base` or credentials fail to decrypt | Set `RAILS_MASTER_KEY` from `config/master.key` on the `web` service. | | Cache, queue or cable connection goes to a local socket such as `/var/run/postgresql` | Set `CACHE_DATABASE_URL`, `QUEUE_DATABASE_URL` and `CABLE_DATABASE_URL`. | | Logs show `bind: permission denied` for port 80 | Set `HTTP_PORT=8080` and the service port to `8080`. | | Deploy never becomes healthy | Check that the service port matches the table above: `lizard port --service web`. See [service never healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md). | | Jobs stay queued | Set `SOLID_QUEUE_IN_PUMA=true`. To run jobs apart from the web process, add a second service in [worker mode](https://lizard.build/docs/deploy/workers.md) with `dockerfilePath=Dockerfile` and `startCommand=./bin/jobs`. | | Uploaded files vanish after a deploy | Active Storage's `local` service writes inside the container. Use [Managed Object Storage](https://lizard.build/docs/addons/storage.md), which is S3-compatible, with the `amazon` service. | If the app uses Sidekiq or a Redis Action Cable adapter, add [Managed Redis](https://lizard.build/docs/addons/redis.md) and set `REDIS_URL='${{redis.REDIS_PRIVATE_URL}}'` on each service that needs it. ## Related - [Deploy a Docker container from a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md) - [Variables and secrets](https://lizard.build/docs/variables.md) - [Managed Postgres](https://lizard.build/docs/addons/postgres.md) - [Build pipeline](https://lizard.build/docs/concepts/build-pipeline.md) - [Storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md) --- title: "Deploy Docusaurus on Lizard" description: "Deploy Docusaurus on Lizard as a static documentation site. Build the build directory, set URL and baseUrl, use port 80, and verify direct routes and real 404s." url: https://lizard.build/docs/framework-guides/docusaurus/ updated: 2026-09-09 --- # Deploy Docusaurus on Lizard Build Docusaurus on Lizard and serve its generated `build/` directory through nginx on port `80`. The production deployment serves static files; it does not run the `docusaurus start` development server. Start with the [complete source example](https://github.com/lizard-build/docs/tree/main/_examples/docusaurus), which includes the configuration and files used by this recipe. ## Prepare the site Run from the Docusaurus directory containing `package.json`, its lockfile, and `docusaurus.config.*`. Keep `@docusaurus/core` in the dependencies and a build script that runs `docusaurus build`. | Setting | Value | |---|---| | Build | `npm run build` | | Output | `build/` | | Runtime | nginx | | Service port | `80` | Set `url` in the Docusaurus config to the site's intended public origin and `baseUrl` to the path where it will run. For a site at the domain root, `baseUrl` is `/`. Once you know the final hostname, rebuild with that hostname so generated canonical URLs and sitemap entries use it. Choose a consistent trailing-slash policy and check the resulting files. Keep the default output directory unless you provide a Dockerfile that copies your custom directory. The [Docusaurus deployment guide](https://docusaurus.io/docs/deployment) explains these framework settings. ## Build and check locally ```bash npm ci npm run build npm run serve ``` The last command assumes your project has the scaffold's `serve` script. Check the home page, a nested documentation page, an image, and a versioned page if you use docs versions. Fix broken links reported by the build before deployment. ## Deploy After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project): ```bash lizard init --name docusaurus-docs lizard add --service web lizard up --service web --port 80 lizard logs --build --service web --json lizard ps --json ``` If you use the generated hostname, read it after the first deploy: ```bash lizard service show web --json ``` Set `url` in `docusaurus.config.js` to that hostname: ```js url: 'https://YOUR_PUBLIC_HOST', ``` Upload the changed config so the build uses the public URL: ```bash lizard up --service web --port 80 ``` Commit source, plugins, configuration, and the lockfile. Exclude `node_modules/`, `build/`, `.docusaurus/`, and `.env` files from uploads. Leave service command overrides unset to use the Docusaurus detection path. ## Serve inner routes and missing pages New Docusaurus builds serve generated route files and return HTTP 404 for unknown paths. A custom Dockerfile is not needed for this routing policy. If your service still uses an image built before the September 2026 routing fix, rebuild it. Use [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) only when you need custom nginx behavior. After deployment, request a nested docs URL directly, refresh it, and check a made-up URL's status. Also inspect the canonical URL and sitemap on the final hostname. A visible error page with HTTP 200 is still a soft 404. If assets are missing, compare their URLs with `baseUrl`. If an inner route returns the home page, inspect the generated file layout and nginx rules. If old content remains after editing an environment value or Markdown file, verify that a new build actually completed; static HTML changes only with a build. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy VitePress on Lizard" description: "Deploy VitePress on Lizard with docs:build, the .vitepress/dist directory, nginx and port 80. Configure clean URLs and verify documentation routes and 404s." url: https://lizard.build/docs/framework-guides/vitepress/ updated: 2026-10-12 --- # Deploy VitePress on Lizard Lizard can build a VitePress documentation site and serve the generated HTML through nginx on port `80`. Match the npm script and output directory to your docs root: `vitepress build docs` writes `docs/.vitepress/dist`, while `vitepress build` writes `.vitepress/dist`. The [complete source example](https://github.com/lizard-build/docs/tree/main/_examples/vitepress) includes the configuration and files used in this guide. ## Set the build script For Markdown files in `docs/`, keep these scripts in `package.json`: ```json { "scripts": { "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs" } } ``` | Setting | Value in this guide | |---|---| | Build | `npm run docs:build` | | Docs root | `docs/` | | Output | `docs/.vitepress/dist/` | | Production server | nginx | | Service port | `80` | The detector finds a script containing `vitepress build` and uses its root argument. Use a script that calls the build command directly. Shell wrappers, several matching scripts, or a custom `outDir` need explicit build configuration or a Dockerfile. ## Check the site locally ```bash npm ci npm run docs:build npm run docs:preview ``` Check an inner Markdown page and an asset. With `cleanUrls: true`, VitePress links to extensionless routes; the production server must resolve those URLs to the generated HTML files. Set `base` to the actual path prefix, or `/` for the domain root. See [VitePress deployment](https://vitepress.dev/guide/deploy). ## Deploy After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), run from the directory containing `package.json`, not from inside `docs/`: ```bash lizard init --name vitepress-docs lizard add --service web lizard up --service web --port 80 lizard logs --build --service web --json lizard ps --json ``` Include the VitePress config, Markdown, source assets, package manifest, and lockfile. Exclude local dependencies, generated output, caches, and secrets. Do not set `vitepress dev` or `vitepress preview` as the service start command. ## Check clean routes and HTTP status New VitePress builds resolve extensionless routes to their generated `.html` files and return HTTP 404 for missing URLs. Keep command overrides unset to use this detection path. No custom Dockerfile is needed for the standard output. See [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) for custom routing. Check a clean URL directly and reload it. Request a path that does not exist and confirm HTTP 404. If an older image serves the home page for missing paths, rebuild the service to pick up the current routing rules. If the build reports `Missing script: build`, check that the selected path uses the VitePress detector and that no service command override replaces it. If the build succeeds but nginx serves no docs, compare the script's docs root with the copied output directory. Rebuild after content or configuration changes. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Deploy Hugo on Lizard" description: "Deploy Hugo on Lizard by building public with hugo --minify and serving it on port 80. Check detection, themes, baseURL, routes and missing-page status." url: https://lizard.build/docs/framework-guides/hugo/ updated: 2026-10-12 --- # Deploy Hugo on Lizard Lizard can build a Hugo source site with `hugo --minify` and serve `public/` through nginx on port `80`. Start from the Hugo source directory, with a Hugo configuration and `content/`, rather than uploading only the generated HTML. The [complete source example](https://github.com/lizard-build/docs/tree/main/_examples/hugo) includes the configuration and files used in this guide. ## Prepare the source Include your Hugo config, content, layouts, assets, and all theme files needed by the build. The Hugo detector looks for configuration such as `hugo.toml`, `hugo.yaml`, or `config/_default/` plus a `content/` directory. | Setting | Value | |---|---| | Build | `hugo --minify` | | Output | `public/` | | Production server | nginx | | Service port | `80` | Set `baseURL` to the intended public site URL, including its trailing slash. Rebuild after assigning a different hostname so links and generated sitemap entries use it. Check [Hugo's build and output guide](https://gohugo.io/getting-started/usage/). ## Check build dependencies The default Hugo build image is `hugomods/hugo:base`; it does not pin a Hugo version for your project. If a theme needs a particular Hugo version, the extended edition, or Node tooling, use a complete Dockerfile with those dependencies and the version you tested. A Hugo config and `content/` select the Hugo builder before Go or Node detection. Hugo Modules projects with `go.mod` use a build image with Go support. A `package.json` causes the build to stop with a request for a Dockerfile: install the Node dependencies, build the assets, then run `hugo --minify`. See [build decision order](https://lizard.build/docs/concepts/build-pipeline.md#build-decision-order). ## Build locally and deploy ```bash hugo --minify hugo server ``` Inspect `public/` after the first command. Use the local server to check content and theme rendering; `hugo server` is not the production start command. After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), deploy the source directory: ```bash lizard init --name hugo-site lizard add --service web lizard up --service web --port 80 lizard logs --build --service web --json lizard ps --json ``` If you use the generated hostname, read it after the first deploy: ```bash lizard service show web --json ``` Set `baseURL` in `hugo.toml` to that hostname: ```toml baseURL = "https://YOUR_PUBLIC_HOST/" ``` Upload the changed config so the build uses the public URL: ```bash lizard up --service web --port 80 ``` Exclude local generated output and secrets. Include the theme's source files in the upload directory. A remote submodule reference does not include those files. ## Verify the deployed site Open the live home page, an inner article, and an image. Check the generated canonical URLs and `sitemap.xml`. Test a made-up path's HTTP status. New Hugo builds serve the generated HTML and return HTTP 404 for missing pages. No custom Dockerfile is needed for the simple source layout in this guide. Rebuild older images to pick up the current routing rules. Use [static routes and 404s](https://lizard.build/docs/framework-guides/static-routing.md) if you need a custom nginx policy. A custom Hugo build stage must include the theme's required Hugo version and tools, run `hugo --minify`, and copy `public/` into the serving image. If theme assets are missing, inspect the build logs, theme source, and `baseURL`. If the wrong builder starts, check that the Hugo config and `content/` are in the upload root and inspect existing service overrides. Hugo produces static files, so content changes need a new build. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Static routes and 404s" description: "Configure static site routes on Lizard: serve generated HTML, support clean URLs, return HTTP 404 for missing pages, and choose when an SPA fallback belongs." url: https://lizard.build/docs/framework-guides/static-routing/ updated: 2026-10-10 --- # Static routes and 404s A generated content site should serve each route's HTML and return HTTP 404 for a missing page. New lizardpack builds for Astro static, Docusaurus, VitePress, Hugo, and SvelteKit adapter-static use that policy. React and Vue SPAs keep the `index.html` fallback needed by browser routers. The custom configuration below is optional. Use it for a routing policy that the detected framework does not provide. Existing images keep their old rules until you rebuild them. ## Choose the routing policy | App type | Route behavior | |---|---| | React or Vue SPA | A valid client route loads `index.html`, then the client router renders it. | | Generated documentation or content | A route resolves to its generated HTML; an unknown path returns HTTP 404. | | Server-rendered app | The application server resolves routes and returns the status. | An SPA catch-all view can show a missing-page message, but browser code cannot change the HTTP status of the HTML response already sent. If you need route-aware HTTP status for an SPA, use a server or prerendered route setup that knows which paths exist. ## Configure nginx for generated pages Create `nginx.conf` at the application root: ```nginx server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri.html $uri/ =404; } error_page 404 /404.html; location = /404.html { internal; } } ``` This supports both `guide.html` and `guide/index.html` output layouts. Include a generated `404.html` if you want a custom error page. Otherwise omit the `error_page` and exact-location blocks to use nginx's default error body. Keep one canonical URL style in the site's links and sitemap; this lookup rule does not add canonical redirects. ## Build the site with the config Use this complete Dockerfile for an npm project with a committed lockfile. Change `BUILD_SCRIPT` and `OUTPUT_DIR` defaults to match the table below: ```dockerfile FROM node:22-slim AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . ARG BUILD_SCRIPT=build RUN npm run "$BUILD_SCRIPT" FROM nginx:alpine ARG OUTPUT_DIR=dist COPY --from=build /app/${OUTPUT_DIR}/ /usr/share/nginx/html/ COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 ``` | Site | `BUILD_SCRIPT` | `OUTPUT_DIR` | |---|---|---| | Astro static | `build` | `dist` | | Docusaurus | `build` | `build` | | VitePress with a `docs/` root | `docs:build` | `docs/.vitepress/dist` | | VitePress at the project root | `docs:build` or your actual script | `.vitepress/dist` | | SvelteKit with adapter-static | `build` | `build` | | Next.js export | `build` | `out` | | Nuxt generate | `build` with `nuxt generate` | `.output/public` | Use the generated-pages policy only when the app has those route files. A SvelteKit SPA fallback, for example, needs its own routing policy. [Next.js static export](https://lizard.build/docs/framework-guides/nextjs/static-export.md) has a complete recipe with a trailing-slash layout. Exclude dependencies, build output, `.git`, and `.env*` in `.dockerignore`. If the build needs public variables, write them as `ENV` lines before the build command. Lizard passes no build args except `COMMIT_SHA`, so an `ARG` stays empty. Do not copy private secrets into the image or browser output. After [CLI setup](https://lizard.build/docs/framework-guides.md#prepare-the-project), create a service with `lizard add --service web`, then deploy this source with `lizard up --service web --port 80`. Skip the add step when the service already exists. On an existing service, check [build decision order](https://lizard.build/docs/concepts/build-pipeline.md#build-decision-order): command overrides can take precedence over the Dockerfile. A config file by itself does not replace the generated nginx configuration; the Dockerfile must copy it into the image. ## Verify HTTP responses Set `SITE_URL` to the actual local test URL or deployed origin, then replace `/guide/` with a page that exists: ```bash SITE_URL=https://YOUR_PUBLIC_HOST curl -sS -o /dev/null -w '%{http_code}\n' "$SITE_URL/" curl -sS -o /dev/null -w '%{http_code}\n' "$SITE_URL/guide/" curl -sS -o /dev/null -w '%{http_code}\n' "$SITE_URL/this-page-does-not-exist" ``` Expect 200 for real pages and 404 for the missing path. If a valid route redirects to its canonical form, inspect the redirect and then verify the destination. Also test an actual asset and a made-up `.js` path: a missing script must not return the home page as HTML with status 200. Finally, open the inner route directly in a browser and reload it. Correct client navigation alone can hide a server routing error. Check the framework's canonical URL and sitemap settings alongside HTTP status before publishing an indexed site. See [tested versions and cloud results](https://lizard.build/docs/framework-guides/validation.md) for the September 9, 2026 deployment checks and their limits. --- title: "Framework guide test results" description: "Framework guide results from 16 real Lizard CLI deployments on September 9, 2026: tested versions, routes, forms, migrations, canonical URLs, and limits." url: https://lizard.build/docs/framework-guides/validation/ updated: 2026-09-09 --- # Framework guide test results On September 9, 2026, all 16 recipes across 11 frameworks passed a local build and a new cloud deployment with Lizard CLI 0.3.95. We followed the published setup, created a fresh project and service for each recipe, and uploaded source with `lizard up`. These results cover the versions and small test apps below. The cloud run passed 100 checks of routes, APIs, and forms, plus 45 checks of linked JavaScript and CSS files. Django migrations, a database write and read, and the final Docusaurus and Hugo site URLs also passed. ## Tested recipes | Recipe | Tested versions | Port | Cloud check | |---|---|---|---| | Next.js server | Next.js 16.3.4, React 19.2.8 | `3000` | Passed | | Next.js static export | Next.js 16.3.4, React 19.2.8 | `80` | Passed | | React with Vite | React 19.2.8, Vite 8.2.2 | `80` | Passed | | Vue with Vite | Vue 3.5.42, Vue Router 5.3.1, Vite 8.2.2 | `80` | Passed | | Astro static | Astro 7.3.1 | `80` | Passed | | Astro Node server | Astro 7.3.1, Node adapter 11.1.5 | `3000` | Passed | | Nuxt Node server | Nuxt 4.5.2 | `3000` | Passed | | Nuxt generate | Nuxt 4.5.2 | `80` | Passed | | SvelteKit Node server | SvelteKit 2.70.3, Svelte 5.57.0, adapter-node 5.5.7 | `3000` | Passed | | SvelteKit static | SvelteKit 2.70.3, Svelte 5.57.0, adapter-static 3.0.10 | `80` | Passed | | Docusaurus | Docusaurus 3.10.2 | `80` | Passed | | VitePress, docs root | VitePress 1.6.4 | `80` | Passed | | VitePress, project root | VitePress 1.6.4 | `80` | Passed | | FastAPI | FastAPI 0.141.1, Uvicorn 0.52.4 | `8000` | Passed | | Django | Django 6.1.1, Gunicorn 26.2.0, WhiteNoise 6.12.0, psycopg 3.3.5 | `8000` | Passed | | Hugo | Hugo 0.165.0 (extended) | `80` | Passed | JavaScript builds used Node 22; Python builds used Python 3.13. Uploads ran from macOS without `COPYFILE_DISABLE`. Package manifests and lockfiles pin the versions above. Container image tags can change independently of a package lockfile. ## What we checked on September 9 - Ran the documented local build and server commands in Linux containers, then ran `lizard init`, `lizard add`, and `lizard up` for each recipe. Read cloud build and runtime logs and checked the final service state and port. - Requested the public home page, an inner route, a real asset, a missing page, and a missing JavaScript path. Static content sites returned real 404s; React and Vue kept their documented SPA fallback. Both VitePress layouts returned the right content at clean URLs. - Checked request-time API responses, POST handlers, SvelteKit form results, and rejection of a form from an untrusted origin. Submitted the deployed Next.js Server Action form over HTTP using its generated action ID and checked the redirect and returned value. - Rebuilt Docusaurus and Hugo after setting the generated hostname in the config. Docusaurus canonical URLs and both sitemaps used the public host. - Ran Django migrations against Managed Postgres twice, wrote and read a test row, served collected static files, accepted a valid CSRF form, and rejected a form without its token. Each recipe used a separate test project, with different names to keep the run isolated. Workspace, region, and JSON flags made the test context explicit. Build, start, and Dockerfile-path service overrides stayed unset. Only the Next.js static export, Nuxt generate, and Django recipes supplied the Dockerfiles documented in their guides. The Docusaurus, VitePress, Hugo, and SvelteKit Node tests included the [published source examples](https://github.com/lizard-build/docs/tree/main/_examples). ## Browser checks The September 9 run confirmed a React button click in the browser. The browser connection then returned repeated timeouts during navigation and page reads, so this run does **not** claim a fresh browser pass for all 16 recipes. HTTP route and form checks passed independently. The September 7 run included direct-route reloads, React interaction, Vue Router navigation, Next.js Server Actions, and SvelteKit form submissions in a browser. Those remain results from that earlier run. ## Fixes since the first run The September 7 run found missing service-creation steps, a Nuxt static preset mismatch, static routing defects, macOS archive metadata, failed-build exit codes, and port-change errors. The guides now include `lizard add` and the corrected Nuxt static setup. The September 8 static-routing recheck passed without custom Dockerfiles for Astro static, SvelteKit static, Docusaurus, Hugo, and both VitePress layouts. New builds include those routing rules; existing images need a rebuild. Lizard CLI 0.3.95 includes the archive and failed-build exit-code fixes. The September 9 production checks also verified service port changes and preservation of an explicit port during upload and rebuild. See [known issues](https://lizard.build/docs/platform/known-issues.md) for the release notes and remaining limits. ## Limits of these results These checks cover source uploads and the documented modes. They do not establish support for every plugin, adapter, theme, framework version, GitHub deployment path, database recovery procedure, or multiple-replica workload. Django's small test app still reports security warnings from `check --deploy`; it is not a complete production security setup. These tests do not measure search rankings or AI citations. Check your app's own routes and data operations before release. --- title: "Guides" description: "Task guides for deploying from coding agents, hosting MCP servers, Discord and Telegram bots, and running Claude Code or OpenCode in a sandbox." url: https://lizard.build/docs/guides/ updated: 2026-10-12 --- # Guides Choose a task below for setup steps, checks and limits. Look up exact flag syntax in the command reference. | Task | Guide | |---|---| | Deploy an app written by Claude Code, Codex, or Cursor | [Deploy from a coding agent](https://lizard.build/docs/guides/deploy-from-coding-agent.md) | | Give an MCP client a remote HTTPS endpoint | [Host a remote MCP server](https://lizard.build/docs/guides/deploy-mcp-server.md) | | Keep one Telegram long-polling process running | [Run a Telegram bot](https://lizard.build/docs/guides/telegram-bot.md) | | Host a Discord bot that stays connected to the gateway | [Host a Discord bot](https://lizard.build/docs/guides/discord-bot.md) | | Consume queued jobs without an HTTP listener | [Background workers](https://lizard.build/docs/deploy/workers.md) | | Run code in a separate Linux guest | [Sandboxes quickstart](https://lizard.build/docs/sandboxes/quickstart.md) | | Let Claude Code work in its own sandbox | [Run Claude Code in a sandbox](https://lizard.build/docs/sandboxes/guides/claude-code.md) | | Run OpenCode, or its web UI, in a sandbox | [Run OpenCode in a sandbox](https://lizard.build/docs/sandboxes/guides/opencode.md) | | Open a dev server in a sandbox from your browser | [Preview URLs](https://lizard.build/docs/sandboxes/preview-urls.md) | | Collect website analytics with PostgreSQL | [Run Umami](https://lizard.build/docs/guides/umami.md) | | Keep Flowise flows and encrypted credentials in PostgreSQL | [Run Flowise](https://lizard.build/docs/guides/flowise.md) | | Reuse files in a later sandbox | [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md) | Check [limits](https://lizard.build/docs/platform/limits.md), [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md), and [known issues](https://lizard.build/docs/platform/known-issues.md) when choosing a resource. Read the [scenario test results](https://lizard.build/docs/guides/validation.md) for tested versions, cloud checks and remaining gaps. --- title: "Deploy from Claude Code, Codex or Cursor" description: "Deploy an app from Claude Code, Codex, or Cursor with Lizard Skill and Lizard CLI. Choose GitHub or local source, connect a database, and verify the result." url: https://lizard.build/docs/guides/deploy-from-coding-agent/ updated: 2026-10-12 --- # Deploy from Claude Code, Codex or Cursor Claude Code, Codex and Cursor can deploy the app they built to Lizard with Lizard Skill and Lizard CLI. The agent reads the installed CLI's guide, checks the target project, runs the build and checks the live URL. It needs no separate MCP transport. Use the prompt below to deploy local or GitHub source, then check the build, public URL and database connection. ## Before you start Have a working application, permission to deploy it, and a Lizard account on Pro or the 7-day Pro trial (card required). Until the trial starts, `lizard add` and `lizard up` return HTTP 402 with a Checkout link; `lizard billing start` opens it. The application must listen on `0.0.0.0` and the configured port. Run its local checks before starting a cloud build. ## Give the agent the current guide ```bash curl -fsSL https://lizard.build/install.sh | bash lizard skills get core --json lizard --help --json ``` On Windows, run `irm https://lizard.build/api/cli/install.ps1 | iex` in PowerShell, or install from npm with `npm install -g @lizard-build/cli`. To make the skill available to every agent on your machine, install it once with `npx skills add lizard-build/skill -g`; see [Coding agents](https://lizard.build/docs/agents.md). Lizard Skill loads instructions that match the installed CLI. For a specific command, read its schema rather than guessing flags: ```bash lizard up --help --json lizard service set --help --json ``` A useful prompt to paste into Claude Code, Codex or Cursor is: > Deploy this app with Lizard. Read the installed CLI guide, check the linked project and service, run the app's checks, and show the build result and a working URL. Ask before changing an existing production service or deleting data. ## Deploy local source For a complete test, use the [agent-app example](https://github.com/lizard-build/docs/tree/23635ba7e162bafce75d3f6206553b3ef2c0c48e/_examples/agent-app). It uses Node.js 22 and `pg` 8.16.3, listens on port 3000 and reads one row from Managed Postgres. Its startup creates the demo table and row if they do not exist. For a larger application, use your own migration process. Copy the example into its own directory and run its local checks: ```bash npm ci npm run check lizard status --json ``` This checks JavaScript syntax. The database and public HTTP checks come after deployment. If this directory is already linked, check that it is the intended project. For a new test project: ```bash lizard init --name agent-example --json lizard add --service api --json lizard add postgres --json lizard secrets set DATABASE_URL='${{postgres.DATABASE_URL}}' --service api --json lizard up --service api --port 3000 --json ``` Use the database's actual name if it is not `postgres`. Keep the reference quoted so the local shell does not expand it. Configure it before the first deployment. Sign in with `lizard login` only if a command reports that authentication is required. Use an upload for this copied example. For an application already in GitHub, use the next section instead. A failed build exits with code 1; still inspect the terminal `failed`/`deployed` event and verify the application. See [known issues](https://lizard.build/docs/platform/known-issues.md). ## Deploy from GitHub Use this path when the application has a GitHub repository. Check the remote first: ```bash git remote get-url origin lizard status --json lizard ps --json ``` Use a linked test project or create one with `lizard init --name YOUR_PROJECT_NAME`. Attach the repository without starting a build so there is time to configure its environment: ```bash lizard add --repo YOUR_ORG/YOUR_REPO --name api-git --no-deploy --json lizard secrets set DATABASE_URL='${{postgres.DATABASE_URL}}' --service api-git --json ``` Provision Managed Postgres first if this project does not have it. This sequence uses the same Node.js example as above. For a repository with that app in a subdirectory or on another branch, configure it before deployment: ```bash lizard service set api-git --set branch=YOUR_BRANCH --set rootDirectory=YOUR_APP_DIRECTORY --set containerPort=3000 --json lizard redeploy --service api-git --json ``` For an app at the repository root on `main`, omit the `service set` command. Set the correct port if your application uses another one. If the repository is not available to Lizard, connect the GitHub app; do not silently replace this path with an upload. For later updates to an existing GitHub service, use `lizard redeploy --service api-git` or push to its tracked branch when auto-deploy is enabled. `lizard up` switches a service to upload source. Changing build settings on an already running service can trigger its own build; inspect events before requesting another. ## Verify the result Read build and runtime logs separately: ```bash lizard logs --build --service api --json lizard logs --service api --json lizard ps --json ``` Use `api-git` instead of `api` for the GitHub path. Set `APP_URL` to the HTTPS URL reported by the CLI, then run: ```bash curl --fail "$APP_URL/health" curl --fail "$APP_URL/data" ``` The first response is `App ready`. The second is `{"value":"database-connected"}`. A build alone does not prove the database reference resolved. The example exposes only this fixed demo row, not a database administration API. For this test service, verify a runtime restart: ```bash lizard restart --service api --json ``` Wait for the service to return to `running` in `lizard ps --json` and repeat both HTTP requests. A runtime log tail can be empty; the HTTP response is the application check. Managed Postgres stores the row, so it survives the service restart. To check reuse of existing data, update the demo row to a distinct value in the database editor before the restart and confirm `/data` returns that value afterward. `logs --json` returns a log tail and exits. Read [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md) before changing real application data. ## When deployment fails Use the exit code and JSON error from the failed command. A compile error, a process that exits, and an unreachable port require different fixes. See [JSON and automation](https://lizard.build/docs/cli/json.md) and [service never healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md). `redeploy` is a new build, not a rollback to an older version. ## Limits and cost An agent can create billable resources through the same CLI as a person. Check [limits](https://lizard.build/docs/platform/limits.md) and [pricing](https://lizard.build/pricing), and scope credentials to the project and services it needs. To run the agent itself on a separate machine instead of your laptop, see [Claude Code in a sandbox](https://lizard.build/docs/sandboxes/guides/claude-code.md). See [scenario test results](https://lizard.build/docs/guides/validation.md) for checked versions, cloud results and remaining limits. --- title: "Host a remote MCP server" description: "Host a remote MCP server on Lizard with Streamable HTTP, bearer-token checks, a health endpoint, and a client that verifies a tool call." url: https://lizard.build/docs/guides/deploy-mcp-server/ updated: 2026-10-10 --- # Host a remote MCP server Deploy an MCP server as an HTTP application when clients need a remote URL. This example serves one arithmetic tool, validates a bearer token, and exposes a health endpoint. It uses Streamable HTTP; a local `stdio` server cannot serve remote clients by itself. ## Before you start You need Node.js 22, Lizard CLI, a project you can deploy to, and an MCP client that accepts a configured bearer token. The example uses `@modelcontextprotocol/sdk` 1.30.0 in stateless mode. It does not provide OAuth login, browser access, or an identity provider. The runnable files live in [remote-mcp-node](https://github.com/lizard-build/docs/tree/23635ba7e162bafce75d3f6206553b3ef2c0c48e/_examples/remote-mcp-node). Use the checked-in lockfile. The local smoke test checks initialization, tool discovery, a tool call, and rejection without a valid token. A deployment check must also confirm the public proxy and TLS path. ## Run locally From the example directory: ```bash npm ci npm test node issue-token.mjs export MCP_PUBLIC_KEY="$(cat .mcp-public-key.pem)" export MCP_ALLOWED_HOSTS=127.0.0.1,localhost npm start ``` The generated token expires after one hour. The private signing key is not saved. Use your own token issuer and rotation process for a lasting deployment; keep its private key outside the server. In another terminal: ```bash export MCP_URL=http://127.0.0.1:8000/mcp export MCP_TOKEN="$(cat .mcp-token)" node client.mjs ``` The client checks initialization, tool discovery and the result `5`, then prints `MCP initialize, tools/list and tools/call passed: 5`. `/health` returns `ok`; a request to `/mcp` without a valid token returns `401`. ## Deploy the application Keep the example's Dockerfile and lockfile. From the directory containing them: ```bash lizard init --name mcp-example lizard add --service mcp lizard domain --service mcp --json ``` Sign in with `lizard login` if a command reports that authentication is required. `init` links the project; `add` creates the named service. The domain command assigns its hostname before deployment. Use the returned `hostname` below, without `https://` or a path: ```bash lizard secrets set MCP_PUBLIC_KEY="$(cat .mcp-public-key.pem)" --service mcp lizard secrets set MCP_ALLOWED_HOSTS=YOUR_SERVICE_HOSTNAME --service mcp ``` Set both values before the first deployment. Then run: ```bash lizard up --service mcp --port 8000 lizard logs --build --service mcp --json lizard logs --service mcp --json lizard ps --json ``` Check the final deployment event and public endpoint, not only the exit code. `server.mjs` listens on `0.0.0.0` and reads `PORT`, with 8000 as its default. The deploy command sets the service port to 8000. Configure the public key as a multiline environment value; do not upload the private signing key or the token file. ## Verify the public endpoint Set `MCP_URL` to `https://YOUR_SERVICE_HOSTNAME/mcp`, keep a valid token in the client environment, and run `node client.mjs`. Verify all three results: 1. `/health` returns `200` over HTTPS. 2. `/mcp` rejects a client with no valid token. 3. The authenticated client lists tools and calls `add`, returning `5`. A healthy process alone does not prove the MCP handshake or streamed response works through the public proxy. ## Troubleshooting | Result | Check | |---|---| | Process exits during startup | `MCP_PUBLIC_KEY` must contain the public PEM key; `MCP_ALLOWED_HOSTS` must contain the service hostname. | | `401` | The token must use RS256, match issuer and audience `mcp-example`, and not have expired. | | `403` | Match the request hostname to `MCP_ALLOWED_HOSTS`. Browser origins are not enabled by this example. | | `405` with a valid token | This stateless MCP endpoint accepts protocol requests through POST. Use an MCP client. A browser GET without a token returns `401` first. | | Local test passes but remote call fails | Check port, HTTPS, response streaming, and proxy timeouts. | ## Limits and cost This example does not keep user sessions or durable files in server memory. Add a database for lasting application state and check access per user before exposing private tools. For clients that require OAuth discovery or interactive login, add a supported OAuth provider instead of distributing this test token. An HTTP process may remain active between calls. Check [pricing](https://lizard.build/pricing), [limits](https://lizard.build/docs/platform/limits.md), and measured CPU/memory; an empty request queue does not imply no charges. ## Next steps - [Environment references](https://lizard.build/docs/variables/references.md) - [Deployment recovery](https://lizard.build/docs/concepts/deployments.md) - [MCP TypeScript SDK server guide](https://ts.sdk.modelcontextprotocol.io/server) See [scenario test results](https://lizard.build/docs/guides/validation.md) for checked versions, cloud results and remaining limits. --- title: "Run a Telegram bot" description: "Run a Telegram long-polling bot as a Lizard worker. Configure its token, disable HTTP routing, keep one replica, and check messages and retries." url: https://lizard.build/docs/guides/telegram-bot/ updated: 2026-10-10 --- # Run a Telegram bot A long-polling bot is a background worker: it asks Telegram for updates and does not need a public HTTP endpoint. Run it with `containerPort=0` and one replica per bot token. **Test status:** seven local tests with mocked Telegram responses pass. We have not checked this guide with a real bot in the cloud. Use a separate test bot and complete the message and restart checks below before relying on it. See [scenario test results](https://lizard.build/docs/guides/validation.md). ## Before you start Create a bot with BotFather and keep its token private. You need Python 3.13, Lizard CLI, and a project you can deploy to. A bot using long polling must not have an active webhook; check its current configuration before changing how it receives updates. The [example files](https://github.com/lizard-build/docs/tree/23635ba7e162bafce75d3f6206553b3ef2c0c48e/_examples/telegram-worker) include `worker.py`, a Dockerfile, and local unit tests. The tests do not call Telegram. The echo handler stores its offset in memory; it is not an exactly-once processing system. ## Check locally From the example directory: ```bash python3 -m unittest -v ``` The checks cover text replies, ignored updates, empty polls, and failed sends. Run the bot locally with `TELEGRAM_BOT_TOKEN` set only if you intend to send replies. Stop that local process before starting the hosted worker. ## Deploy as a worker ```bash lizard init --name telegram-bot lizard add --service bot ``` Sign in with `lizard login` if a command reports that authentication is required. Set `TELEGRAM_BOT_TOKEN` on the **bot service** before deploying. You can use the dashboard's variables editor, or put the value in a local `.env` file and import it: ```bash lizard secrets import --service bot < .env lizard run --service bot -- python3 check_bot.py lizard up --service bot --port 0 ``` The preflight checks the token with `getMe` and refuses a bot with an active webhook. It does not remove or change that webhook. A second polling process can still conflict: stop any local copy before deployment. Inside a git repository, the example's `.gitignore` keeps `.env*` out of the upload. Outside git, the CLI uploads `.env`, so run `git init` in the example directory first; its `.dockerignore` keeps the file out of the image either way. Keep the token out of source files and screenshots. Read the final deployment event and logs even if the command exits successfully. For an existing service, set worker mode explicitly: ```bash lizard service set bot --set containerPort=0 ``` After changing worker mode on a running service, apply it with `lizard redeploy --service bot`. Check deployment events before requesting another build. Worker mode skips HTTP port checks and the load-balancer route; do not add a dummy web server just to make this process look like an HTTP app. ## Verify the result ```bash lizard ps --json lizard logs --service bot --json lizard events --json ``` The log should show `Telegram worker started`. Send a message to your bot and check for one echo reply. Then stop and restart the worker during an approved test window and confirm it reconnects. A worker that is marked running still needs this application-level check. ## Common failures | Symptom | Check | |---|---| | Process exits immediately | Set `TELEGRAM_BOT_TOKEN` on the consuming service. | | HTTP health check never passes | Worker mode must use `containerPort=0`. | | Updates stop or conflict errors appear | Only one polling process should use this token; check for a local process, another replica, or an active webhook. | | Repeated replies after a failure | The demo does not persist offsets or deduplicate update IDs. Add durable idempotency before handling irreversible actions. | | Frequent retries | Check network access, token validity, Telegram limits, and your handler. Do not print request URLs: they contain the token. | ## State and cost For durable bot state, connect [Managed Postgres](https://lizard.build/docs/addons/postgres.md). Store processed update IDs and make handlers safe to retry. Long polling can keep the worker active between messages; budget from [pricing](https://lizard.build/pricing) and observed resource use, not from message count alone. ## Related guides - [Background workers](https://lizard.build/docs/deploy/workers.md) - [Logs](https://lizard.build/docs/observability/logs.md) - [Limits](https://lizard.build/docs/platform/limits.md) - [Telegram getUpdates reference](https://core.telegram.org/bots/api#getupdates) See [scenario test results](https://lizard.build/docs/guides/validation.md) for checked versions, cloud results and remaining limits. --- title: "Host a Discord bot" description: "Host a Discord bot on Lizard as a background worker: a discord.js example, the bot token as a secret, worker mode with no port, one replica, logs." url: https://lizard.build/docs/guides/discord-bot/ updated: 2026-10-12 --- # Host a Discord bot To host a Discord bot on Lizard, run it as a background worker. A gateway bot opens an outbound connection to Discord and serves no HTTP, so deploy it with `lizard up --port 0` (`containerPort=0`) and keep one replica per bot token. Lizard detects a Node.js bot from `package.json` and starts it with `npm run start`; a Python bot needs a Procfile `web:` line. **Test status:** the example bot runs locally with discord.js 14.27.0 and stops with `TokenInvalid` when given a fake token. We checked its build plan with lizardpack, the build planner Lizard runs. We have not yet run this guide against Discord from Lizard; send a test message and check the logs before you rely on it. ## Before you start - **A bot token.** In the [Discord Developer Portal](https://discord.com/developers/applications), create an application, open **Bot** and press **Reset Token**. Keep the token private: anyone with it controls the bot. - **The Message Content intent.** The example reads `!ping`, so turn on **Message Content Intent** under **Bot → Privileged Gateway Intents**. A bot that only answers slash commands does not need it. - **The bot in a server.** Under **OAuth2 → URL Generator**, tick the `bot` scope and the **View Channels**, **Send Messages** and **Read Message History** permissions, then open the URL and pick a server. - **Lizard CLI and a plan.** Install the CLI and run `lizard login`. Services run on Lizard Pro or its 7-day trial (card required); see [pricing](https://lizard.build/pricing). ## Write the bot Create a folder with two files. `package.json`: ```json { "name": "discord-bot", "private": true, "type": "module", "scripts": { "start": "node bot.js" }, "dependencies": { "discord.js": "^14.27.0" } } ``` `bot.js`: ```js import { Client, Events, GatewayIntentBits } from "discord.js"; const token = process.env.DISCORD_TOKEN; if (!token) { console.error("DISCORD_TOKEN is not set"); process.exit(1); } const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ], }); client.once(Events.ClientReady, (ready) => { console.log(`Logged in as ${ready.user.tag}`); }); client.on(Events.MessageCreate, async (message) => { if (message.author.bot) return; if (message.content === "!ping") { await message.reply("pong"); } }); client.login(token); ``` Run `npm install` to create `package-lock.json`, and keep the lock file next to `package.json`: with it, Lizard installs with `npm ci`. Add a `.gitignore` with `node_modules` and `.env`. To try the bot on your computer, paste the token without saving it to a file, then start it: ```bash read -rs DISCORD_TOKEN && export DISCORD_TOKEN npm start ``` Type `!ping` in a channel the bot can see; it answers `pong`. Stop the local copy before you deploy. Two running copies both answer every message. ## Deploy as a worker From the bot's folder: ```bash lizard init --name discord-bot lizard add --service bot read -rs DISCORD_TOKEN lizard secrets set DISCORD_TOKEN="$DISCORD_TOKEN" --service bot lizard up --service bot --port 0 ``` - `lizard add --service bot` creates an empty service; `lizard up --service bot` uploads and deploys the code to it. - `lizard secrets set` stores the token on the **bot** service only. See [variables and secrets](https://lizard.build/docs/variables.md). - `--port 0` turns on [worker mode](https://lizard.build/docs/deploy/workers.md): Lizard sets no `PORT`, runs no port health check and adds no HTTP route. The service may still show an `onlizard.com` domain, but nothing answers there. Inside a git repository, `lizard up` skips what `.gitignore` lists. Outside git it uploads `.env` files, so keep the token out of files in this folder or run `git init` first. See [deploy from local code](https://lizard.build/docs/deploy/upload.md). To keep git push to deploy instead, push the folder to GitHub and [connect the repository](https://lizard.build/docs/deploy/github.md), then switch the service to worker mode: ```bash lizard port 0 --service bot lizard redeploy --service bot ``` ### One replica per token Each replica opens its own gateway session with the same token, so each one answers every message. Keep the bot service at one replica. When you deploy a new version of a worker, Lizard stops the old process before it starts the new one, so the bot will be briefly unavailable. A bot in more than 2,500 servers must use Discord's sharding; that is a code change, not more replicas of the same process. ## Python bots For discord.py, list it in `requirements.txt` and add a `Procfile`: ```text web: python bot.py ``` lizardpack reads only the `web:` line of a Procfile, even for a worker, and ignores a `worker:` line. Without a Procfile it runs `python main.py`, so a bot in `main.py` needs no Procfile. Deploy with the same commands and `--port 0`. To pick the Python version, see [runtime versions](https://lizard.build/docs/deploy/runtime-versions.md). ## Keep data in Managed Postgres Files written inside the container are lost on the next deploy. Keep settings, scores or queues in [Managed Postgres](https://lizard.build/docs/addons/postgres.md): ```bash lizard add postgres lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service bot ``` The single quotes stop your shell from reading `${{…}}`; Lizard resolves the [reference](https://lizard.build/docs/variables/references.md) when the bot starts, and the variable change restarts it. Read `process.env.DATABASE_URL` with a client such as `pg`. Make handlers safe to run twice: a restart can replay work that was in progress. ## Check that it works ```bash lizard port --service bot lizard logs --service bot --tail 50 lizard ps ``` `lizard port` prints `worker mode`. The log shows `Logged in as` and the bot's name. Then type `!ping` in your server and wait for one `pong`. A worker marked running still needs this check: the process can run while Discord rejects it. ## Common problems | Symptom | Fix | |---|---| | `An invalid token was provided`, then restarts | Reset the token in the Developer Portal and set `DISCORD_TOKEN` again on the bot service. | | `Privileged intent provided is not enabled or whitelisted` | Turn on **Message Content Intent**, or remove `GatewayIntentBits.MessageContent` from the code. | | Bot is online but ignores `!ping` | Message content arrives empty without the intent. Also check that the bot can view and send in that channel. | | Every message gets two replies | Another copy uses the token: a local process, a second replica, or another host. Check `lizard ps`. | | Deploy waits for a health check | The service is not in worker mode. Run `lizard port 0 --service bot`, then `lizard redeploy --service bot`. | | `DISCORD_TOKEN is not set` | The secret is on another service or the project. Run `lizard secrets list --service bot`. | A bot that only answers slash commands through an **Interactions Endpoint URL** is an HTTP service instead: deploy it with a real port and give Discord its HTTPS URL. ## Related - [Background workers](https://lizard.build/docs/deploy/workers.md) - [Run a Telegram bot](https://lizard.build/docs/guides/telegram-bot.md) - [Deploy a Node.js app](https://lizard.build/docs/framework-guides/nodejs.md) - [Logs](https://lizard.build/docs/observability/logs.md) - [Limits](https://lizard.build/docs/platform/limits.md) - [discord.js guide](https://discordjs.guide/) --- title: "Run Umami" description: "Deploy Umami with Managed Postgres, set its encryption secrets, and check analytics after a restart and redeploy." url: https://lizard.build/docs/guides/umami/ updated: 2026-10-10 --- # Run Umami This guide runs the official Umami Docker image on Lizard with a separate PostgreSQL database. It uses Lizard CLI to deploy a small local Dockerfile and expose Umami over HTTPS. ## Prerequisites - A Lizard account on Pro or the 7-day Pro trial (card required). Until the trial starts, `lizard add` and `lizard up` return a Checkout link; `lizard billing start` opens it. - OpenSSL on your computer to generate secrets. Install Lizard CLI and log in: ```bash curl -fsSL https://lizard.build/install.sh | bash lizard login ``` Complete the login in your browser before continuing. The commands below were tested with Lizard CLI 0.3.95 and Umami 3.3.1. ## Create the project and database Use a new directory for this deployment: ```bash mkdir umami-on-lizard cd umami-on-lizard lizard init --name umami-on-lizard lizard add postgres --name umami-db lizard add --service umami ``` If you belong to more than one workspace, pass `--workspace ` to `lizard init` to choose one. Wait for the database to reach `running` before deploying Umami. ## Set the image and secrets Create a file named `Dockerfile`: ```dockerfile FROM ghcr.io/umami-software/umami:3.3.1 EXPOSE 3000 ``` Tell Lizard to use this file as written: ```bash lizard service set umami --set dockerfilePath=Dockerfile ``` Connect the database and generate two separate secrets: ```bash lizard secrets set \ DATABASE_URL='${{umami-db.DATABASE_URL}}' \ APP_SECRET="$(openssl rand -hex 32)" \ TWO_FACTOR_ENCRYPTION_KEY="$(openssl rand -hex 32)" \ --service umami ``` Keep the single quotes around the database reference: Lizard resolves it when the service starts. The values belong to this service, so other apps in the project do not receive them. Save both generated secrets in your password manager. Set them once during setup; do not regenerate them when restarting or upgrading. `TWO_FACTOR_ENCRYPTION_KEY` is required for two-factor authentication. ## Deploy and sign in From the directory containing the Dockerfile, run: ```bash lizard up --service umami --port 3000 ``` Umami's image runs its database setup and migrations on startup. No separate migration command is needed for this image. Open the HTTPS URL returned by the deployment. For a fresh Umami 3.3.1 database, sign in with username `admin` and password `umami`, then immediately change the password in your profile before sharing the URL. Add a website and install its tracking script on a page you control. If the deployment fails, inspect its status and logs: ```bash lizard events --service umami lizard logs --build --service umami lizard logs --service umami ``` If the first startup times out, check `lizard events` for container readiness. Once the container has started, retry the upload with `lizard up --service umami --port 3000`. ## Check data persistence Visit the tracked page and confirm that Umami records a pageview. Restart the application: ```bash lizard restart --service umami ``` Wait for the service to run again. Confirm that your new password works and that the website and pageview remain. Then repeat the check after a redeploy: ```bash lizard redeploy --service umami ``` Umami stores accounts, website settings, and analytics in PostgreSQL. The application does not need a file volume for those records. Keep the database and the encryption secrets when replacing or upgrading the app. A restart check does not replace a tested database backup and restore plan. ## Update Umami Back up PostgreSQL and read the release notes before upgrading. Change the image tag in the local Dockerfile, then run `lizard up --service umami --port 3000` again. For this upload-based setup, `lizard redeploy` rebuilds the last uploaded files; it does not upload local edits. See [Lizard's PostgreSQL guide](https://lizard.build/docs/addons/postgres/) for database access and [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery/) for backup planning. --- title: "Run Flowise with PostgreSQL" description: "Deploy Flowise with PostgreSQL, preserve its credential encryption key, and check saved flows after restarts and upgrades." url: https://lizard.build/docs/guides/flowise/ updated: 2026-10-10 --- # Run Flowise with PostgreSQL This guide runs one Flowise service on Lizard, with PostgreSQL for flows, accounts, and encrypted credentials. It builds an exact npm version and sets the encryption key as a service secret. The basic setup covers flows that use APIs. Uploaded files and local vector stores need separate persistent storage; see [File storage](#file-storage) before using those features. ## Prerequisites - A Lizard account on Pro or the 7-day Pro trial (card required). Until the trial starts, `lizard add` and `lizard up` return a Checkout link; `lizard billing start` opens it. - OpenSSL on your computer. - Enough memory for your Flowise workload; the test service uses 4 GiB. Install Lizard CLI and finish the browser login: ```bash curl -fsSL https://lizard.build/install.sh | bash lizard login ``` ## Create the project ```bash mkdir flowise-on-lizard cd flowise-on-lizard lizard init --name flowise-on-lizard lizard add postgres --name flowise-db lizard add --service flowise ``` Use `--workspace ` with `lizard init` if you need to select a workspace. Wait until the database is running. ## Build a fixed Flowise version Create `Dockerfile` with the contents below. This follows the Flowise Docker build and fixes the npm package at `3.1.4`: ```dockerfile FROM node:24-alpine AS build RUN apk add --no-cache git python3 py3-pip py3-setuptools make g++ build-base cairo-dev pango-dev ENV PUPPETEER_SKIP_DOWNLOAD=true RUN npm install -g flowise@3.1.4 --legacy-peer-deps FROM node:24-alpine RUN apk add --no-cache chromium git python3 py3-pip py3-setuptools make g++ build-base cairo-dev pango-dev curl ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser COPY --from=build /usr/local/lib/node_modules /usr/local/lib/node_modules COPY --from=build /usr/local/bin /usr/local/bin RUN chown -R node:node /usr/local/lib/node_modules /usr/local/bin USER node EXPOSE 3000 ENTRYPOINT ["flowise", "start"] ``` `--legacy-peer-deps` avoids installing optional peer integrations, including an old native SQLite dependency. This recipe targets PostgreSQL and API-based flows; install and test extra dependencies separately for nodes that need them. Select the Dockerfile explicitly: ```bash lizard service set flowise --set dockerfilePath=Dockerfile ``` ## Configure PostgreSQL and encryption Reference the database variables on the Flowise service: ```bash lizard secrets set \ DATABASE_TYPE=postgres \ DATABASE_HOST='${{flowise-db.PGHOST}}' \ DATABASE_PORT='${{flowise-db.PGPORT}}' \ DATABASE_USER='${{flowise-db.PGUSER}}' \ DATABASE_PASSWORD='${{flowise-db.PGPASSWORD}}' \ DATABASE_NAME='${{flowise-db.PGDATABASE}}' \ --service flowise ``` Keep the single quotes so your shell does not expand the references. Lizard resolves them when the service starts. Generate the following secrets once during setup: ```bash lizard secrets set \ FLOWISE_SECRETKEY_OVERWRITE="$(openssl rand -hex 32)" \ JWT_AUTH_TOKEN_SECRET="$(openssl rand -hex 32)" \ JWT_REFRESH_TOKEN_SECRET="$(openssl rand -hex 32)" \ EXPRESS_SESSION_SECRET="$(openssl rand -hex 32)" \ TOKEN_HASH_SECRET="$(openssl rand -hex 32)" \ SECURE_COOKIES=true \ --service flowise ``` Keep a secure copy of these values. In particular, preserve `FLOWISE_SECRETKEY_OVERWRITE`: Flowise needs the same key to decrypt credentials already stored in PostgreSQL. Do not regenerate the secrets when restarting, redeploying, or upgrading. ## Deploy and create your account ```bash lizard up --service flowise --port 3000 ``` The first build installs Flowise and its dependencies, so it can take several minutes. Open the HTTPS URL returned by the deployment and create the first owner account before sharing the URL. Sign in to the editor. Set the public URL for links that Flowise generates, replacing the example with your actual HTTPS URL: ```bash lizard secrets set APP_URL=https://YOUR-SERVICE.onlizard.com --service flowise ``` This change restarts the service. Configure SMTP separately if you need password-reset or invitation emails. ## Verify persistence Create and save a flow. Add a test credential, then restart Flowise: ```bash lizard restart --service flowise ``` Wait until the service is running, sign in again, and check that the flow and credential remain. Run a flow that uses the credential to confirm that Flowise can still decrypt it. Repeat the check after redeploying: ```bash lizard redeploy --service flowise ``` Back up both PostgreSQL and the encryption key. A database backup without the key cannot recover saved credentials. ## File storage PostgreSQL does not store every file that Flowise writes. This setup does not mount a persistent file volume, so local uploads, local vector databases, and file logs can disappear when the container is replaced. Before using uploads or document workflows, configure a private S3 bucket using Flowise's [storage variables](https://docs.flowiseai.com/configuration/environment-variables). Set `STORAGE_TYPE=s3`, `S3_STORAGE_BUCKET_NAME`, `S3_STORAGE_ACCESS_KEY_ID`, `S3_STORAGE_SECRET_ACCESS_KEY`, and `S3_STORAGE_REGION`. For an S3-compatible provider, also set `S3_ENDPOINT_URL` and `S3_FORCE_PATH_STYLE=true`. Lizard's Managed Object Storage creates a private `default` bucket, which suits Flowise documents. Keep it private: making it public from the dashboard S3 browser exposes every file in it. The PostgreSQL setup above does not provision or test file storage, local vector stores, or queue workers. ## Troubleshooting and updates ```bash lizard events --service flowise lizard logs --build --service flowise lizard logs --service flowise ``` If a first deployment times out while the image is still being pulled, inspect `lizard events`. Once the container has started, retry `lizard up --service flowise --port 3000`, or run `lizard redeploy --service flowise` to rebuild the last upload, even after a failed first build. For updates, back up the database, read Flowise's release notes, change the npm version in the Dockerfile, and upload it again with `lizard up`. Verify the new version and run the persistence checks before relying on the update. --- title: "Scenario guide test results" description: "Checks for the MCP, coding-agent, Redis worker and Telegram guides: cloud results, restart behavior, tested versions and remaining limits." url: https://lizard.build/docs/guides/validation/ updated: 2026-09-07 --- # Scenario guide test results On 2026-09-07, we checked these guides in a separate test project with Lizard CLI 0.3.92. Application checks are separate from build status. The tests used the guide commands, with a distinct project name and the documentation test branch for the GitHub example. | Guide | Result | Checks | |---|---|---| | [Remote MCP](https://lizard.build/docs/guides/deploy-mcp-server.md) | Passed locally and in the cloud | Health 200; missing and invalid token 401; authenticated GET 405; rejected Origin 403; initialize, tools/list and tools/call; result `5` over public HTTPS | | [Coding agent: upload](https://lizard.build/docs/guides/deploy-from-coding-agent.md) | Passed | `lizard up`, health 200, database-backed response 200, unknown route 404; an updated Postgres row remained after service restart | | [Coding agent: GitHub](https://lizard.build/docs/guides/deploy-from-coding-agent.md) | Passed | Repository attached with `--no-deploy`; branch, root directory and environment configured before `redeploy`; the public app read the same existing Postgres row | | [Redis worker](https://lizard.build/docs/deploy/workers.md) | Passed | Port 0; enqueue and result; result retained after restart; new job after restart; an unfinished processing entry recovered on startup | | [Telegram bot](https://lizard.build/docs/guides/telegram-bot.md) | Local tests only | Seven mocked tests cover updates, failed sends, token checks and webhook refusal. A real token, chat and cloud echo test are still required | ## Versions The MCP container ran Node.js 22.23.2 with MCP TypeScript SDK 1.30.0. The coding-agent example uses `pg` 8.16.3 and connected to Managed Postgres running PostgreSQL 18.4. The worker container ran Python 3.13.15 with redis-py 6.4.0. Dependency lockfiles or exact requirements live with the examples. ## Scope The MCP check covers the example's stateless HTTP transport and fixed bearer token. It does not establish OAuth compatibility, browser support, long-duration streaming or load capacity. The token expires after one hour. The GitHub check used the documentation branch and `_examples/agent-app` directory. It tested an explicit redeploy; it did not test every private-repository permission setup or webhook event. The Redis recovery check seeded an unfinished processing entry before restarting a single consumer. It did not test Redis failure, multiple workers or exactly-once external effects. The database check establishes persistence across an application restart, not backup or disaster recovery. Runtime log tails were initially empty for the test services. A live worker log stream later delivered a newly processed job. Use the HTTP, MCP client and job-result checks as the completion criteria; an empty log tail alone does not establish either success or failure. The Telegram example is not marked cloud-verified. Its preflight reads `getMe` and `getWebhookInfo` without changing the bot's webhook. Use a separate test bot before enabling its polling loop. See [framework test results](https://lizard.build/docs/framework-guides/validation.md) for the separate 16-recipe framework batch, and [known issues](https://lizard.build/docs/platform/known-issues.md) for CLI archive and failed-build exit-code limits. --- title: "Billing: Pro plan, trial and usage rates" description: "Lizard Pro costs $19 a month with $19 in monthly credits. How the trial, pay as you go usage, limits, failed payments, cancelling and Enterprise work." url: https://lizard.build/docs/platform/billing/ updated: 2026-10-12 --- # Billing Lizard has one self-serve plan, Pro. It costs $19 a month and comes with $19 in monthly credits. The credits cover apps, Sandboxes, Managed Postgres, Managed Redis, Managed Object Storage, Persistent Volumes and outgoing traffic. Once you use your credits, you pay as you go at the published rates. Manage your plan on the **Billing** page in account settings or with [`lizard billing`](https://lizard.build/docs/cli/billing.md). The plan belongs to the workspace owner. Usage in every workspace you own counts toward your plan, and members add no seat fee. ## Pro | | Pro | | --- | --- | | Price | $19 a month | | Monthly credits | $19, for usage across the platform | | Usage above the credits | Pay as you go at the [resource rates](#resource-rates) | | Trial | 7 days with $5 in trial credits | | Payment | Card, billed monthly | Pro renews each month until you cancel. Unused monthly credits do not carry over to the next month. ## Monthly credits and overage Each month starts with $19 in credits, and usage draws from them first. Usage above $19 is overage, charged at the same rates. Lizard charges overage as it builds up, not in one bill at the end of the month. When unbilled overage reaches $20, Lizard charges your card. Each paid charge raises the next step: $100, then $200, $400 and $1,000, and $1,000 after that. The steps start again at $20 each month. When the month ends, any overage not yet charged goes on one final invoice. There is no spending cap. The Billing page shows how much of your $19 you have used this month, the overage so far and your next charge. ## Resource rates Prices are in US dollars. | Resource | Rate | | --- | ---: | | CPU | $0.000006948 per vCPU-second | | Memory | $0.000003474 per GB-second | | Persistent Volumes | $0.000000054 per GB-second | | Managed Object Storage | $0.0135 per GB-month | | Outgoing traffic | $0.045 per GB, from the first byte | Incoming traffic is free. Apps, Managed Postgres and Managed Redis use these rates. Sandboxes have a flat hourly price for each size; see [Sandbox sizes](https://lizard.build/docs/platform/limits.md#sandbox-sizes). For example, one vCPU and 1 GB of memory used for one hour cost $0.0375192 in compute. Storage and outgoing traffic can add to that. See [pricing](https://lizard.build/pricing) for all rates and plan terms. ## Trial You can browse with a new account, but you need Pro to create apps, Sandboxes, databases or volumes. Pro starts with a 7-day trial and $5 in trial credits. You add a card to start it; Apple Pay works too. Nothing is charged when the trial starts. When it ends, Pro begins at $19 a month unless you cancel first. Each account and each card gets one trial. If the account had one before, Pro starts at once and charges $19 that day. If the card had one, the trial does not start and nothing is charged; start Pro at $19 on the Billing page. The trial includes every Pro feature, with lower [limits](#limits) until your first paid invoice. ### When trial credits run out - When you use all your trial credits, Lizard pauses running resources and blocks new ones. It keeps their data. - To go on, choose **Start Pro now** on the Billing page or run `lizard billing start-now`. This ends the trial, charges $19 and starts your first paid month with $19 in credits. Paused apps and databases start again once the payment goes through. Unused trial credits end with the trial. They do not move to the paid month. ## Promo codes A promo code can give a longer trial, up to 730 days, and more trial credits. It replaces the standard 7 days and $5. Redeem a code on the Billing page or with `lizard billing promo `. - Before the trial starts, Lizard saves the code and applies it when you add your card. - During the trial, a code raises its length and credits to what the code gives. It does not add to what you already have. - Codes work only before your first payment. - Each account and each card can use one code. ## Limits | | Trial, until the first paid invoice | Pro | | --- | ---: | ---: | | Sandboxes running at once | 5 | 100 | | Replicas per app | 1 | 5 | Pro limits apply from your first paid invoice, including a **Start Pro now** payment. See [Limits](https://lizard.build/docs/platform/limits.md#sandbox-account-limits) for Sandbox start rates and other limits. ## Payment failures If a charge fails, whether the monthly $19 or an overage charge: - You cannot create new resources from the moment the charge fails. - Running resources keep running for 72 hours, then pause. - Paused resources keep their data. Pay the open invoice on the Billing page. Once it is paid, Lizard lifts the block and starts paused apps and databases again. Resume paused Sandboxes yourself. Stripe retries a failed charge; if the last retry fails, Pro ends. ## Cancelling Cancel at any time on the Billing page, in the Stripe customer portal, or with `lizard billing cancel`. Pro stays active until the end of the current month or trial. Overage up to that date goes on a final invoice. Cancelling during the trial costs nothing. Until the end date, you can undo the cancel on the Billing page or with `lizard billing resume`. When Pro ends, Lizard blocks new resources and pauses running ones. It keeps their data. To use them again, start Pro on the Billing page. A second trial is not available, so Pro starts at once at $19. ## Enterprise Enterprise accounts pay as you go and get an invoice each month for the month before. Rates, limits and payment terms follow your contract. If you need more than Pro allows, ask us about Enterprise on the [pricing](https://lizard.build/pricing) page. ## Idle apps and stored data An app can use memory and CPU even when it has no requests. Stop the app to stop compute usage. You pay for Persistent Volumes and Managed Object Storage while you keep data in them, even when the app is stopped. See [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md) before removing data. Read [metrics](https://lizard.build/docs/observability/metrics.md) to compare configured limits with measured use. --- title: "Limits: app replicas, sandbox sizes and quotas" description: "App replica limits, sandbox sizes and hourly prices, how many sandboxes can run at once, start rates, egress, lifetimes and regions on Lizard." url: https://lizard.build/docs/platform/limits/ updated: 2026-10-12 --- # Limits On Lizard, an app runs up to 5 replicas per service after the first paid Pro invoice, and 1 during the trial. Pro runs up to 100 sandboxes at once and starts up to 24 a minute; the trial allows 5 at once. Sandbox sizes run from Small (2 vCPU / 4 GB) to Large (8 vCPU / 16 GB), and sandbox egress is included up to 2 TB per account over a rolling 30 days. Limits depend on the resource, plan, and account: a per-replica setting is not the total available to a service or an account. Check your plan and the result of the create or scale request before choosing resources. ## Applications | Setting | Scope | What to check | |---|---|---| | CPU and memory | Each replica | Three replicas with a 2 vCPU / 2 GiB ceiling have a combined configured ceiling of 6 vCPU / 6 GiB. This is not a usage forecast. | | Replica count | Service and plan | Your plan sets the limit: 1 per app during the Pro trial, 5 on Pro after the first paid invoice and on Enterprise. The API accepts at most 10, which only an account override can reach. | | Account resource cap | Account owner | A second project does not necessarily create a second quota. Ask for the effective account cap before planning a large deployment. | | Worker mode | Service | `containerPort=0` runs without an HTTP listener or public load-balancer route. | | Custom domain | Hostname | Wildcard custom domains are not accepted. Use a specific hostname and complete ownership and DNS checks. | ## Sandboxes [Sandboxes](https://lizard.build/docs/sandboxes.md) run short-lived commands and agent tasks as Firecracker microVMs. The limits below apply to every sandbox an account runs. ### Sizes | Size | CPU and memory | Disk (`/workspace`) | Price while running | |---|---|---|---| | Small | 2 vCPU / 4 GB | 5 GB | $0.0162 per hour | | Medium (default) | 4 vCPU / 8 GB | 20 GB | $0.0324 per hour | | Large | 8 vCPU / 16 GB | 50 GB | $0.0648 per hour | CPU and memory are included in the price. Persistent Volumes are billed separately. ### Account limits These limits cap how many sandboxes an account runs at once and how fast it starts new ones. They follow the account owner's plan. Trial limits apply until the first paid invoice; see [Billing](https://lizard.build/docs/platform/billing.md#limits). | Plan | Running at once | Starts per minute | Per hour | Per day | |---|---|---|---|---| | Pro trial | 5 | 6 | 30 | 100 | | Pro | 100 | 24 | 120 | 400 | | Enterprise | Custom | Custom | Custom | Custom | - Lizard counts limits per account owner, across all of the owner's workspaces. A second workspace does not add a second allowance. - Running means running or resuming. A paused sandbox does not count; resuming one needs a free slot. - A start is a new sandbox. A resume is not a start. A create that fails on Lizard's side does not count. - Over a limit, create and resume return HTTP 429 with `code` set to `SANDBOX_LIMIT_REACHED` (running) or `SANDBOX_RATE_LIMITED` (starts). A start-rate refusal includes a `Retry-After` header. - The dashboard shows how many sandboxes are running against your limit next to **New Sandbox**. For more than 100 at once, contact us about Enterprise. ### Network egress Sandbox egress is included up to 2 TB per account over a rolling 30 days, counted across all of the account's sandboxes. Past that, the account's sandboxes lose outbound network until the 30-day total falls back under 2 TB. Nothing extra is charged. ### Lifetime | Setting | Current create API | |---|---| | Templates | `base`, `interpreter`, `codex`, `desktop`, `opencode`, `pi`, `prime`, `claude` | | Raw API default lifetime | `timeoutMs: 0`, no expiration | | Lizard SDK default lifetime | 300000 ms, five minutes | | Explicit lifetime range | Integer milliseconds from 0 to 2147483647 | | Updating a lifetime | At least 1000 ms; it is not an idle timer | | Persistent volume attachment | One sandbox at a time, on the volume's node | The CLI also defaults to five minutes; the dashboard defaults to no expiration. Commands do not reset the deadline. For pause, snapshots, fork and what each one keeps, see the sandbox [lifecycle](https://lizard.build/docs/sandboxes.md#lifecycle). ## Regions The current configured regions include US East (Virginia), `us-east-1`, and EU West (Limburg), `eu-west-lim-a`. A region in the catalog does not guarantee available capacity for every resource. Check region choices when creating the resource; a sandbox using an existing volume must run on that volume's node. [`lizard regions`](https://lizard.build/docs/cli/regions.md) lists the regions open to your account; see [regions](https://lizard.build/docs/deploy/regions.md). ## Before increasing scale Check the active plan, per-replica settings, account cap, database connection limit, and storage growth together. Use [scaling](https://lizard.build/docs/deploy/scaling.md), [metrics](https://lizard.build/docs/observability/metrics.md), and [pricing](https://lizard.build/pricing) to compare configured capacity with measured usage. Rates for every resource are on the [billing page](https://lizard.build/docs/platform/billing.md#resource-rates). --- title: "Storage and recovery" description: "Choose storage for application data and sandbox files. Understand attachments, host failure, exports, and recovery before deleting or replacing a resource." url: https://lizard.build/docs/platform/storage-and-recovery/ updated: 2026-10-12 --- # Storage and recovery Choose storage based on whether your data must survive a process restart, a new deployment, or a host failure. Each requires different protection. Persistent storage does not by itself provide a backup, a restore point, or high availability. | Data | Where to keep it | Boundary | |---|---|---| | Application records | Managed Postgres or another database | A restart is different from restoring deleted or corrupt data. Test a separate export and restore path. | | Cache and queue data | Managed Redis | Decide whether the workload can rebuild its state or needs its own recovery plan. Managed Redis has no eviction policy and no append-only file: if it runs out of memory, it restarts and recent writes can be lost. Keep data you cannot lose in Managed Postgres. | | Files shared by applications | Managed Object Storage | The automatically created `default` bucket is private. Make a bucket public from the bucket browser in the dashboard only if anyone may read its files. | Sandbox files follow other rules: only files on an attached Persistent Volume at `/workspace` outlive a sandbox. See [storage and billing](https://lizard.build/docs/sandboxes.md#storage-and-billing). ## Keep files after a sandbox ends The Persistent Volumes tab manages named volumes and their attachments. Attach a volume when creating the sandbox and write to `/workspace`. Stop the current sandbox before attaching the volume to another. [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md) ## Plan a database restore Before a migration or release that changes data: 1. Choose an export method for the database and version in use. 2. Store the export separately from the resource being changed. 3. Restore into a separate test database and verify the application can read it. 4. Record the restore duration and the latest data included in the export. Do not assume scheduled backups, point-in-time recovery, multi-node replication, or a restore-time guarantee from a database being managed. Confirm the current service agreement and available controls for your resource. ## Environment references An application should read its database connection from an environment reference. A connection string is a credential; avoid printing it to verify an update. A connection check such as `SELECT 1` proves more than seeing the variable in a shell. ## Related guides - [Managed Postgres](https://lizard.build/docs/addons/postgres.md) - [Managed Redis](https://lizard.build/docs/addons/redis.md) - [Managed Object Storage](https://lizard.build/docs/addons/storage.md) - [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md) - [Deployment recovery](https://lizard.build/docs/concepts/deployments.md#failed-releases-and-recovery) --- title: "Known issues" description: "Current Sandboxes lifecycle caveats, deployment recovery limits, and build-setting precedence to check before relying on a workflow." url: https://lizard.build/docs/platform/known-issues/ updated: 2026-10-12 --- # Known issues Check these limits before using Lizard: sandbox pause and expiration, redeploys that are not rollbacks, Dockerfile precedence, and fixed issues with the date they were verified. A fix under review may not yet apply to a running service. ## Sandbox pause and expiration `connect` does not resume a paused or stopped sandbox, and fork returns HTTP 409 when a volume is attached and HTTP 501 for a pod sandbox; see the sandbox [lifecycle](https://lizard.build/docs/sandboxes.md#lifecycle). Pausing closes open network connections. If the processes cannot be saved, the sandbox keeps running and reports the reason in `pauseError`. A paused sandbox resumes only on its own node; if that node fails, the paused state is lost. After a sandbox stops, only files on an attached Persistent Volume at `/workspace` remain, and volumes use node-local storage; export files that must survive node loss. See [storage and billing](https://lizard.build/docs/sandboxes.md#storage-and-billing). ## Redeploy is not rollback `lizard redeploy` builds the selected source again with the current configuration. It does not select a previous build. A failed build and a failed start have different effects; inspect the active service and its logs before choosing a recovery step. Do not assume every runtime keeps the old release serving through a failed start. ## Dockerfile precedence Explicit build or start commands take priority over a repository Dockerfile. Clear those overrides when selecting `dockerfilePath`. A settings update can itself start a build, so check events before sending another redeploy. See [Dockerfile troubleshooting](https://lizard.build/docs/deploy/troubleshooting/incomplete-dockerfile.md). ## Templates and MCP Template availability depends on the region and pool capacity, and the public CLI does not provide a custom-template upload flow. Check tool versions inside your sandbox. The catalog is in [runtime and templates](https://lizard.build/docs/sandboxes.md#runtime-and-templates). Lizard Skill uses Lizard CLI and does not expose an MCP transport. For MCP clients, use the hosted Lizard MCP server; see [Lizard MCP](https://lizard.build/docs/agents.md#lizard-mcp). Hosting your own remote MCP server is a separate application deployment; see the [guide](https://lizard.build/docs/guides/deploy-mcp-server.md). ## Service port changes: fixed and verified on 2026-09-09 The September 7 check found HTTP 503 after changing an upload service from port `3000` to `80`, despite a ready process. The production fix now applies the chosen port to the running service and preserves an explicit port during upload and rebuild. A live port-change check passed on September 9. Check the public URL after a deployment as well as the service state. ## Upload archives and failed-build exit codes: fixed Lizard CLI 0.3.95 excludes macOS `._*` metadata entries from source uploads and returns a nonzero exit code for failed cloud builds. Update older CLI versions; `COPYFILE_DISABLE` is no longer needed. The [framework checks](https://lizard.build/docs/framework-guides/validation.md) cover uploads from macOS with the current CLI. A successful build alone does not prove the app works. Check the public URL, routes, forms, and data operations after deployment. --- title: "Concepts: how Lizard builds and runs apps" description: "The core model behind Lizard: how your code is organized, how it is built, and how a release reaches production." url: https://lizard.build/docs/concepts/ updated: 2026-10-12 --- # Concepts These pages explain how Lizard groups your apps, builds them, and puts each release into production. For a one-paragraph summary of what Lizard is, see the [docs home](https://lizard.build/docs/index.md). To compare platforms that run containers with PaaS, FaaS and IaaS, read [container as a service](https://lizard.build/blog/container-as-a-service) on the blog. ## In this section - [Architecture](https://lizard.build/docs/concepts/architecture.md): the workspace → project → service hierarchy, managed addons, and cross-resource references. - [Build Pipeline](https://lizard.build/docs/concepts/build-pipeline.md): how source becomes a container image (synthesized Dockerfile, repo Dockerfile, or lizardpack auto-detect) and what triggers a rebuild. - [Deployments](https://lizard.build/docs/concepts/deployments.md): the build-and-release lifecycle, restarts vs. redeploys, and live config changes. --- title: "Architecture: workspaces, projects and services" description: "How Lizard organizes work into workspaces, projects, and services, plus managed addons, cross-resource references, and injected variables." url: https://lizard.build/docs/concepts/architecture/ updated: 2026-10-12 --- # Architecture Lizard groups services into projects and projects into workspaces. Managed addons belong to a project. ``` workspace └── project ├── service (app) ← built from GitHub or an upload ├── service (worker) ← background job, no HTTP port └── addons ├── postgres ├── redis └── s3 ``` ## Workspace A **workspace** is the account or organization level. You belong to at least one, and billing, members, and projects live under it. List the ones you can access: ```bash lizard workspace list lizard whoami # shows your active workspace ``` Many commands accept `-w, --workspace ` to disambiguate when the same project name exists in more than one workspace. ## Project A **project** groups related services and addons inside a workspace. Your working directory gets *linked* to a project, and that link (stored in `~/.lizard/config.json`) is how the CLI knows what you're targeting. ```bash lizard init --name my-project # create or select a project, link the cwd lizard link --project my-project # link to an existing project lizard status # show the current directory's link lizard unlink # remove the link lizard project list # all projects in the workspace ``` > **Tip:** Use the repo or directory name for the project, and app-style names (`api`, `worker`, `web`) for services. ## Service A **service** is a single deployable unit. Its source is one of: - **`github`**: a connected GitHub repo. Pushes to the tracked branch auto-redeploy. - **`upload`**: a tarball uploaded with `lizard up`. Each running service runs as its own **isolated pod** with a default-deny network policy, a generated `-.onlizard.com` domain (older services keep `..onlizard.com`), and automatic TLS. This is [container as a service](https://lizard.build/blog/container-as-a-service): you supply the container and Lizard runs it. The blog explains which tasks remain yours. Inspect and manage services with: ```bash lizard ps # list services with status + URL lizard service show # full config as JSON lizard service set --set = lizard service rename --service lizard service delete --service ``` Service configuration fields are **flat** and map 1:1 to the wire schema (e.g. `repoUrl`, `branch`, `buildCommand`, `startCommand`, `containerPort`, `rootDirectory`). There is no `build.*` / `deploy.*` nesting. See [`lizard service`](https://lizard.build/docs/cli/service.md) for the full list. ### App vs. worker services By default a service is an **HTTP app** that listens on a port (default `3000`) and is served at its domain. A service that doesn't listen on a port, such as a queue consumer, reconciler, or polling loop, should run in [**worker mode**](https://lizard.build/docs/deploy/workers.md) by setting `containerPort=0`. Workers skip port injection, the reachability health check, and load-balancer registration. ## Managed addons **Addons** are Managed Postgres, Managed Redis, and Managed Object Storage (S3-compatible) that you provision per project: ```bash lizard add postgres redis s3 ``` Each addon exposes a fixed set of environment variables that your services consume by **reference** (`${{.KEY}}`). See [Managed Addons](https://lizard.build/docs/addons.md). ## Cross-resource references To read a service or addon value from another resource's environment, use: ``` ${{.}} ``` References resolve at **deploy time** against the target's merged environment. They match the target's **current name**: if you rename a service or addon, update every reference that uses the old name. A reference to a missing target or key resolves to an empty string (it does **not** fail the deploy); only circular references throw. ```bash # Wire a service to the project's Postgres lizard secrets set DATABASE_URL='${{postgres.DATABASE_URL}}' --service api ``` Check that the reference resolved. The build log prints a warning for every reference that resolved to an empty value, and `secrets list` shows what is stored: ```bash lizard logs --build --service api lizard secrets list --service api --show ``` ## Platform-injected variables Lizard sets these variables on every service; you cannot override them: | Variable | Description | |----------|-------------| | `PORT` | Port your app should listen on (omitted in worker mode) | | `LIZARD_SERVICE_NAME` | The service's name | | `LIZARD_PROJECT_ID` | The project ID | | `LIZARD_PUBLIC_DOMAIN` | The service's public domain (set once a domain is assigned) | | `LIZARD_PRIVATE_DOMAIN` | The service's private hostname, `.lizard.internal`, reachable from the same project | See [Variables & Secrets](https://lizard.build/docs/variables.md) for how these interact with your own variables and the full precedence order. --- title: "Build Pipeline" description: "How Lizard turns source into a container image: synthesized Dockerfile, repo Dockerfile, or lizardpack auto-detect, and what triggers a rebuild." url: https://lizard.build/docs/concepts/build-pipeline/ updated: 2026-10-12 --- # Build Pipeline Lizard builds a container image from your source on its build nodes, then runs the image in an isolated pod. You do not need Docker locally. The rules below determine how Lizard builds the image. For a comparison of hosts that build images and hosts that require an image, see [container as a service](https://lizard.build/blog/container-as-a-service). ## Build decision order The platform picks exactly one build strategy, in this order: 1. **Repo Dockerfile at `dockerfilePath`**: if `dockerfilePath` is set on the service, that Dockerfile is used unchanged, even when build or start commands are also set. A set `startCommand` still replaces the Dockerfile's `ENTRYPOINT` and `CMD` at run time. See [Deploy with a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md). 2. **Synthesized Dockerfile**: otherwise, if `buildCommand` and/or `startCommand` are set on the service (or passed via `lizard up --build-command` / `--start-command`), Lizard generates a Dockerfile from those commands. lizardpack is not invoked. 3. **Dockerfile in the source**: otherwise a `Dockerfile` at the root of the build directory is used (see the GitHub exception below). 4. **lizardpack auto-detect**: otherwise the platform runs **lizardpack**, its buildpack/Dockerfile generator. ### lizardpack auto-detect lizardpack inspects your repo and builds a multi-stage image. Supported stacks, matched in this order: **Hugo → Go → Node → Python → Rust → Ruby → PHP → Java → static** See [Framework guides](https://lizard.build/docs/framework-guides.md) for production scripts, adapters, output paths, and ports: [Hugo](https://lizard.build/docs/framework-guides/hugo.md), [Go](https://lizard.build/docs/framework-guides/go.md), Node ([Node.js](https://lizard.build/docs/framework-guides/nodejs.md), [Next.js](https://lizard.build/docs/framework-guides/nextjs.md), [Nuxt](https://lizard.build/docs/framework-guides/nuxt.md), [SvelteKit](https://lizard.build/docs/framework-guides/sveltekit.md), [Astro](https://lizard.build/docs/framework-guides/astro.md), [React](https://lizard.build/docs/framework-guides/react.md), [Vue](https://lizard.build/docs/framework-guides/vue.md), [Docusaurus](https://lizard.build/docs/framework-guides/docusaurus.md), [VitePress](https://lizard.build/docs/framework-guides/vitepress.md)), Python ([Django](https://lizard.build/docs/framework-guides/django.md), [FastAPI](https://lizard.build/docs/framework-guides/fastapi.md), [Streamlit](https://lizard.build/docs/framework-guides/streamlit.md)) and Ruby ([Rails](https://lizard.build/docs/framework-guides/rails.md)). Detection uses project files and dependencies; a repository that matches several providers follows this order. To pin a Node.js or Python version, see [runtime versions](https://lizard.build/docs/deploy/runtime-versions.md). On this path: - Uploaded source (`lizard up`) always uses a root `Dockerfile` as written. - GitHub source uses a root `Dockerfile` as written unless it looks incomplete: it has a `COPY` of `build`, `dist`, `out`, `.next` or `public` written without a trailing slash (`COPY dist /usr/share/nginx/html`) and no `RUN` line that starts with `npm`, `yarn`, `pnpm`, `bun`, `pip`, `pip3`, `bundle`, `cargo`, `go build`, `mvn` or `gradle`. lizardpack then generates a replacement. - With no Dockerfile, lizardpack generates one for you. - The **start command** is auto-detected: a `Procfile` `web:` line (Python/Ruby) or `package.json` `scripts.start` (Node) is picked up automatically. For Django, Flask and FastAPI start commands, see [Python app hosting](https://lizard.build/blog/python-app-hosting). - The **port** is inferred from `EXPOSE`, framework defaults, or an explicit `PORT` env. > **Gotcha:** On GitHub source, a Dockerfile that copies pre-built output (`COPY dist /dest`) and has no package-manager `RUN` step is treated as incomplete and regenerated by lizardpack. Add a real build step, or set `dockerfilePath` to force verbatim use. See [Dockerfile changes don't apply](https://lizard.build/docs/deploy/troubleshooting/incomplete-dockerfile.md). ## What triggers a rebuild | Action | Rebuilds? | |--------|-----------| | `git push` to the tracked branch | ✅ via GitHub webhook | | `lizard redeploy` / `lizard up` | ✅ explicit | | Changing `VITE_*` or `NEXT_PUBLIC_*` vars | ✅ rebuilds a running service; see [build-time variables](#build-time-variables) for when the values reach the build | | `service set` of build fields (`repoUrl`, `branch`, `sourceType`, `buildCommand`, `dockerfilePath`, `rootDirectory`) | ✅ auto-rebuilds a running service; otherwise run `lizard redeploy` | | `service set` of runtime-only fields (`startCommand`, `preDeployCommand`, `containerPort`, `watchPatterns`) | ❌ run `lizard redeploy` to apply | | Any other env var / secret change | ❌ applied on a quick restart (no rebuild) | > **Don't double-build.** After a `service set` that changes a build field on a running service, Lizard starts a rebuild. Running `lizard redeploy` afterward queues a second build ([why](https://lizard.build/docs/deploy/troubleshooting/double-build.md)). A crashed or never-deployed service does not rebuild on its own; run `lizard redeploy`. ## Build-time variables Lizard writes `VITE_*` and `NEXT_PUBLIC_*` variables into the Dockerfile as `ENV` lines in two cases: - GitHub services without `dockerfilePath`: the lizardpack Dockerfile or the repo's root `Dockerfile` gets them. - Services with `buildCommand` or `startCommand` whose synthesized Dockerfile is a Node build. Services deployed with `lizard up` and no build or start command get no Lizard variables at build time. No service gets build args other than `COMMIT_SHA`, so an `ARG` in your Dockerfile stays empty. For public values with `lizard up`, commit them in `.env.production` (check that `.gitignore` does not exclude it), write them into your Dockerfile, or, for a Node server app, set both `buildCommand` and `startCommand`. ## Watching a build Build logs stream during `lizard up`. For any service: ```bash lizard logs --build # the most recent build's logs lizard events # deploy history + replica status ``` If a build fails, read `lizard logs --build`, fix the cause (in your repo, or by adjusting `buildCommand` / `startCommand` via `lizard service set`), then `lizard redeploy`. ## Runtime notes - **No Docker `HEALTHCHECK`.** The runtime doesn't run Docker's healthcheck loop, so `HEALTHCHECK` directives are ignored. Lizard runs a TCP probe against your port instead (skipped in [worker mode](https://lizard.build/docs/deploy/workers.md)). - **Don't write a Dockerfile unsolicited.** lizardpack auto-detects most stacks: try a deploy first, and only add a Dockerfile (or set `dockerfilePath`) if the auto-build doesn't fit. ## Build configuration reference | Field | Description | |-------|-------------| | `buildCommand` | Command to build your app → selects the **synthesized Dockerfile** path unless `dockerfilePath` is set | | `startCommand` | Command to start your app at runtime; replaces the image's `ENTRYPOINT` and `CMD` | | `preDeployCommand` | Runs at the start of every container (each replica, each restart), before `startCommand`; only runs when `startCommand` is set. Make it safe to run many times and at the same time | | `dockerfilePath` | Path to a repo Dockerfile to use **verbatim**; wins over `buildCommand` / `startCommand` for the image build | | `rootDirectory` | Subdirectory to build from (monorepos) | | `watchPatterns` | Only redeploy when matching paths change | | `containerPort` | TCP port your app listens on (default `3000`; `0` = [worker](https://lizard.build/docs/deploy/workers.md)) | Set any of these with `lizard service set --set =`. Clear a field with an empty value, such as `--set buildCommand=`. See [`lizard service`](https://lizard.build/docs/cli/service.md). --- title: "Deployments: lifecycle, restarts and recovery" description: "The deploy lifecycle on Lizard: what triggers a build, how traffic shifts to healthy replicas, and how restarts differ from redeploys." url: https://lizard.build/docs/concepts/deployments/ updated: 2026-10-12 --- # Deployments A **deployment** is one build-and-release of a service. Services run as Kubernetes pods. A successful build does not by itself prove that the application started or can serve requests. ## How a deploy happens These actions start a deployment: - A **`git push`** to the tracked branch (for `github`-source services). - **`lizard redeploy`**: rebuild from the latest commit (git) or last upload, with current variables. - **`lizard up`**: upload the current directory and deploy it. - A **`service set`** that changes a build-affecting field. ```bash lizard redeploy --service api # rebuild + redeploy from current source lizard up # upload cwd and deploy ``` ## Lifecycle 1. **Build**: the platform runs your build (see [Build Pipeline](https://lizard.build/docs/concepts/build-pipeline.md)). 2. **Pre-deploy**: if both `preDeployCommand` and `startCommand` are set, the container runs `sh -c "
 && exec "`. The pre-deploy command runs at the start of every container (each replica, each restart), so make it safe to run many times and at the same time. Without `startCommand` it is skipped.
3. **Start**: Lizard starts the new pod next to the old one. [Workers](https://lizard.build/docs/deploy/workers.md) stop the old pod first.
4. **Health check**: a TCP probe checks the app's port every 2 seconds, and traffic moves only to a ready pod. Lizard waits about 30 seconds for it; if the new pod is not ready by then, the deploy is marked failed. Workers skip the probe.
5. **Verify**: inspect the release status and call the application URL. Port reachability is not a full application health check.

## Streaming output

When you run a non-detached `lizard up` (or `redeploy`), build logs stream live. With `--json`, you get one JSON event per line:

```json
{ "event": "log", "line": "..." }
{ "event": "deployed", "status": "...", "url": "https://..." }
```

The final event is `deployed`, `failed`, or `deploying`. Use `--detach` to start the deploy and return immediately without streaming.

## Inspecting history

```bash
lizard events                  # deploy history + per-replica status
lizard events --limit 25       # show more
lizard ps                      # current services, status, and URLs
```

In the [dashboard](https://lizard.build/docs/dashboard.md), the **Deployments** view shows the full timeline, per-deploy build logs, and a detail drawer for each release.

## Restarts vs. redeploys

| Command | What it does |
|---------|--------------|
| `lizard restart --service ` | Restart of the **current** build: no rebuild |
| `lizard redeploy --service ` | Fresh **build** from latest commit/upload, then release |

See [`lizard restart`](https://lizard.build/docs/cli/restart.md) and [`lizard redeploy`](https://lizard.build/docs/cli/redeploy.md). Use `restart` to restart replicas (e.g. to pick up a runtime secret that didn't hot-reload); use `redeploy` when you need a new build.

## Recovering from a crash

Inspect the most recent crash or restart's logs:

```bash
lizard logs --restart latest       # log tail around the latest restart
lizard logs --restart          # a specific restart
lizard events                      # see replica status
```

## Config changes (no rebuild)

Most environment variable and secret changes apply without a rebuild: Lizard updates the service's config and restarts it, which takes a few seconds. Build-time values (`VITE_*`, `NEXT_PUBLIC_*`) and build-field changes do force a rebuild of a running service: see the [rebuild triggers table](https://lizard.build/docs/concepts/build-pipeline.md#what-triggers-a-rebuild) and [build-time variables](https://lizard.build/docs/concepts/build-pipeline.md#build-time-variables).

## Failed releases and recovery

A failed build and a failed application start are different cases. In both, an HTTP service that already runs keeps serving its previous version: a failed build changes nothing, and a new pod that never becomes ready gets no traffic. A worker's old pod stops before the new one starts, so a broken worker release leaves no worker running. Inspect the active service, build logs, and runtime logs. `lizard redeploy` builds the selected source again; it is not a command for restoring a previous build. Revert the source commit when needed, deploy it, and check the result. See [known issues](https://lizard.build/docs/platform/known-issues.md).


---
title: "Deploy"
description: "Get a service running on Lizard and keep it there: GitHub, local and Dockerfile deploys, runtime versions, workers, scaling, regions and fixes."
url: https://lizard.build/docs/deploy/
updated: 2026-10-12
---

# Deploy

Deploy a service from GitHub, a local folder or a Dockerfile, then set its runtime, region and resources.

To compare hosts, the blog explains how [container as a service](https://lizard.build/blog/container-as-a-service) platforms differ and how to estimate their cost.

## In this section

- [Deploy from GitHub](https://lizard.build/docs/deploy/github.md): connect a repo so pushes to the tracked branch auto-redeploy.
- [Deploy from Local Code](https://lizard.build/docs/deploy/upload.md): ship the current directory with `lizard up`, no Git required.
- [Deploy from a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md): build and host your own Docker container, or wrap a public image.
- [Runtime versions](https://lizard.build/docs/deploy/runtime-versions.md): set the Node.js, Python, Go or Ruby version with `.nvmrc`, `.python-version`, `go.mod` or `.ruby-version`.
- [Background Workers](https://lizard.build/docs/deploy/workers.md): run queue consumers, polling loops and bots such as a [Discord bot](https://lizard.build/docs/guides/discord-bot.md) with `containerPort=0`.
- [Scaling](https://lizard.build/docs/deploy/scaling.md): adjust replicas, resources, and storage.
- [Regions](https://lizard.build/docs/deploy/regions.md): where your services run.
- [Troubleshooting](https://lizard.build/docs/deploy/troubleshooting/incomplete-dockerfile.md): fixes for the most common build and deploy problems.


---
title: "Deploy from GitHub: git push to deploy"
description: "Connect a GitHub repo so every push to the tracked branch builds and redeploys, including private repos, branch switching, and monorepos."
url: https://lizard.build/docs/deploy/github/
updated: 2026-10-12
---

# Deploy from GitHub

Connect a GitHub repository for git push to deploy: Lizard builds and redeploys your app whenever you push to the tracked branch. Run `lizard add -r owner/repo` to create the service; for a private repository, connect the Lizard GitHub App first with `lizard git connect`. This is the recommended way to run an app you keep in GitHub. For code with no remote, [deploy from local code](https://lizard.build/docs/deploy/upload.md) instead.

## Create a service from a repo

```bash
lizard add -r your-org/your-app
```

This creates a `github`-source service, clones the repo, auto-detects the stack ([lizardpack](https://lizard.build/docs/concepts/build-pipeline.md)), builds it, and returns a live URL. Useful flags:

| Flag | Purpose |
|------|---------|
| `-r, --repo ` | Source repository |
| `-n, --name ` | Service name (used in `${{name.KEY}}` refs and the dashboard) |
| `-v, --variables ` | Seed an env var; repeatable |
| `--region ` | Region to deploy in |
| `--no-deploy` | Attach the repo but skip the first build |

## Private repositories

Connect the Lizard GitHub App once per account to grant access to private repos:

```bash
lizard git connect
lizard git status        # show connection + repo status
```

You can also connect and manage repository access from the **GitHub integration** page in the dashboard (`lizard open`).

## Point an existing service at a repo

To switch a service to a git source (or change the repo/branch):

```bash
lizard service set api \
  --set sourceType=github \
  --set repoUrl=https://github.com/your-org/your-app \
  --set branch=main
```

Changing a build field rebuilds a running service. Running `redeploy` afterward will [queue two builds](https://lizard.build/docs/deploy/troubleshooting/double-build.md). A crashed or never-deployed service needs `lizard redeploy`.

> **Note:** `lizard up` always forces `sourceType=upload`. Don't use it to update a git-backed service: push to the remote or use `lizard redeploy` instead.

## Auto-redeploy on push

Once `repoUrl` is set, pushes to the matching `branch` redeploy automatically via the GitHub webhook. Connect the Lizard GitHub App (`lizard git connect`) so Lizard receives push events for the repository. To scope which pushes trigger a redeploy:

- **`rootDirectory`**: only build a subdirectory (monorepos).
- **`watchPatterns`**: only redeploy when matching paths change.

```bash
lizard service set api --set watchPatterns='apps/api/**,packages/**'
```

## Switch branches

```bash
lizard git checkout api staging      # move the `api` service to the `staging` branch and redeploy
```

## Monorepos

For a repo that holds several apps, create one service per app and set its `rootDirectory` to the subpath:

```bash
lizard add -r your-org/monorepo -n api --no-deploy
lizard service set api --set rootDirectory=apps/api
lizard redeploy --service api
```

If your current directory is a sub-app inside a repo whose parent is already linked, add the service in the parent's project and set `rootDirectory` to the cwd subpath.

## See also

- [Build Pipeline](https://lizard.build/docs/concepts/build-pipeline.md): how your stack is detected and built.
- [Framework guides](https://lizard.build/docs/framework-guides.md): build commands and ports for [Next.js](https://lizard.build/docs/framework-guides/nextjs.md), [Nuxt](https://lizard.build/docs/framework-guides/nuxt.md), [Django](https://lizard.build/docs/framework-guides/django.md), [FastAPI](https://lizard.build/docs/framework-guides/fastapi.md), [Rails](https://lizard.build/docs/framework-guides/rails.md), [Go](https://lizard.build/docs/framework-guides/go.md) and more.
- [Deploy with a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md): when the repo has its own Dockerfile.
- [Deploy from Local Code](https://lizard.build/docs/deploy/upload.md): when you don't have a remote.
- [`lizard git`](https://lizard.build/docs/cli/git.md): the full command reference for `connect`, `status`, and `checkout`.
- [Choose the host by the application](https://lizard.build/blog/google-antigravity-how-to-deploy-your-app-to-production#choose-the-host-by-the-application): choosing a host for an app an agent wrote.


---
title: "Deploy from Local Code"
description: "Ship the current directory with lizard up, no Git required. Covers gitignore handling, build and start overrides, and headless CI use."
url: https://lizard.build/docs/deploy/upload/
updated: 2026-10-12
---

# Deploy from Local Code

Run `lizard up` to upload and deploy the current directory, whether or not your code is on GitHub. It packages your working tree as a tarball, sends it to the build nodes, and deploys. For the commands each stack needs, see the guides for [React](https://lizard.build/docs/framework-guides/react.md), [Vue](https://lizard.build/docs/framework-guides/vue.md), [Astro](https://lizard.build/docs/framework-guides/astro.md), [SvelteKit](https://lizard.build/docs/framework-guides/sveltekit.md), [Hugo](https://lizard.build/docs/framework-guides/hugo.md), [Docusaurus](https://lizard.build/docs/framework-guides/docusaurus.md), [VitePress](https://lizard.build/docs/framework-guides/vitepress.md), [FastAPI](https://lizard.build/docs/framework-guides/fastapi.md) and [Streamlit](https://lizard.build/docs/framework-guides/streamlit.md).

## Deploy the current directory

```bash
lizard up
```

- Uploads the current directory as a tarball. Inside a git repository it respects `.gitignore` (disable with `--no-gitignore`). Outside git it skips only a fixed list of build and dependency folders (`node_modules`, `.git`, `dist`, `.next`, `build`, `__pycache__`, `.venv`, `venv`, `.cache`, `coverage`, `.turbo`, `.vercel`) plus `.DS_Store`, `*.pyc`, `*.pyo` and `*.log`, so `.env` files are uploaded. Run `git init` or move secrets out first.
- Forces `sourceType=upload`.
- Streams build logs over SSE and prints the live URL when finished.

If the directory isn't linked to a project yet, `up` runs `init` first. On a TTY that's interactive; in a non-TTY (CI) it will **not** silently create a project: it errors and asks you to run `lizard init --name ` (or pass `--project ` to `up`). This guards against a typo spawning an empty project.

## Common flags

| Flag | Purpose |
|------|---------|
| `-s, --service ` | Target an existing service |
| `--build-command ` | Override the build command |
| `--start-command ` | Override the start command |
| `--pre-deploy-command ` | Run at the start of every container, before the start command; needs `--start-command` (see [build configuration](https://lizard.build/docs/concepts/build-pipeline.md#build-configuration-reference)) |
| `--port ` | Container port (`0` = [worker mode](https://lizard.build/docs/deploy/workers.md)) |
| `--region ` | Region for a new service (ignored for existing services) |
| `-d, --detach` | Start the deploy and exit without streaming logs |
| `-c, --ci` | CI-friendly output |
| `--no-gitignore` | Upload everything, ignoring `.gitignore` |

```bash
lizard up --service api --start-command "node server.js" --port 8080
```

## Setting build/start commands

Passing `--build-command` or `--start-command` saves it on the service and switches the service to the **synthesized Dockerfile** path, unless `dockerfilePath` is set. On that path, `Procfile` and `package.json` `scripts.start` are **not** read: set the start command explicitly. See [Build Pipeline](https://lizard.build/docs/concepts/build-pipeline.md). For Python apps, [Django, Flask and FastAPI](https://lizard.build/blog/python-app-hosting#build-and-start-commands) each need their own `gunicorn` or `uvicorn` command.

## Re-deploying an upload service

`lizard up` re-uploads and rebuilds. To rebuild the **last** upload with current variables without re-uploading:

```bash
lizard redeploy --service api
```

## Headless / CI

For non-interactive flows, link the project explicitly before deploying:

```bash
export LIZARD_TOKEN=liz_xxx   # create one with: lizard keys create 
lizard init --name my-project
lizard up --ci --service api
```

## When to prefer GitHub instead

Use uploads to test changes or deploy code with no remote. For an app you plan to maintain, use a [GitHub source](https://lizard.build/docs/deploy/github.md) so pushes auto-redeploy and each deploy appears in the history.

## See also

- [`lizard up`](https://lizard.build/docs/cli/up.md): the full command reference.
- [Build Pipeline](https://lizard.build/docs/concepts/build-pipeline.md): how your stack is detected and built.


---
title: "Deploy a Docker container from a Dockerfile"
description: "Host a Docker container on Lizard: commit a Dockerfile, run lizard up or connect GitHub; Lizard builds it, takes the port from EXPOSE and serves it over HTTPS."
url: https://lizard.build/docs/deploy/dockerfile/
updated: 2026-10-12
---

# Deploy a Docker container from a Dockerfile

Lizard hosts Docker containers that it builds from your `Dockerfile`. Put the file at the root of your project and run `lizard up`, or connect a GitHub repository: Lizard builds the image on its own build machines, so you need no local Docker. It takes the port from `EXPOSE` or `--port`, passes it to the container in `PORT`, and serves the container over HTTPS on an `onlizard.com` domain. Lizard does not pull ready-made images from a registry; to run a public image, write a one-line Dockerfile that starts `FROM` it.

**Test status:** this page describes Lizard's builder, deployer and CLI 4.0.42 as of 2026-10-12, checked against their source. We did not run a new deploy for it. The [Umami guide](https://lizard.build/docs/guides/umami.md) deploys a one-line `FROM` Dockerfile in this way and records its test.

## Before you start

- A `Dockerfile` whose app listens on `0.0.0.0`. A server bound to `127.0.0.1` accepts no connections from outside the container.
- Lizard CLI, signed in with `lizard login`, on Pro or the 7-day Pro trial (card required). See [pricing](https://lizard.build/pricing).

If you have no Dockerfile, try a deploy without one first: lizardpack detects Go, Node.js, Python, Ruby, PHP, Java, Rust and static sites. See the [build pipeline](https://lizard.build/docs/concepts/build-pipeline.md).

## An example Dockerfile

For a Node.js server such as the one in [Deploy a Node.js app](https://lizard.build/docs/framework-guides/nodejs.md):

```dockerfile
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]
```

Add a `.dockerignore` so local files stay out of the image:

```text
node_modules
.git
.env*
```

## Deploy with lizard up

```bash
lizard init --name my-container
lizard add --service web
lizard up --service web
```

`lizard up` reads the first `EXPOSE` line of the local `Dockerfile`, prints `Detected port 3000 from Dockerfile` and sets that as the service port. Pass `--port ` to choose another. An upload always uses the root `Dockerfile` as written. See [deploy from local code](https://lizard.build/docs/deploy/upload.md) for what the upload includes.

## Deploy from GitHub

```bash
lizard add -r your-org/your-app -n web
```

Every push to the tracked branch rebuilds the image. Lizard uses the root `Dockerfile` unless it only copies output built elsewhere (`COPY dist …` with no `npm`, `pip`, `go build` or similar `RUN` step); then lizardpack writes a replacement. To always use yours, set `dockerfilePath`:

```bash
lizard service set web --set dockerfilePath=Dockerfile
```

See [Dockerfile changes don't apply](https://lizard.build/docs/deploy/troubleshooting/incomplete-dockerfile.md) and [deploy from GitHub](https://lizard.build/docs/deploy/github.md).

## A Dockerfile in a subdirectory or with another name

`dockerfilePath` is relative to the build directory, which `rootDirectory` sets. For a monorepo service:

```bash
lizard service set api --set rootDirectory=apps/api --set dockerfilePath=Dockerfile.prod
```

Leave `buildCommand` and `startCommand` empty on a Dockerfile service. Without `dockerfilePath`, either one makes Lizard write its own Dockerfile from the commands instead of using yours. With `dockerfilePath` set, a `startCommand` still replaces the image's `ENTRYPOINT` and `CMD`: Lizard runs `sh -c ""`.

## Which port Lizard routes to

Lizard picks the service port in this order:

1. A port you set: `lizard up --port`, `lizard port`, the dashboard, or the `EXPOSE` line that `lizard up` read.
2. `0`, which is [worker mode](https://lizard.build/docs/deploy/workers.md): no port, no route.
3. A `PORT` variable you set on the service.
4. The first port the built image exposes, including ports from its base image. Ports sort as text, so `3000` comes before `80`.
5. `3000`.

Inside the container, `PORT` holds the chosen port. Read it, or listen on the same fixed port. To change the port later, run `lizard port 8080 --service web`, then `lizard redeploy --service web`. See [networking](https://lizard.build/docs/networking.md).

## What reaches the build

- **Build arguments:** only `COMMIT_SHA`. Declare `ARG COMMIT_SHA` to use it. Other `ARG` lines keep their default or stay empty.
- **Variables:** service variables and secrets do not reach a Dockerfile build, with one exception for `VITE_*` and `NEXT_PUBLIC_*` values described in [build-time variables](https://lizard.build/docs/concepts/build-pipeline.md#build-time-variables). Write public values into the Dockerfile or commit them; keep secrets for run time.
- **Base images:** the builder has no login for your registries, so every `FROM` image must be public.
- **Changes Lizard makes:** before building, Lizard adds `ARG COMMIT_SHA` ahead of your source `COPY` and adds BuildKit cache mounts to `npm`, `yarn`, `pnpm` and `bun` install steps. Cache mounts you write yourself keep working, scoped to this service; a `--mount` option written with quotes fails the build.

Watch the build with `lizard logs --build --service web`.

## At run time

- **Health check:** Lizard ignores `HEALTHCHECK` and checks that the container accepts TCP connections on the service port. See [service never healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md).
- **User:** the image's `USER` applies. A non-root user may lack permission to open a port below 1024, so prefer `8080` over `80` for such images.
- **Variables:** the container gets your service variables, `PORT` and the `LIZARD_*` platform variables. See [variables and secrets](https://lizard.build/docs/variables.md).
- **Files:** anything the container writes is lost on the next deploy. Use [Managed Postgres](https://lizard.build/docs/addons/postgres.md), [Managed Object Storage](https://lizard.build/docs/addons/storage.md) or another service for data; see [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md).

## Run a public image

Lizard builds every service from source, so wrap a public image in a Dockerfile:

```dockerfile
FROM ghcr.io/umami-software/umami:3.3.1
EXPOSE 3000
```

Use a version tag rather than `latest` to select the intended release. [Run Umami](https://lizard.build/docs/guides/umami.md) and [Run Flowise](https://lizard.build/docs/guides/flowise.md) use this pattern with Managed Postgres.

## Common problems

| Symptom | Fix |
|---|---|
| Your Dockerfile edits do not show up | On GitHub, Lizard may treat the file as incomplete; set `dockerfilePath`. Also check that the service has no `buildCommand` or `startCommand`. |
| Deploy never becomes healthy | The app listens on another port or on `127.0.0.1`. Compare `lizard port --service web` with the log. |
| `pull access denied` for the base image | The image is private or the name is wrong. Use a public image. |
| An `ARG` value is empty | Lizard passes no build arguments except `COMMIT_SHA`. Set a default in the Dockerfile. |
| The service runs but serves nothing | It may be in worker mode: `lizard port --service web` prints `worker mode`. |

## Related

- [Build pipeline](https://lizard.build/docs/concepts/build-pipeline.md)
- [Runtime versions](https://lizard.build/docs/deploy/runtime-versions.md)
- [Deploy a Rails app](https://lizard.build/docs/framework-guides/rails.md)
- [Background workers](https://lizard.build/docs/deploy/workers.md)
- [`lizard up`](https://lizard.build/docs/cli/up.md)


---
title: "Set the Node.js, Python, Go or Ruby version"
description: "Set the Node.js, Python, Go or Ruby version Lizard builds with: .nvmrc, engines.node, .python-version, requires-python, go.mod or .ruby-version."
url: https://lizard.build/docs/deploy/runtime-versions/
updated: 2026-10-12
---

# Set the Node.js, Python, Go or Ruby version

Lizard reads the runtime version from files in your project when lizardpack builds it: `.nvmrc` or `engines.node` for Node.js, `.python-version` or `requires-python` for Python, the `go` line of `go.mod` for Go, and `.ruby-version` or the `Gemfile` `ruby` line for Ruby. To change the version, edit that file and deploy again. Without one, Lizard uses Node.js 22, Python 3.13, Go 1.24 or Ruby 3.4.

**Test status:** we checked every rule on this page on 2026-10-12 by running lizardpack, the build planner Lizard's builders run, against sample projects.

## Where Lizard looks

| Language | Files, first match wins | Part of the version used | Default | Base image |
|---|---|---|---|---|
| Node.js | `.nvmrc`, `.node-version`, `.tool-versions` (`nodejs` or `node`), `engines.node` in `package.json` | Major: `20.11.1` → `20` | `22` | `node:-slim` |
| Python | `.python-version`, `.tool-versions` (`python`), `requires-python` in `pyproject.toml` | Major and minor: `3.12.4` → `3.12` | `3.13` | `python:-slim` |
| Go | The `go` line in `go.mod` | Major and minor: `1.24.3` → `1.24` | `1.24` | `golang:-alpine` to build, `alpine:3.19` to run |
| Ruby | `.ruby-version`, the `ruby` line in `Gemfile` | Major and minor: `3.4.1` → `3.4` | `3.4` | `ruby:-slim` |

Lizard uses the official image tag for the major or major/minor version and drops the patch number. Each build gets the patch release that tag points to when the build runs.

These files apply only when lizardpack writes the Dockerfile. See [when Lizard ignores version files](#when-lizard-ignores-version-files).

## Change the Node.js version

```bash
echo 24 > .nvmrc
lizard up --service web
```

With a GitHub service, commit the file and push instead. Rules:

- Write a number: `24`, `v24` and `24.1.0` all select Node.js 24. Lizard skips lines that start with `#`.
- Lizard skips `lts/*` and `node` in `.nvmrc` and moves on to the next file. It uses other aliases, such as `lts/iron`, as written, and the build fails.
- `.nvmrc` wins over `engines.node`.
- `engines.node` gives the lowest version in its range: `>=18` builds on Node.js 18, and `^20.11.0` on Node.js 20. A range with `||`, such as `18 || 20`, fails the build.

## Change the Python version

```bash
echo 3.12 > .python-version
lizard up --service web
```

- `.python-version` must hold a version number such as `3.12` or `3.12.4`. Lizard skips lines that start with `#`. It also skips other values, such as `pypy3.10`, and moves on to the next file.
- `requires-python = ">=3.11"` builds on Python 3.11, the lowest version in the range.
- A range with an upper bound, such as `">=3.11,<3.14"`, produces an image name that does not exist and the build fails. Add a `.python-version` file; it wins.
- `runtime.txt` is not read.

## Change the Go version

```bash
go mod edit -go=1.25
lizard up --service api
```

Lizard reads only the `go` line. It does not read the `toolchain` line.

## Change the Ruby version

```bash
echo 3.4.1 > .ruby-version
lizard up --service web
```

Write the bare number. `rails new` writes `ruby-3.4.1`, and Lizard does not strip the `ruby-` prefix, so the build fails. A `Gemfile` line such as `ruby "~> 3.3.0"` builds on Ruby 3.3. A Rails app that keeps its generated `Dockerfile` takes the version from that file instead; see [Deploy a Rails app](https://lizard.build/docs/framework-guides/rails.md).

## When Lizard ignores version files

- **Your own Dockerfile.** Its `FROM` line decides, for example `FROM node:24-slim`. See [Deploy a Docker container from a Dockerfile](https://lizard.build/docs/deploy/dockerfile.md).
- **A build or start command on the service.** Lizard then writes a Dockerfile from the commands on a fixed image: `node:22-alpine`, `python:3.13-slim` or `golang:1.24-alpine`, chosen by the first word of the command. To return to version files, clear both:

```bash
lizard service set web --set buildCommand= --set startCommand=
```

Clearing `buildCommand` on a running service starts a rebuild by itself. Check `lizard events` before you run `lizard redeploy`, so you do not queue a second build. See the [build decision order](https://lizard.build/docs/concepts/build-pipeline.md#build-decision-order).

## Check which version a build used

```bash
lizard logs --build --service web
```

The build log names the base image, such as `node:24-slim` or `python:3.12-slim`. To check the version at run time, print it when your app starts, for example `process.version` in Node.js or `sys.version` in Python, and read `lizard logs --service web`.

## Related

- [Build pipeline](https://lizard.build/docs/concepts/build-pipeline.md)
- [Deploy a Node.js app](https://lizard.build/docs/framework-guides/nodejs.md)
- [Deploy a Go app](https://lizard.build/docs/framework-guides/go.md)
- [Deploy a Streamlit app](https://lizard.build/docs/framework-guides/streamlit.md)
- [Deploy FastAPI](https://lizard.build/docs/framework-guides/fastapi.md)
- [Deploy Django](https://lizard.build/docs/framework-guides/django.md)


---
title: "Background Workers"
description: "Run queue consumers and polling loops with no HTTP port. What containerPort=0 changes, how to enable worker mode, and when not to."
url: https://lizard.build/docs/deploy/workers/
updated: 2026-10-12
---

# Background Workers

A background worker on Lizard is a service with no HTTP port: a queue consumer, a bot that polls for updates, a reconciler, or a polling loop. Run it in **worker mode** by setting the container port to `0`, for example with `lizard up --port 0`. The worker gets no `PORT`, no readiness probe and no public route, so it is never marked unhealthy for not answering HTTP. Pair it with [Managed Redis](https://lizard.build/docs/addons/redis.md) for a job queue. Chat bots are workers too; see the [Telegram bot](https://lizard.build/docs/guides/telegram-bot.md) and [Discord bot](https://lizard.build/docs/guides/discord-bot.md) guides.

## What worker mode changes

When `containerPort=0`, the platform:

- **Skips `PORT` injection**: the worker doesn't bind anywhere.
- **Skips the TCP readiness probe**: no false-positive "unhealthy" status. The old pod stops before the new one starts.
- **Skips `EXPOSE`** in the synthesized Dockerfile.
- **Skips load-balancer route registration**: nothing is served. (A generated `*.onlizard.com` domain may still appear on the service, but it won't respond.)

## Enable worker mode

Three equivalent ways:

```bash
# New upload-source worker
lizard up --port 0

# Flip an existing service
lizard port 0 --service worker

# Via the config:apply path
lizard service set worker --set containerPort=0
```

Redeploy after changing the port to apply worker mode.

## Check the current port

```bash
lizard port --service worker
```

The command prints the current container port, or `worker mode` when it's `0`.

## When *not* to use worker mode

Don't use worker mode for a regular HTTP service that's just slow to start. Worker mode disables the reachability check entirely, so it will not detect a server that fails to listen. If your service is meant to serve traffic, keep a real port and fix the startup instead.

## Run a queue consumer

Use the [redis-worker example](https://github.com/lizard-build/docs/tree/23635ba7e162bafce75d3f6206553b3ef2c0c48e/_examples/redis-worker). It moves a job from a pending list to a processing list, stores its uppercase result, and acknowledges the job in a Redis transaction. Keep one replica: startup recovery assumes a single consumer. The example uses Python 3.13 and redis-py 6.4.0.

From the directory containing its Dockerfile:

```bash
lizard init --name queue-example
lizard add --service worker
lizard add redis
lizard secrets set REDIS_URL='${{redis.REDIS_URL}}' --service worker
lizard up --service worker --port 0
```

Sign in with `lizard login` if a command reports that authentication is required. Use the database's actual name if it is not `redis`. Keep the reference quoted. Set it before uploading the application.

Check the final deploy event, then check the worker:

```bash
lizard port --service worker
lizard logs --service worker --json
```

Submit a job from your computer. `lizard run --service redis` runs a local command with the Redis addon's connection values, including its public `REDIS_URL`:

```bash
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
lizard run --service redis -- python jobs.py enqueue first-job
lizard run --service redis -- python jobs.py result first-job
```

The process prints `Queue worker ready`, then `Processed first-job` after the job runs. If the log tail is empty, rely on the job check; the [test results](https://lizard.build/docs/guides/validation.md) record the observed logging limit. The result command waits up to 30 seconds and prints `{"id": "first-job", "result": "HELLO"}`. A running process alone does not prove it can consume a job.

## Verify a restart

For this test service, restart the worker and submit another job:

```bash
lizard restart --service worker
lizard run --service redis -- python jobs.py result first-job
lizard run --service redis -- python jobs.py enqueue after-restart
lizard run --service redis -- python jobs.py result after-restart
```

If `after-restart` has no result after 30 seconds, wait for the service to return to `running` in `lizard ps --json` and run the result command again. Both results should be `HELLO`. The first result lives in Redis, so a worker restart does not remove it. On startup the single consumer returns unfinished processing entries to the pending list.

This demo only accepts the JSON jobs created by `jobs.py`. It does not implement a dead-letter queue, validation for untrusted producers, or exactly-once external side effects. Use an established queue library and idempotent handlers for payments, email or other effects. Redis durability and backup are separate from worker restart behavior; see [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md).

## Troubleshooting

| Symptom | Check |
|---|---|
| Missing service | Run `lizard add --service worker` after creating the project. |
| No `Queue worker ready` | Check the service-scoped `REDIS_URL`, addon readiness and connection errors. Never print the connection string. |
| Worker expects an HTTP port | Set `containerPort=0`; changing it on a running service requires a redeploy. |
| No result after 30 seconds | Read worker logs and verify the job ID. This example accepts jobs from its own `jobs.py` only. |
| Duplicate effects | A retry can run work again. This example stores a result by job ID; external effects require their own idempotency. |

## See also

- [`lizard port`](https://lizard.build/docs/cli/port.md): show or change a service's container port.
- [Service never becomes healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md): the most common worker-mode misconfiguration.
- [Managed Addons](https://lizard.build/docs/addons.md): Redis, Postgres, and S3 for stateful workloads.
- [Python app hosting](https://lizard.build/blog/python-app-hosting#django-postgres-and-celery-need-separate-checks): a Celery worker is the same image with a different command and no HTTP port.

See [scenario test results](https://lizard.build/docs/guides/validation.md) for checked versions, cloud results and remaining limits.


---
title: "Scale a service: replicas, CPU and memory"
description: "Add replicas or raise CPU and memory for a service, and grow addon storage. Allowed ranges, rollout behavior, and how to verify a change."
url: https://lizard.build/docs/deploy/scaling/
updated: 2026-10-12
---

# Scaling

Use `lizard scale` to add service replicas, raise CPU or memory, or grow addon storage.

## Scale a service

```bash
lizard scale --service api --replicas 3
lizard scale --service api --cpu 2 --memory 2048
```

| Flag | Applies to | Allowed values |
|------|-----------|----------------|
| `--replicas ` | apps | `1` up to your plan's [limit](https://lizard.build/docs/platform/limits.md#applications): `1` during the trial, `5` on Pro and Enterprise |
| `--cpu ` | apps and addons | whole cores: `1`, `2`, `3`, `4` |
| `--memory ` | apps and addons | `128` to `8192` MB (1 MB steps) |
| `--storage ` | **addons only**, grow-only | `512`, `1024`, `2048`, `4096`, `8192`, `16384` |

You can combine flags in a single command. Replica changes roll out without a rebuild. CPU and memory changes restart the service with the new limits.

## Horizontal scaling

Run multiple replicas of an app behind the load balancer:

```bash
lizard scale --service api --replicas 5
```

Each replica is its own pod. Make sure your app is stateless (store state in [Managed Postgres](https://lizard.build/docs/addons/postgres.md), [Managed Redis](https://lizard.build/docs/addons/redis.md), or [Managed Object Storage](https://lizard.build/docs/addons/storage.md)) so replicas are interchangeable.

## Vertical scaling

Give a single replica more resources:

```bash
lizard scale --service api --cpu 4 --memory 8192
```

## Growing addon storage

You can grow addon data volumes, but you cannot shrink them:

```bash
lizard scale --service postgres --storage 8192
```

## Verifying

```bash
lizard ps                 # replica status + URL
lizard events             # per-replica deploy/scale history
lizard metrics            # live CPU / memory / network / disk
lizard metrics --cost     # include cost
```

## See also

- [Observability → Metrics](https://lizard.build/docs/observability/metrics.md): resource and cost monitoring.
- [`lizard scale`](https://lizard.build/docs/cli/scale.md): the full command reference.
- [How to estimate CaaS cost](https://lizard.build/blog/container-as-a-service#how-to-estimate-caas-cost): which meters to compare when estimating a bill.


---
title: "Regions: where services and addons run"
description: "Pick where your services and addons run, co-locate stateful resources to cut latency, and see which hostnames services and object storage get."
url: https://lizard.build/docs/deploy/regions/
updated: 2026-10-12
---

# Regions

Lizard runs services, addons and Sandboxes in regions such as US East (`us-east-1`) and EU West (`eu-west-lim-a`). You pick the region when you create a resource; without `--region`, `lizard add` creates it in `us-east-1`. Put a service and its databases in the same region to keep latency low. [`lizard regions`](https://lizard.build/docs/cli/regions.md) lists the regions open to your account.

## List regions

```bash
lizard regions
lizard regions --json
```

## Choose a region

Pass `--region ` when creating a service or addon:

```bash
lizard add -r your-org/your-app --region 
lizard add postgres --region 
lizard up --region           # new services only
```

Projects have no default region. Without `--region`, `lizard add` creates resources in `us-east-1`, and `lizard up` creates a new service in the region of the API server that handles the request. Pass `--region` to choose.

> **Co-locate stateful resources.** Put a service and the addons it talks to (Managed Postgres, Managed Redis, Managed Object Storage) in the **same region** to keep latency low and avoid cross-region data charges.

## Generated domains

Each service gets a generated domain with automatic TLS: `-.onlizard.com` for new services, while older services keep `..onlizard.com`. [Managed Object Storage](https://lizard.build/docs/addons/storage.md) uses a gateway host per region: `s3-us-east-1.onlizard.com` for `us-east-1` and `s3-eu-west-1.onlizard.com` for `eu-west-lim-a`. To attach your own hostname, see [Networking](https://lizard.build/docs/networking.md).


---
title: "My Dockerfile changes don't seem to apply"
description: "Why Lizard replaces a repo Dockerfile that has no build step, how the incomplete-Dockerfile rule works, and how to get yours used verbatim."
url: https://lizard.build/docs/deploy/troubleshooting/incomplete-dockerfile/
updated: 2026-10-10
---

# My Dockerfile changes don't seem to apply

You have a `Dockerfile` in your repo, you edited it, but the build Lizard runs doesn't match what you wrote.

## What this means

The platform decided your repo's Dockerfile was **incomplete** and regenerated one for you with [lizardpack](https://lizard.build/docs/concepts/build-pipeline.md) instead of using yours verbatim.

## Why this can happen

This check runs only for GitHub-source services. Uploads (`lizard up`) always use a root `Dockerfile` as written.

On GitHub source, Lizard treats a root Dockerfile as incomplete when both are true:

- it has a `COPY` of `build`, `dist`, `out`, `.next` or `public` written without a trailing slash, such as `COPY dist /usr/share/nginx/html`;
- it has no `RUN` line that starts with `npm`, `yarn`, `pnpm`, `bun`, `pip`, `pip3`, `bundle`, `cargo`, `go build`, `mvn` or `gradle`.

Lizard assumes those artifacts were built outside the image, and lizardpack generates a replacement Dockerfile that includes the missing build step.

Two other settings also bypass your repo's root Dockerfile. With `dockerfilePath` set, Lizard uses that file as written. Without it, a set `buildCommand` or `startCommand` makes Lizard synthesize a Dockerfile from those commands.

## Possible solutions

### Add a real build step

If you want your Dockerfile used as-is, give it an actual build step instead of copying pre-built output:

```dockerfile
RUN npm ci && npm run build
```

### Or force verbatim use

Set `dockerfilePath`. It takes priority over build and start commands for the image build, but a set `startCommand` still replaces your Dockerfile's `CMD` at run time, so clear both in the same update. An empty value clears a field:

```bash
lizard service set api --set buildCommand= --set startCommand= --set dockerfilePath=Dockerfile
```

## See also

- [Build Pipeline](https://lizard.build/docs/concepts/build-pipeline.md) — the full build decision order.
- [Build configuration reference](https://lizard.build/docs/concepts/build-pipeline.md#build-configuration-reference)
- [A minimal FastAPI container](https://lizard.build/blog/python-app-hosting#a-minimal-fastapi-container) — a complete Python Dockerfile, and when to bring your own instead of the source build.

Changing these build fields can start a build. Check `lizard events` before sending another `redeploy`, to avoid starting a second build.


---
title: "My change queued two builds back to back"
description: "Why lizard service set followed by redeploy queues two builds, which fields rebuild on their own, and how to stop the redundant one."
url: https://lizard.build/docs/deploy/troubleshooting/double-build/
updated: 2026-10-10
---

# My change queued two builds back to back

You ran `lizard service set` to change a field, then `lizard redeploy`, and ended up with two builds instead of one.

## What this means

The `service set` call already triggered a rebuild on its own. The follow-up `redeploy` queued a second, redundant one.

## Why this can happen

Changing a **build-affecting** field on a service — `repoUrl`, `branch`, `sourceType`, `buildCommand`, `dockerfilePath`, or `rootDirectory` — auto-rebuilds a running service immediately. A crashed or never-deployed service does not rebuild on its own. Changing a **runtime-only** field — `startCommand`, `preDeployCommand`, `containerPort`, `watchPatterns` — does **not** auto-rebuild; it takes effect only on the next deploy.

It's easy to reflexively chain a `redeploy` after every `service set`, but that only makes sense for the runtime-only fields.

## Possible solutions

### Check which kind of field you changed

See the [rebuild triggers table](https://lizard.build/docs/concepts/build-pipeline.md#what-triggers-a-rebuild). If it's a build field and the service was running, the rebuild is already queued — do nothing further. If the service was crashed or never deployed, run `lizard redeploy` once.

```bash
lizard events   # confirm only one build is in flight
```

### Only redeploy after runtime-only changes

```bash
lizard service set api --set startCommand="node server.js"
lizard redeploy --service api   # needed here — startCommand doesn't auto-rebuild
```

## See also

- [Build Pipeline → what triggers a rebuild](https://lizard.build/docs/concepts/build-pipeline.md#what-triggers-a-rebuild)
- [`lizard service`](https://lizard.build/docs/cli/service.md) — the full command reference.


---
title: "My service deploys but never becomes healthy"
description: "A build succeeds but the deploy stays unhealthy. Port binding, accidental worker mode, and the other causes of a failing health check."
url: https://lizard.build/docs/deploy/troubleshooting/service-never-healthy/
updated: 2026-10-10
---

# My service deploys but never becomes healthy

The build succeeds and a new pod starts, but the deploy is marked failed or the new version never receives traffic.

## What this means

The platform's health check — a TCP probe on your app's port — never passes, so traffic never shifts to the new pod. Lizard waits about 30 seconds, then marks the deploy failed. A service that already ran keeps serving its previous version; a first deploy shows as crashed.

## Why this can happen

- **Your app isn't listening on the expected port.** Lizard injects a `PORT` environment variable (default container port `3000`) and checks that your app is reachable there. If your app is hardcoded to a different port, or never binds one, the health check never passes. A Python app is the usual case: `gunicorn` and `uvicorn` bind `127.0.0.1` unless you tell them otherwise, and the probe comes from outside the process. [Python app hosting](https://lizard.build/blog/python-app-hosting#build-and-start-commands) has the `--bind 0.0.0.0:$PORT` line each framework needs.
- **The service is accidentally in worker mode.** Setting `containerPort=0` puts a service into [worker mode](https://lizard.build/docs/deploy/workers.md), which **skips the health check and load-balancer registration entirely**. A service that's supposed to serve HTTP but got flipped to `containerPort=0` will deploy "successfully" and just never serve anything — worker mode hides the underlying startup problem instead of surfacing it.

## Possible solutions

### Check the current port

```bash
lizard port --service api
```

If this prints `worker mode`, the service has `containerPort=0`. Set it back to a real port if this service is meant to serve HTTP:

```bash
lizard port 3000 --service api
lizard redeploy --service api
```

### Confirm your app is listening where Lizard expects

Read the runtime logs for startup errors and the address the app reports it listens on. Compare it with the service port, which Lizard passes to the app as `PORT`:

```bash
lizard logs --service api
lizard port --service api
```

## See also

- [Background Workers](https://lizard.build/docs/deploy/workers.md) — when `containerPort=0` is (and isn't) the right call.
- [Deployments → lifecycle](https://lizard.build/docs/concepts/deployments.md#lifecycle) — where the health check sits in a deploy.


---
title: "Variables & Secrets"
description: "Set secrets at service or project scope, learn the merge precedence, and see which values reach a build versus the running container."
url: https://lizard.build/docs/variables/
updated: 2026-10-10
---

# Variables & Secrets

Lizard stores configuration as **secrets**. They come in two scopes — **service** (the default) and **project** (`--global`) — and merge into your app's environment in a defined precedence order. There is no workspace-level global scope.

## Setting variables

`lizard secrets set` is variadic — set one or many at once. By default it writes to the **linked service**:

```bash
lizard secrets set DATABASE_URL=postgres://… LOG_LEVEL=info
lizard secrets set API_KEY=sk_live_… --service api
```

Add `--global` to write to **project** scope (every service in the project sees it):

```bash
lizard secrets set NODE_ENV=production --global
```

## Listing, deleting, importing

```bash
lizard secrets list                       # current scope
lizard secrets list --show                # reveal values
lizard secrets delete OLD_KEY ANOTHER_KEY # variadic; add -y in scripts
cat .env | lizard secrets import          # KEY=value lines from stdin
```

`secrets import` reads plain `KEY=value` lines and keeps quotes as part of the value. `secrets delete` asks for confirmation; pass `-y` when it runs outside a terminal.

See the [`lizard secrets` command reference](https://lizard.build/docs/cli/secrets.md) for every flag.

## Scoping

| Scope | Command | Stored as | Visible to |
|-------|---------|-----------|------------|
| **Service** (default) | `lizard secrets set K=v --service ` | `secrets.services[]` | that service only |
| **Project** (global) | `lizard secrets set K=v --global` | `secrets.shared` | every service in the project |

**Default to service scope.** A compromised service can read its own environment — broader scope means more credentials exposed for no reason. Reserve `--global` for non-secret, provably-public values like `LOG_LEVEL`, `NODE_ENV`, feature flags, or a frontend `SENTRY_DSN`. If you're unsure whether a value is a secret, treat it as one and scope it to the service.

Every running Postgres and Redis addon's `DATABASE_URL` and `REDIS_URL` already reach every service in the project, at the lowest precedence. To pick a specific addon or use its private URL, bind it per consumer with a [reference](https://lizard.build/docs/variables/references.md) instead of `--global`:

```bash
lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service api
lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service worker
```

If the addon's values change, each consumer picks up the new value on its next deploy or variable change.

## Precedence

When the same key is defined in multiple places, the **last writer wins**:

```
addon-issued env  <  project secrets  <  project env  <  app env  <  app secrets  <  platform vars
```

So **app secrets override project secrets**, and **platform variables** (`PORT`, `LIZARD_SERVICE_NAME`, `LIZARD_PROJECT_ID`, `LIZARD_PUBLIC_DOMAIN`, `LIZARD_PRIVATE_DOMAIN`) are applied last and cannot be shadowed. `PORT` is set only when the service has an HTTP port. Setting `PORT` yourself changes the port Lizard routes to on the next deploy, unless the port was set with `lizard port`.

## Build-time vs. runtime

Most variable changes apply with **no rebuild** — the service restarts to pick them up, which takes a few seconds. The exceptions are build-time values baked into the image:

- Changing a `VITE_*` or `NEXT_PUBLIC_*` value starts a rebuild of the affected running services right away (every running service for `--global`).
- Lizard bakes these values in as `ENV` lines for GitHub services (the Dockerfile lizardpack generates, or the repo's root Dockerfile) and for Node services built from `buildCommand` / `startCommand`. A service with a custom `dockerfilePath`, or one uploaded with `lizard up` without those commands, gets no Lizard variables at build time; no service gets build args except `COMMIT_SHA`. For public values in those cases, commit them in `.env.production`, set a build command, or write them into your Dockerfile.

See [Build Pipeline → rebuild triggers](https://lizard.build/docs/concepts/build-pipeline.md#what-triggers-a-rebuild).

## Verifying

Shell access to services (`lizard ssh`) is turned off, so check the values another way:

```bash
lizard logs --build --service api          # warns about every empty ${{…}} reference
lizard secrets list --service api --show   # stored values
lizard secrets list --service api --ref    # reference templates you can use
```

If a value you expected isn't there, see [Troubleshooting → a secret or reference isn't showing up](https://lizard.build/docs/variables/troubleshooting/reference-not-applied.md).

## Local development

Run a command **locally** with the service's project and service secrets injected:

```bash
lizard run --service api -- node scripts/seed.js
```

Values arrive as stored: `${{…}}` references stay literal text, and platform variables (`PORT`, `LIZARD_*`) are not injected. For a script that needs the database, run it with the addon's real connection values:

```bash
lizard run --service postgres -- node scripts/seed.js
```

See [what `lizard run` injects](https://lizard.build/docs/cli/run.md#what-run-injects).

## See also

- [Cross-Service References](https://lizard.build/docs/variables/references.md) — read one service's values from another without copy-pasting credentials.
- [`lizard secrets`](https://lizard.build/docs/cli/secrets.md) — the full command reference.


---
title: "Cross-Service References"
description: "Read one service or addon's values from another with ${{name.KEY}} syntax. How references resolve at deploy time and why they beat copying."
url: https://lizard.build/docs/variables/references/
updated: 2026-10-10
---

# Cross-Service References

References let a service read another service's or addon's values without copy-pasting credentials. They keep each value in one place; consumers pick up changes on their next deploy.

## Syntax

```
${{.}}
```

- `` — the target service or addon name (the one shown in the dashboard and used in `lizard add -n`).
- `` — a key in the target's merged environment.

Use them anywhere a value is accepted — most commonly in a secret:

```bash
lizard secrets set DATABASE_URL='${{postgres.DATABASE_URL}}' --service api
```

## How they resolve

- References resolve at **deploy time** against the target's merged environment.
- They match the target's **current name**. Renaming the target breaks them; update every `${{oldname.KEY}}` after a rename.
- A reference to a **missing** target or key resolves to an **empty string** — it does *not* fail the deploy. The build log prints a warning for each empty reference with the reason.
- Only **circular** references throw an error.

> After wiring a reference, check the build log for empty ones:
> ```bash
> lizard logs --build --service api
> ```

If a reference isn't showing up the way you expect, see [Troubleshooting → a secret or reference isn't showing up](https://lizard.build/docs/variables/troubleshooting/reference-not-applied.md).

## Common patterns

**Wire an addon to several services** — bind on each consumer; use the private URL inside the project:

```bash
lizard secrets set REDIS_URL='${{redis.REDIS_PRIVATE_URL}}' --service api
lizard secrets set REDIS_URL='${{redis.REDIS_PRIVATE_URL}}' --service worker
```

**Reference another service's value** — e.g. share a computed URL or token. `LIZARD_PUBLIC_DOMAIN` is a bare hostname and exists once the service has a domain; `LIZARD_PRIVATE_DOMAIN` (`.lizard.internal`) reaches the service inside the project:

```bash
lizard secrets set UPSTREAM_URL='https://${{api.LIZARD_PUBLIC_DOMAIN}}' --service web
lizard secrets set INTERNAL_API='http://${{api.LIZARD_PRIVATE_DOMAIN}}:8080' --service web
```

**Reference a second addon of the same type** — use its generated name:

```bash
lizard secrets set CACHE_URL='${{redis-winter-otter.REDIS_URL}}' --service api
```

## Why not just use `--global`?

Putting a shared value in project (`--global`) scope exposes it to *every* service, including ones that don't need it. A reference keeps the value in one place and binds it only where you set it. Note that every running Postgres and Redis addon's `DATABASE_URL` and `REDIS_URL` reach every service anyway, at the lowest precedence; a reference overrides them. See [Secret scoping](https://lizard.build/docs/variables.md#scoping).

## See also

- [Variables & Secrets](https://lizard.build/docs/variables.md) — scopes and precedence.
- [`lizard secrets`](https://lizard.build/docs/cli/secrets.md) — the full command reference.


---
title: "A secret or reference isn't showing up in my service"
description: "Your secret or reference is set but the service never sees it. Missing targets resolve to empty strings, and precedence can shadow a value."
url: https://lizard.build/docs/variables/troubleshooting/reference-not-applied/
updated: 2026-10-10
---

# A secret or reference isn't showing up in my service

You set a secret or a `${{name.KEY}}` reference, redeployed, and the running service still doesn't see the value you expect.

## What this means

The value you set never made it into the service's merged environment — or something else in the merge overwrote it before the app started.

## Why this can happen

- **The reference target or key doesn't exist.** A reference to a missing service/addon name, or a key that addon doesn't expose, resolves to an **empty string** instead of failing the deploy. The build log prints a warning for each empty reference with the reason; a variable change applied by restart, with no build, shows no warning. A renamed target counts as missing: references match the current name.
- **Something with higher precedence overrode it.** Values merge in a fixed order — `addon-issued env < project secrets < project env < app env < app secrets < platform vars` — so a project-scoped (`--global`) secret can be silently shadowed by a service-scoped one with the same key, and platform variables (`PORT`, `LIZARD_SERVICE_NAME`, `LIZARD_PROJECT_ID`, `LIZARD_PUBLIC_DOMAIN`, `LIZARD_PRIVATE_DOMAIN`) always win. Setting `PORT` yourself changes the port Lizard routes to instead, unless the port was set with `lizard port`.
- **A build-time value didn't reach the build.** Most variable changes apply with no rebuild — the service restarts to pick them up. `VITE_*` and `NEXT_PUBLIC_*` values are baked into the built assets, and changing one starts a rebuild of running services right away. A service that was crashed or stopped at the time is skipped, and a service with a custom `dockerfilePath` or uploaded with `lizard up` without build/start commands never gets these values at build time.

## Possible solutions

### Check the build log for empty references

Shell access to services (`lizard ssh`) is turned off. The build log is the place to look: each empty reference gets a line like `==> Warning: ... is empty` with the reason.

```bash
lizard logs --build --service api
```

Then list the reference templates that are valid in this scope, and compare the name and key with yours. Check keys against the addon's [documented variables](https://lizard.build/docs/addons.md):

```bash
lizard secrets list --service api --ref
```

### Check for a scope collision

List secrets in both scopes and compare:

```bash
lizard secrets list --service api --show
lizard secrets list --global --show
```

Remember **app secrets override project secrets** — if a service-scoped value exists for the same key, it wins over anything set with `--global`.

### Rebuild after a build-time change

If the changed key is `VITE_*` or `NEXT_PUBLIC_*` and the service was crashed or stopped when you changed it, trigger a rebuild:

```bash
lizard redeploy --service api
```

See [Build-time vs. runtime](https://lizard.build/docs/variables.md#build-time-vs-runtime) and [Build Pipeline → what triggers a rebuild](https://lizard.build/docs/concepts/build-pipeline.md#what-triggers-a-rebuild).

## See also

- [Variables & Secrets](https://lizard.build/docs/variables.md) — scoping and precedence.
- [Cross-Service References](https://lizard.build/docs/variables/references.md) — reference syntax and resolution.


---
title: "Managed Addons: Postgres, Redis and Object Storage"
description: "Provision managed Postgres, Redis, and S3-compatible storage with one lizard add command, then wire them into services by reference."
url: https://lizard.build/docs/addons/
updated: 2026-10-12
---

# Managed Addons

Lizard runs three managed addons in your project: Managed Postgres, Managed Redis, and S3-compatible Managed Object Storage. `lizard add postgres redis s3` creates all three in one command. Each addon exposes a fixed set of environment variables, such as `DATABASE_URL`, and your services read them through [references](https://lizard.build/docs/concepts/architecture.md#cross-resource-references) such as `${{postgres.DATABASE_URL}}`. Use them when an app needs data that outlives a deploy or a restart.

## Provision an addon

```bash
lizard add postgres            # one addon
lizard add postgres redis s3   # several at once
lizard add --list              # show available types
```

## Naming

The first addon of each type uses the type as its name, so `${{postgres.DATABASE_URL}}` works without extra setup. Additional addons of the same type get a generated name like `postgres-autumn-bear`. A deleted addon keeps its name in the trash for 3 days, so a new addon added in that window gets a generated name; restore the old one or point references at the new name.

There's no type-alias fallback: a reference must use the addon's **current** name. Renaming an addon breaks references to the old name, so update them after a rename:

```bash
lizard service rename main-db --service postgres   # addons rename through the same command
lizard secrets set DATABASE_URL='${{main-db.DATABASE_URL}}' --service api
```

## Consuming an addon

Reference an addon's variables from any service. The reference resolves at deploy time, so the service picks up the addon's current values on its next deploy:

```bash
lizard secrets set DATABASE_URL='${{postgres.DATABASE_URL}}' --service api
lizard redeploy --service api
```

Lizard also injects `DATABASE_URL` and `REDIS_URL` from every running Postgres and Redis addon into every service in the project, at the lowest precedence. An explicit reference overrides them and picks a specific addon. For services in the same project, prefer the private URL:

```bash
lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service api
```

See [Secret scoping](https://lizard.build/docs/variables.md#scoping).

## Variables by type

| Addon | Variables |
|-------|-----------|
| **postgres** | `DATABASE_URL`, `DATABASE_PRIVATE_URL`, `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `POSTGRES_USER`, `POSTGRES_DB`, `POSTGRES_PASSWORD` |
| **redis** | `REDIS_URL`, `REDIS_PRIVATE_URL` |
| **s3** | `S3_ENDPOINT`, `S3_DEFAULT_BUCKET`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_REGION` |

`DATABASE_URL` and `REDIS_URL` use the public endpoint. `DATABASE_PRIVATE_URL` and `REDIS_PRIVATE_URL` use `.lizard.internal` on the [private network](https://lizard.build/docs/networking.md#private-networking) and work only from services in the same project.

## Dashboard browsers

Inspect and edit addon data in the [dashboard](https://lizard.build/docs/dashboard.md). It has a SQL/table editor for Managed Postgres, a key browser for Managed Redis, and a bucket/object browser for Managed Object Storage.

## Storage

Postgres and Redis data volumes are **grow-only**. Increase capacity with:

```bash
lizard scale --service postgres --storage 8192
```

## Per-type guides

- [Managed Postgres](https://lizard.build/docs/addons/postgres.md)
- [Managed Redis](https://lizard.build/docs/addons/redis.md)
- [Managed Object Storage](https://lizard.build/docs/addons/storage.md)

For help choosing data services and setting their variables, see [make data and configuration explicit](https://lizard.build/blog/google-antigravity-how-to-deploy-your-app-to-production#make-data-and-configuration-explicit).


---
title: "Managed Postgres: connection string and setup"
description: "Add a PostgreSQL 18 database on Lizard, find its connection string and PG variables, connect services by reference, run migrations and grow storage."
url: https://lizard.build/docs/addons/postgres/
updated: 2026-10-12
---

# Managed Postgres

Managed Postgres on Lizard is a PostgreSQL database that you add to a project with `lizard add postgres`. New databases run PostgreSQL 18 by default. Lizard stores the connection string in `DATABASE_URL` and its parts in `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD` and `PGDATABASE`. Connect services with a reference such as `${{postgres.DATABASE_PRIVATE_URL}}`. The database uses the same CPU, memory and storage [rates](https://lizard.build/docs/platform/billing.md#resource-rates) as apps. You can query it in the dashboard's SQL editor. For an overview and prices, see the [Managed Postgres page](https://lizard.build/postgres).

## Provision

```bash
lizard add postgres
```

The first Postgres addon is named `postgres`, so `${{postgres.DATABASE_URL}}` works immediately.

## Environment variables

The addon exposes a standard set of connection variables:

| Variable | Description |
|----------|-------------|
| `DATABASE_URL` | Full connection string over the public endpoint (`postgresql://…`) |
| `DATABASE_PRIVATE_URL` | Connection string over the project's private network (`.lizard.internal:5432`) |
| `PGHOST` | Host |
| `PGPORT` | Port |
| `PGUSER` | User |
| `PGPASSWORD` | Password |
| `PGDATABASE` | Database name |
| `POSTGRES_USER` | Alias for the user |
| `POSTGRES_DB` | Alias for the database |
| `POSTGRES_PASSWORD` | Alias for the password |

## Postgres connection string

Each database has two connection strings. Both use the user `postgres` and the database `postgres`:

| Variable | Use it from | Format |
|---|---|---|
| `DATABASE_PRIVATE_URL` | Services in the same project | `postgresql://postgres:@.lizard.internal:5432/postgres` |
| `DATABASE_URL` | Your machine, CI, or a service in another project | `postgresql://postgres:@:/postgres` |

`` is the database's name in the project, `postgres` for the first one. Lizard assigns the public host and port when it creates the database, so read them from `DATABASE_URL`, `PGHOST` and `PGPORT` rather than typing them. The private address works only inside the project; see [private networking](https://lizard.build/docs/networking.md#private-networking).

Most drivers take the string as is:

```js
// Node.js with pg
import pg from 'pg';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
```

```python
# Python with psycopg
import os, psycopg
conn = psycopg.connect(os.environ["DATABASE_URL"])
```

`psql` and other libpq tools read the `PG*` variables, so they connect with no arguments.

### How do I see the connection string?

- **Dashboard:** open the database in your project. The **Overview** tab shows `DATABASE_URL` and the private URL with copy buttons. The **Secrets** tab lists every connection variable and the `${{…}}` reference to paste into a service.
- **CLI:** `lizard run --service postgres -- printenv DATABASE_URL` prints the public string. It contains the password, so do not paste it into logs or chat.

The string is a credential. Point services at it with a reference instead of copying it into a secret, so they get the current value on every deploy.

## Connect a service

Reference the private connection string from the consumer service and redeploy:

```bash
lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service api
lizard redeploy --service api
```

The reference resolves at deploy time, so every consumer picks up the addon's current values on its next deploy. Use `DATABASE_URL` instead for clients outside the project. Every service in the project also gets the addon's public `DATABASE_URL` at the lowest precedence; an explicit reference overrides it.

Verify connectivity from your machine without printing the connection string:

```bash
lizard run --service postgres -- sh -c 'psql "$DATABASE_URL" -c "SELECT 1"'
```

`lizard run --service postgres` runs the command on your machine with the database's public connection values, so it needs `psql` installed locally. `lizard run --service api` would pass `${{…}}` references through as literal text; see [`lizard run`](https://lizard.build/docs/cli/run.md).

## Run migrations on deploy

Use a pre-deploy command for migrations. It runs only when a start command is also set, as `sh -c "
 && exec "` at the start of every container: each replica, each restart. Make migrations safe to run many times and at the same time, and compatible with both the old and new application version:

```bash
lizard service set api --set preDeployCommand="npm run migrate" --set startCommand="npm start"
lizard redeploy --service api
```

For a Django app the command is `python manage.py migrate`; see the [Django guide](https://lizard.build/docs/framework-guides/django.md) and [Python app hosting](https://lizard.build/blog/python-app-hosting#django-postgres-and-celery-need-separate-checks). The [Umami](https://lizard.build/docs/guides/umami.md) and [Flowise](https://lizard.build/docs/guides/flowise.md) guides show complete apps on Managed Postgres.

## Browse and query data

Open the **Postgres editor** in the dashboard (`lizard open`) to run SQL and browse tables directly. For ad-hoc local work, run a command with the addon's connection values:

```bash
lizard run --service postgres -- sh -c 'exec psql "$DATABASE_URL"'
```

## Grow storage

Data volumes are grow-only:

```bash
lizard scale --service postgres --storage 8192
```

## Multiple databases

Adding a second Postgres gives it a generated name (e.g. `postgres-autumn-bear`). Reference it explicitly:

```bash
lizard add postgres
lizard secrets set ANALYTICS_URL='${{postgres-autumn-bear.DATABASE_PRIVATE_URL}}' --service api
```

## Backups and restore

Do not assume scheduled backups or point-in-time recovery. Before a risky migration, export the database yourself and test the restore in a separate database. For example, with `pg_dump` 18 or newer installed locally:

```bash
lizard run --service postgres -- sh -c 'pg_dump "$DATABASE_URL" > backup.sql'
```

See [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md) for a restore plan.

## See also

- [Managed Addons](https://lizard.build/docs/addons.md): naming, references, and the dashboard browsers.
- [Cross-Service References](https://lizard.build/docs/variables/references.md): reference syntax and resolution.


---
title: "Managed Redis: connection URL and durability"
description: "Add a Redis 8 instance on Lizard for caching, queues, rate limiting and pub/sub, connect it by reference, and learn what happens when memory runs out."
url: https://lizard.build/docs/addons/redis/
updated: 2026-10-12
---

# Managed Redis

Managed Redis on Lizard is a Redis server for caching, queues, rate limiting and pub/sub. New instances run Redis 8 by default. Add one with `lizard add redis`; your services read `REDIS_PRIVATE_URL` or `REDIS_URL` through a reference such as `${{redis.REDIS_PRIVATE_URL}}`. It has no eviction policy and no append-only file, so keep only data you can rebuild in it. For an overview and prices, see the [Managed Redis page](https://lizard.build/redis).

## Provision

```bash
lizard add redis
```

The first Redis addon is named `redis`, so `${{redis.REDIS_URL}}` works immediately.

## Environment variables

| Variable | Description |
|----------|-------------|
| `REDIS_URL` | Full connection string over the public endpoint (`redis://…`) |
| `REDIS_PRIVATE_URL` | Connection string over the project's private network (`.lizard.internal:6379`) |

Both strings use the form `redis://default:@:`. The private one works only from services in the same project; see [private networking](https://lizard.build/docs/networking.md#private-networking).

## Connect a service

```bash
lizard secrets set REDIS_URL='${{redis.REDIS_PRIVATE_URL}}' --service api
lizard redeploy --service api
```

The reference resolves at deploy time; consumers pick up the addon's current values on their next deploy. Use `REDIS_URL` for clients outside the project. Every service in the project also gets the addon's public `REDIS_URL` at the lowest precedence; an explicit reference overrides it.

## Does Managed Redis keep data?

Managed Redis can lose recent writes. It has no eviction policy and no append-only file. If it runs out of memory, the instance restarts and may lose those writes. Use it for data you can rebuild, and keep data you cannot lose in [Managed Postgres](https://lizard.build/docs/addons/postgres.md).

## Common uses

- **Cache**: store computed results keyed by request.
- **Queue / worker**: pair Redis with a [worker-mode service](https://lizard.build/docs/deploy/workers.md) that consumes jobs. [Run a queue consumer](https://lizard.build/docs/deploy/workers.md#run-a-queue-consumer) walks through the redis-worker example.
- **Rate limiting / sessions**: fast shared state across [replicas](https://lizard.build/docs/deploy/scaling.md).

```bash
# A worker that drains a Redis queue
lizard add -r your-org/worker -n jobs --no-deploy
lizard service set jobs --set containerPort=0
lizard secrets set REDIS_URL='${{redis.REDIS_PRIVATE_URL}}' --service jobs
lizard redeploy --service jobs
```

## Browse keys

Open the **Redis browser** in the dashboard (`lizard open`) to inspect keys and values. For local access with the addon's connection values:

```bash
lizard run --service redis -- sh -c 'exec redis-cli -u "$REDIS_URL"'
```

## Grow storage

```bash
lizard scale --service redis --storage 4096
```

## See also

- [Managed Addons](https://lizard.build/docs/addons.md): naming, references, and the dashboard browsers.
- [Background Workers](https://lizard.build/docs/deploy/workers.md): pairing Redis with a queue-consuming worker.

`lizard run --service redis` starts the command on your machine with the addon's public connection values. Install `redis-cli` locally.


---
title: "Managed Object Storage: S3-compatible buckets"
description: "S3-compatible object storage for uploads, assets, and backups. Get a private default bucket and connection variables from one lizard add s3."
url: https://lizard.build/docs/addons/storage/
updated: 2026-10-12
---

# Managed Object Storage

Managed Object Storage on Lizard is S3-compatible storage for uploads, assets, and backups. `lizard add s3` creates a private bucket named `default` and five connection variables, from `S3_ENDPOINT` to `S3_REGION`. Use them with any AWS SDK and path-style addressing. Each account can store up to 16 GiB, and you pay for the bytes you store. To serve files without authentication, make the bucket public from the dashboard's S3 browser. For an overview and prices, see the [Managed Object Storage page](https://lizard.build/object-storage).

## Provision

```bash
lizard add s3
```

The first Managed Object Storage addon is named `s3`, so references such as `${{s3.S3_ENDPOINT}}` work immediately.

## Environment variables

| Variable | Description |
|----------|-------------|
| `S3_ENDPOINT` | S3-compatible endpoint URL |
| `S3_DEFAULT_BUCKET` | The auto-created bucket name (`default`) |
| `S3_ACCESS_KEY_ID` | Access key |
| `S3_SECRET_ACCESS_KEY` | Secret key |
| `S3_REGION` | Region |

## Connect a service

```bash
lizard secrets set \
  S3_ENDPOINT='${{s3.S3_ENDPOINT}}' \
  S3_DEFAULT_BUCKET='${{s3.S3_DEFAULT_BUCKET}}' \
  S3_ACCESS_KEY_ID='${{s3.S3_ACCESS_KEY_ID}}' \
  S3_SECRET_ACCESS_KEY='${{s3.S3_SECRET_ACCESS_KEY}}' \
  S3_REGION='${{s3.S3_REGION}}' \
  --service api
lizard redeploy --service api
```

## Using an AWS SDK

The endpoint is S3-compatible. Set **path-style** addressing:

```ts
import { S3Client } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: process.env.S3_ENDPOINT,
  region: process.env.S3_REGION,
  forcePathStyle: true, // required
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID!,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
  },
});
```

## Public files

Buckets are private by default. Once you make a bucket public, anyone can read its objects through either address:

1. **Gateway URL** (shown in the dashboard):
   ```
   https://s3-.onlizard.com///
   ```
   `` is the value of `S3_REGION` (`eu-west-1` or `us-east-1`).
2. **Platform proxy** (the dashboard host, with long-lived immutable cache headers):
   ```
   /api/s3//public//
   ```

Once you make the `default` bucket public, anyone can read its objects at:

```
/api/s3//public/default/
```

## Browse objects

Open the **S3 browser** in the dashboard (`lizard open`) to upload and delete objects, create folders, and make buckets public or private. From the CLI, `lizard s3 upload ` uploads a file and `lizard s3 list` lists objects; see [`lizard s3`](https://lizard.build/docs/cli/s3.md).

> **Bucket ACL changes** (making a bucket public/private) require the dashboard; the CLI does not support them yet.

## Storage limit

Each account can store up to 16 GiB of objects across its Managed Object Storage addons. You pay for stored bytes, not a reserved size; `lizard scale --storage` does not change this limit.

## See also

- [Managed Addons](https://lizard.build/docs/addons.md): naming, references, and the dashboard browsers.
- [Cross-Service References](https://lizard.build/docs/variables/references.md): reference syntax and resolution.


---
title: "Sandboxes for AI agents"
description: "Run AI-generated code in Firecracker microVMs with Lizard SDK or Lizard CLI: when to use Sandboxes, startup time, templates, sizes, lifecycle and storage."
url: https://lizard.build/docs/sandboxes/
updated: 2026-10-12
---

# Sandboxes for AI agents

Sandboxes are isolated Linux machines for running code and AI agent tasks on Lizard. Each sandbox is a Firecracker microVM with its own kernel. Create one with Lizard SDK (`@lizard-build/sdk` for TypeScript, `lizard-sdk` for Python) or run `lizard sandbox create` in a terminal. You can then run commands, read and write files, and open ports. In a test on 2026-10-12, the median time from creating a sandbox to its first Python output was 228 ms.

## When to use Sandboxes

- **Run code a model wrote.** Give an LLM a [code interpreter](https://lizard.build/docs/sandboxes/code-interpreter.md) that keeps Python state between calls.
- **Give a coding agent its own machine.** Run [Claude Code](https://lizard.build/docs/sandboxes/guides/claude-code.md), [OpenCode](https://lizard.build/docs/sandboxes/guides/opencode.md) or Codex in a sandbox instead of on your laptop. Connect its credentials with [`lizard agents`](https://lizard.build/docs/cli/agents.md) and send prompts with [`lizard sandbox prompt`](https://lizard.build/docs/cli/sandbox.md#coding-agents).
- **Preview what the agent built.** Start a dev server and open it through a [private preview URL](https://lizard.build/docs/sandboxes/preview-urls.md).
- **Try several paths at once.** Fork a running sandbox, memory and processes included, or restore many copies from one snapshot.
- **Drive a browser.** The `desktop` template runs a graphical desktop that an agent controls with screenshots, clicks and keys.

For a web service or worker that should keep running, deploy an app instead; see the [app quickstart](https://lizard.build/docs/getting-started.md).

## How fast does a sandbox start?

In a test on 2026-10-12, the median time from creating a sandbox to its first Python output was 228 ms, and the 95th percentile was 310 ms. The test ran 300 times from Railway's US East region against `us-east-1`, with the `base` template and the Small size. Results of the same test for E2B, Daytona and Modal are on the [Sandboxes page](https://lizard.build/sandboxes).

## Sandboxes at a glance

| Question | Answer |
|---|---|
| What runs the code? | A Firecracker microVM with its own kernel; see the exception under [runtime and templates](#runtime-and-templates) |
| How do I create one? | [Lizard SDK](https://lizard.build/docs/sandboxes/sdk-reference.md), [`lizard sandbox`](https://lizard.build/docs/cli/sandbox.md), the [dashboard](https://lizard.build/docs/sandboxes/dashboard.md), or the [HTTP API](https://lizard.build/docs/sandboxes/sdk-reference.md#http-api) |
| Which templates exist? | `base`, `interpreter`, `codex`, `claude`, `opencode`, `pi`, `prime` and `desktop` |
| What sizes are there? | Small (2 vCPU / 4 GB), Medium (4 vCPU / 8 GB, the default) and Large (8 vCPU / 16 GB); see [sizes and prices](https://lizard.build/docs/platform/limits.md#sandbox-sizes) |
| How long does one live? | Five minutes by default from the SDK and CLI; no expiration by default from the raw API and dashboard |
| What survives a stop? | Only files on a [Persistent Volume](https://lizard.build/docs/sandboxes/volumes.md) mounted at `/workspace` |
| How many can run at once? | 100 on Pro, 5 during the trial; see [account limits](https://lizard.build/docs/platform/limits.md#sandbox-account-limits) |

## Runtime and templates

Choose a template from the current catalog: `base`, `interpreter`, `codex`, `claude`, `opencode`, `pi`, `prime`, or `desktop`. Availability depends on the region and pool capacity. `interpreter` includes Python and common data libraries. `desktop` runs a graphical desktop that you drive with `sandbox.desktop` or `lizard sandbox desktop`. Check tool versions inside your sandbox. The public CLI does not provide a custom-template upload flow.

A sandbox that attaches a pod-backed Persistent Volume, or restores a pod snapshot, runs as a Kubernetes pod with the runc runtime and shares the host kernel. `getInfo().runtime` tells you which. Restrict credentials and network access when running code you do not trust.

## Lifecycle

Create a sandbox, run commands, then call `kill`. `connect` attaches to an existing sandbox without changing its state, so it does not resume a paused or stopped sandbox. Pause, resume, snapshot, and restore are available; see [Pause, resume, and snapshots](https://lizard.build/docs/sandboxes/quickstart.md#pause-and-resume). `fork` copies a running sandbox, memory and processes included; it returns 409 when a volume is attached and 501 for a sandbox that runs as a pod. File watching is not available yet. `lizard sandbox ssh` opens an interactive shell with your CLI credentials. SSH protocol access is not available, so SSH clients, `scp`, and port forwarding do not work.

```text
create -> running -> stopped (kill / expiration)
running -> paused -> running (pause / resume)
running -> new sandboxes (fork)
snapshot -> new sandbox (restore)
```

## Resources and timeouts

Choose a size when you create a sandbox: Small (2 vCPU / 4 GB), Medium (4 vCPU / 8 GB, the default), or Large (8 vCPU / 16 GB). See [sizes and prices](https://lizard.build/docs/platform/limits.md#sandbox-sizes). Select a region explicitly when needed; a catalog entry does not guarantee free capacity.

The SDK and CLI default to a five-minute lifetime. The raw API and dashboard default to no expiration. Set an explicit lifetime: `timeoutMs: 0` at creation disables expiration; a positive value sets a deadline. `setTimeout` accepts 1000 to 2147483647 ms, not zero. Commands do not reset the deadline. This is not an idle timer.

## Storage and billing

Files and running processes remain available while the sandbox runs. After it stops, only files written to an attached Persistent Volume at `/workspace` can be reused by a later sandbox. Without a volume, `/workspace` is temporary. Each volume attaches to one sandbox at a time and uses node-local storage; export files that must survive node loss.

Each size has a flat hourly price, billed by the second while the sandbox runs, idle time included. Egress is included up to [2 TB per account](https://lizard.build/docs/platform/limits.md#sandbox-egress) over a rolling 30 days. A sandbox restored from a snapshot runs at its source's size and price; one restored from a pod snapshot is billed by measured usage. A paused sandbox is not billed; Persistent Volumes bill separately while you keep the data. Pause or kill a sandbox when the task ends. Rates for every resource are on the [billing page](https://lizard.build/docs/platform/billing.md) and [pricing](https://lizard.build/pricing).

## Guides

- [Run Claude Code in a sandbox](https://lizard.build/docs/sandboxes/guides/claude-code.md)
- [Run OpenCode in a sandbox](https://lizard.build/docs/sandboxes/guides/opencode.md)
- [Preview URLs for a dev server](https://lizard.build/docs/sandboxes/preview-urls.md)
- [Code interpreter for LLM-generated code](https://lizard.build/docs/sandboxes/code-interpreter.md)
- [Keep files with Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md)

## Reference

- [Sandboxes quickstart](https://lizard.build/docs/sandboxes/quickstart.md): your first sandbox, step by step.
- [Sandbox API and SDK reference](https://lizard.build/docs/sandboxes/sdk-reference.md): every call in TypeScript and Python.
- [`lizard sandbox`](https://lizard.build/docs/cli/sandbox.md): create, exec, shell, snapshots and coding agents from the terminal.
- [Sandboxes dashboard](https://lizard.build/docs/sandboxes/dashboard.md): create, inspect and stop sandboxes in the browser.
- [Limits](https://lizard.build/docs/platform/limits.md#sandboxes): sizes, prices, concurrency and egress.


---
title: "Sandboxes quickstart: run code from Python or JavaScript"
description: "Create your first sandbox with Lizard SDK in TypeScript or Python: set an API key, run a command, write a file, open a port, reconnect, pause and clean up."
url: https://lizard.build/docs/sandboxes/quickstart/
updated: 2026-10-12
---

# Sandboxes quickstart

Use Lizard SDK to create a sandbox, run Python, write a file and open a port, then stop the sandbox. Each step has TypeScript and Python code. You need a Lizard account on Pro or the trial, an API key and a project. To learn what Sandboxes are first, read the [overview](https://lizard.build/docs/sandboxes.md).


## Install and authenticate

Install the Lizard SDK and create an API key in Settings → API keys, or with [`lizard keys create `](https://lizard.build/docs/cli/keys.md). Store it in `LIZARD_API_KEY`. Every sandbox belongs to a project; the examples pass its ID as `projectId` (Python: `project_id`), and `project` also accepts a slug or name.

```sh
npm install @lizard-build/sdk
pip install lizard-sdk
export LIZARD_API_KEY=""
```

## Run a command

`Sandbox.create` boots a sandbox from a template, here `interpreter`, which has Python and common data libraries. `timeoutMs` sets its lifetime; the SDK default is five minutes.

```ts
import { Sandbox } from '@lizard-build/sdk';

const sandbox = await Sandbox.create('interpreter', {
  projectId: 'proj_123', timeoutMs: 300_000,
});
try {
  const result = await sandbox.process.exec("python -c 'print(2 ** 10)'");
  console.log(result.stdout, result.stderr, result.exitCode);
} finally {
  await sandbox.kill();
}
```

```python
from lizard import Sandbox

sandbox = Sandbox.create("interpreter", project_id="proj_123", timeout_ms=300_000)
try:
    result = sandbox.process.exec_("python -c 'print(2 ** 10)'")
    print(result.stdout, result.stderr, result.exit_code)
finally:
    sandbox.kill()
```

A nonzero exit code is a result, not an error: check `exitCode` (Python: `exit_code`) yourself.

## Files

Write text or bytes with `fs.write`, read text with `fs.read`, and read bytes with `fs.readBytes` (Python: `read_bytes`). Files live only as long as the sandbox, unless you write them to a [Persistent Volume](https://lizard.build/docs/sandboxes/volumes.md) at `/workspace`.

```ts
await sandbox.fs.write("/tmp/task.py", "print(42)\n");
const result = await sandbox.process.exec("python /tmp/task.py");
console.log(result.stdout);
```

```python
sandbox.fs.write("/tmp/task.py", "print(42)\n")
result = sandbox.process.exec_("python /tmp/task.py")
print(result.stdout)
```

## Public ports

Start a server that listens on `0.0.0.0`, then call `exposePort(port)` (Python: `expose_port`). It returns `url`, `hostname`, and `accessToken`. The port is private: open `url` in a browser (it carries `?lizard_token=`), or send the token in the `X-Lizard-Access-Token` header. Requests without the token get 401. A stopped sandbox no longer serves the port. See [preview URLs](https://lizard.build/docs/sandboxes/preview-urls.md) for a dev server an agent built.

```ts
await sandbox.process.exec(
  "nohup python -m http.server 3000 --bind 0.0.0.0 --directory /tmp >/tmp/http.log 2>&1 /tmp/http.log 2>&1 `, `lizard sandbox resume `, `lizard sandbox snapshot  --name my-app`, and `lizard sandbox restore `.

## Clean up and next steps

Call `kill()` when the task ends, as the examples do in `finally`. In Python, `with Sandbox.create(...) as sandbox:` kills the sandbox when the block exits. A sandbox you forget stops at its deadline, five minutes after creation unless you set another lifetime; see [resources and timeouts](https://lizard.build/docs/sandboxes.md#resources-and-timeouts).

- Every call, option and error: [Sandbox API and SDK reference](https://lizard.build/docs/sandboxes/sdk-reference.md).
- Code from an LLM with state between calls: [code interpreter](https://lizard.build/docs/sandboxes/code-interpreter.md).
- Files that outlive a sandbox: [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md).
- A coding agent in a sandbox: [Claude Code](https://lizard.build/docs/sandboxes/guides/claude-code.md) or [OpenCode](https://lizard.build/docs/sandboxes/guides/opencode.md).
- The same steps from a terminal: [`lizard sandbox`](https://lizard.build/docs/cli/sandbox.md). In the browser: the [Sandboxes dashboard](https://lizard.build/docs/sandboxes/dashboard.md).
- Sizes, prices and how many sandboxes can run at once: [limits](https://lizard.build/docs/platform/limits.md#sandboxes). Product overview and comparison: [Sandboxes](https://lizard.build/sandboxes).


---
title: "Sandbox API and SDK reference"
description: "Lizard SDK reference for TypeScript and Python: create options, commands, files, ports, pause, snapshots, fork, errors and the HTTP API, with code examples."
url: https://lizard.build/docs/sandboxes/sdk-reference/
updated: 2026-10-12
---

# Sandbox API and SDK reference

Lizard SDK is the client for Sandboxes on Lizard: `@lizard-build/sdk` on npm for TypeScript and JavaScript, and `lizard-sdk` on PyPI for Python, imported as `lizard`. Both call the same [HTTP API](#http-api). Find each sandbox call below under its TypeScript and Python names, with examples in both languages. For a guided first run, use the [quickstart](https://lizard.build/docs/sandboxes/quickstart.md); for what Sandboxes are, read the [overview](https://lizard.build/docs/sandboxes.md).

## Install and authenticate

```sh
npm install @lizard-build/sdk
pip install lizard-sdk
export LIZARD_API_KEY=""
```

Create an API key in Settings → API keys or with [`lizard keys create `](https://lizard.build/docs/cli/keys.md). The SDK reads `LIZARD_API_KEY`; pass `apiKey` (Python: `api_key`) to use another key. Each sandbox needs a project: pass `projectId` (Python: `project_id`) or resolve an ID, slug, or name with `project`.

## Create options

| TypeScript | Python |
|---|---|
| `projectId / project` | `project_id / project` |
| `timeoutMs` | `timeout_ms` |
| `region` | `region` |
| `volumeName / volumeId` | `volume_name / volume_id` |
| `size` | `size` |
| `snapshotId` | `snapshot_id` |
| `envs` | `envs` |
| `metadata` | `metadata` |
| `lizardToken` | `lizard_token` |
| `apiKey` | `api_key` |

```ts
import { Sandbox } from '@lizard-build/sdk';

const sandbox = await Sandbox.create('base', {
  projectId: 'proj_123',
  size: 'small',
  timeoutMs: 10 * 60 * 1000,
  envs: { MODE: 'test' },
  metadata: { task: 'report-42' },
});
```

```python
from lizard import Sandbox

sandbox = Sandbox.create(
    "base",
    project_id="proj_123",
    size="small",
    timeout_ms=10 * 60 * 1000,
    envs={"MODE": "test"},
    metadata={"task": "report-42"},
)
```

- The first argument is a template from the catalog in [runtime and templates](https://lizard.build/docs/sandboxes.md#runtime-and-templates). Without one, the SDK boots `base`.
- `size` is `'small'`, `'medium'`, or `'large'`: 2 vCPU / 4 GB, 4 vCPU / 8 GB (the default), or 8 vCPU / 16 GB; see [sizes and prices](https://lizard.build/docs/platform/limits.md#sandbox-sizes). `size` does not apply to a sandbox restored from a snapshot; it runs at its source's size.
- `timeoutMs` defaults to 300000, five minutes. `0` disables expiration. See [resources and timeouts](https://lizard.build/docs/sandboxes.md#resources-and-timeouts).
- Select a region explicitly when needed; a catalog entry does not guarantee free capacity. Leave `region` unset when you attach a volume: the sandbox runs where the volume is.
- `metadata` is your own labels; `getInfo()` and `list()` return it.
- `lizardToken` writes a `liz_` API key into the sandbox so `lizard` works inside it. Anything in the sandbox can read that key, so pass a workspace-scoped one.

## Methods

| TypeScript | Python | What it does |
|---|---|---|
| `Sandbox.create(template, options)` | `Sandbox.create(template, ...)` | Create in a project from a catalog template. |
| `Sandbox.connect(sandboxId)` | `Sandbox.connect(sandbox_id)` | Attach to an existing sandbox; no automatic resume. |
| `Sandbox.list({ projectId })` | `Sandbox.list(project_id=...)` | List accessible sandboxes; inspect their status. |
| `sandbox.getInfo()` | `sandbox.get_info()` | Fetch status, runtime, and metadata. |
| `sandbox.process.exec(cmd, opts)` | `sandbox.process.exec_(cmd, ...)` | Run a shell command or an argv array; return stdout, stderr, and the exit code. |
| `sandbox.process.list()` | `sandbox.process.list()` | List processes started with `exec`. |
| `sandbox.process.kill(pid, signal)` | `sandbox.process.kill(pid, signal)` | Signal a process and its children; `SIGTERM` by default. |
| `sandbox.fs.write(path, data)` | `sandbox.fs.write(path, data)` | Write text or bytes; create parent directories. |
| `sandbox.fs.read(path)` | `sandbox.fs.read(path)` | Read a UTF-8 text file. |
| `sandbox.fs.readBytes(path)` | `sandbox.fs.read_bytes(path)` | Read bytes from a file. |
| `sandbox.fs.list(path)` | `sandbox.fs.list(path)` | List directory entries. |
| `sandbox.fs.stat(path)` | `sandbox.fs.stat(path)` | Return type, size, mode, owner, and modification time. |
| `sandbox.fs.makeDir(path)` | `sandbox.fs.make_dir(path)` | Create a directory and missing parents. |
| `sandbox.fs.move(from, to)` | `sandbox.fs.move(src, dst)` | Move a file or directory. |
| `sandbox.fs.remove(path)` | `sandbox.fs.remove(path)` | Remove a file or directory. |
| `sandbox.exposePort(port)` | `sandbox.expose_port(port)` | Publish a port; return `url`, `hostname`, and the access token. |
| `sandbox.getHost(port)` | `sandbox.get_host(port)` | Publish a port and return its hostname; requests need `sandbox.accessToken`. |
| `sandbox.unexpose(port)` | `sandbox.unexpose(port)` | Remove a published port. |
| `sandbox.setTimeout(ms)` | `sandbox.set_timeout(ms)` | Set the remaining lifetime, at least 1000 ms. |
| `sandbox.pause()` / `sandbox.resume()` | `sandbox.pause()` / `sandbox.resume()` | Queue a pause or resume and return at once. |
| `sandbox.waitForStatus(status)` | `sandbox.wait_for_status(status)` | Wait for `paused` or `running`; throw if the pause failed. |
| `sandbox.fork({ count })` | `sandbox.fork(count=...)` | Copy a running sandbox 1 to 10 times, memory and processes included. |
| `sandbox.snapshot(name, { poolSize })` | `sandbox.snapshot(name, pool_size=...)` | Save memory and `/workspace` files as a private snapshot. |
| `Sandbox.waitForSnapshot(snapshotId)` | `Sandbox.wait_for_snapshot(snapshot_id)` | Wait until a snapshot is ready. |
| `Sandbox.restore(snapshotId)` | `Sandbox.restore(snapshot_id)` | Start a new sandbox from a snapshot. |
| `Sandbox.deleteSnapshot(snapshotId)` | `Sandbox.delete_snapshot(snapshot_id)` | Delete a snapshot; sandboxes restored from it keep running. |
| `sandbox.kill()` | `sandbox.kill()` | Stop the sandbox and detach its volume. |

## Run commands

`process.exec` (Python: `exec_`) runs a command and waits for it to finish. Pass `onStdout` or `onStderr` (Python: `on_stdout`, `on_stderr`) to receive output while the command runs; the call still returns the full result at the end. Pass an argv array instead of a string to skip the shell, which is the safe way to pass untrusted text as an argument.

```ts
const result = await sandbox.process.exec('npm test', {
  workdir: '/app',
  envs: { NODE_ENV: 'test' },
  timeoutMs: 120_000,
  onStdout: (chunk) => process.stdout.write(chunk),
});
if (result.exitCode !== 0) throw new Error(result.stderr);

await sandbox.process.exec(['grep', '-rn', '--', userQuery, '/workspace']);
```

```python
result = sandbox.process.exec_(
    "npm test",
    workdir="/app",
    envs={"NODE_ENV": "test"},
    timeout_ms=120_000,
    on_stdout=lambda chunk: print(chunk, end=""),
)
if result.exit_code != 0:
    raise RuntimeError(result.stderr)

sandbox.process.exec_(["grep", "-rn", "--", user_query, "/workspace"])
```

Command results contain stdout, stderr, and the exit code. A nonzero exit code is a result you check; request failures throw. Command timeouts are limited to 1 to 600 seconds. `process.exec` accepts `envs`, `workdir`, and `user`; commands run as `root` unless you pass `user`. Create-time `envs` reach every command in a Firecracker sandbox; a pod sandbox ignores them, so pass them per command there.

## Files

Use `fs.write` for text or bytes and `fs.read` for text. For binary files, write bytes and read them back with `fs.readBytes` (Python: `read_bytes`). Use absolute paths. A missing path throws `NotFoundError`.

```ts
await sandbox.fs.write('/app/data.csv', 'x,y\n1,2\n');
const text = await sandbox.fs.read('/app/data.csv');
const entries = await sandbox.fs.list('/app');
const png = await sandbox.fs.readBytes('/app/chart.png');
```

```python
sandbox.fs.write("/app/data.csv", "x,y\n1,2\n")
text = sandbox.fs.read("/app/data.csv")
entries = sandbox.fs.list("/app")
png = sandbox.fs.read_bytes("/app/chart.png")
```

Files outside a [Persistent Volume](https://lizard.build/docs/sandboxes/volumes.md) at `/workspace` are gone after the sandbox stops.

## Ports

Start a server on `0.0.0.0` inside the sandbox, then publish its port. Every request needs the access token, as the `X-Lizard-Access-Token` header or once as `?lizard_token=` in a browser. See [preview URLs](https://lizard.build/docs/sandboxes/preview-urls.md).

```ts
const { url, hostname, accessToken } = await sandbox.exposePort(3000);
console.log(url); // open in a browser; it carries the token
await fetch(`https://${hostname}/api`, { headers: { 'X-Lizard-Access-Token': accessToken } });
await sandbox.unexpose(3000);
```

```python
import httpx

exposed = sandbox.expose_port(3000)
print(exposed.url)  # open in a browser; it carries the token
httpx.get(f"https://{exposed.hostname}/api", headers={"X-Lizard-Access-Token": exposed.access_token})
sandbox.unexpose(3000)
```

## Pause, snapshot, and fork

These calls keep memory and running processes, not just files. The [quickstart](https://lizard.build/docs/sandboxes/quickstart.md#pause-and-resume) explains what each one keeps, how restores are billed, and what fails.

```ts
await sandbox.pause();
await sandbox.waitForStatus('paused');
await sandbox.resume();
await sandbox.waitForStatus('running');

const saved = await sandbox.snapshot('ready-env');
const copy = await Sandbox.restore(saved.id);

for (const f of await sandbox.fork({ count: 2 })) {
  if (f.sandbox) await f.sandbox.process.exec('echo hi');
  else console.warn('fork failed:', f.error);
}
```

```python
sandbox.pause()
sandbox.wait_for_status("paused")
sandbox.resume()
sandbox.wait_for_status("running")

saved = sandbox.snapshot("ready-env")
copy = Sandbox.restore(saved["id"])

for f in sandbox.fork(count=2):
    if f.sandbox:
        f.sandbox.process.exec_("echo hi")
    else:
        print("fork failed:", f.error)
```

`fork` returns one entry per copy: the new sandbox, or an error such as the account's sandbox limit. Each copy bills like its source.

## Run code with state

`CodeSandbox` boots the `interpreter` template and runs code with `runCode` (Python: `run_code`). In that template, Python runs in a persistent Jupyter kernel per context, so variables carry over between calls and results can include charts as `image/png`. JavaScript and bash run as a fresh process on each call. See [code interpreter](https://lizard.build/docs/sandboxes/code-interpreter.md).

## Volumes

Attach a [Persistent Volume](https://lizard.build/docs/sandboxes/volumes.md) by name to keep `/workspace` between sandboxes:

```ts
import { Sandbox, Volume } from '@lizard-build/sdk';

await Volume.getOrCreate('proj_123', 'agent-workdir', { sizeGb: 10 });
const sandbox = await Sandbox.create('base', { projectId: 'proj_123', volumeName: 'agent-workdir' });
```

```python
from lizard import Sandbox, Volume

Volume.get_or_create("proj_123", "agent-workdir", size_gb=10)
sandbox = Sandbox.create("base", project_id="proj_123", volume_name="agent-workdir")
```

## Unsupported APIs

File watching is not available yet. `sandbox.fork()` returns 409 when a volume is attached and 501 for a sandbox that runs as a pod. `Sandbox.connect` does not resume a paused or stopped sandbox; call `resume()` on a paused one. SSH protocol access is not available; `lizard sandbox ssh` opens an interactive shell with your CLI credentials.

## Errors

API failures throw (Python: raise) these errors. Each carries the HTTP status and, when the API sends one, a `code`.

| Error | Meaning |
|---|---|
| `PaymentRequiredError` | HTTP 402: the account must start Pro or pay an invoice before it can create anything. `url` is the page to open. |
| `AuthenticationError` | HTTP 401 or 403: invalid key or no access. |
| `NotFoundError` | HTTP 404: the sandbox, file, or snapshot does not exist or has expired. |
| `ConflictError` | HTTP 409: conflicting state, such as forking a sandbox with a volume. |
| `TimeoutError` | HTTP 408 or 504. |
| `LizardError` | Other API failures, including unsupported operations. |

Too many sandboxes at once, or too many starts, returns HTTP 429; see [account limits](https://lizard.build/docs/platform/limits.md#sandbox-account-limits).

## HTTP API

The SDK calls Lizard's REST API at `https://lizard.build`. Send your API key as `Authorization: Bearer liz_...` or in the `X-API-Key` header.

| Call | Request |
|---|---|
| Create | `POST /api/sandboxes` with `template`, `projectId`, `size`, `timeoutMs`, `envs`, `metadata`, `region`, `volumeName`; returns 201 with `sandboxId` |
| Get status | `GET /api/sandboxes/{id}` |
| List | `GET /api/sandboxes`, or `GET /api/projects/{projectId}/sandboxes` for one project |
| Run a command | `POST /api/sandboxes/{id}/exec` with `cmd`, `envs`, `workdir`, `user`, `timeoutMs`; returns `stdout`, `stderr`, `exitCode` |
| Change lifetime | `POST /api/sandboxes/{id}/timeout` with `timeoutMs` |
| Pause or resume | `POST /api/sandboxes/{id}/pause`, `POST /api/sandboxes/{id}/resume` |
| Publish a port | `POST /api/sandboxes/{id}/expose/{port}`; `DELETE` on the same path removes it |
| Kill | `DELETE /api/sandboxes/{id}` |

```bash
curl -X POST https://lizard.build/api/sandboxes \
  -H "Authorization: Bearer $LIZARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template": "base", "projectId": "proj_123", "timeoutMs": 300000}'
```

The raw API creates a sandbox with no expiration unless you pass `timeoutMs`.

## Storage and billing

For billing, egress and files that survive a stop, see [storage and billing](https://lizard.build/docs/sandboxes.md#storage-and-billing). Sizes and hourly prices are in [limits](https://lizard.build/docs/platform/limits.md#sandbox-sizes).


---
title: "Code interpreter API for LLM-generated code"
description: "Run code an LLM wrote in a sandbox with Lizard SDK: a stateful Python kernel, charts as PNG, errors with tracebacks, timeouts and an LLM tool example."
url: https://lizard.build/docs/sandboxes/code-interpreter/
updated: 2026-10-12
---

# Code interpreter API for LLM-generated code

A code interpreter runs code that a language model writes and returns the output to the model. On Lizard, `CodeSandbox` from Lizard SDK does this inside a sandbox, a Firecracker microVM, which isolates the code from your app and computer. It boots the `interpreter` template, which has Python and common data libraries. Python runs in a Jupyter kernel that keeps variables between calls, and each result carries the printed output, the value of the last expression, charts as `image/png`, and any error with its traceback.

## Run code with state

```ts
import { CodeSandbox } from '@lizard-build/sdk';

const sandbox = await CodeSandbox.create({ projectId: 'proj_123', timeoutMs: 300_000 });
try {
  await sandbox.runCode('import pandas as pd\ndf = pd.DataFrame({"x": [1, 2, 3]})');
  const run = await sandbox.runCode('df.x.sum()');
  console.log(run.results[0]?.data); // "6"
  const bad = await sandbox.runCode('1/0');
  console.log(bad.error?.name); // "ZeroDivisionError"
} finally {
  await sandbox.kill();
}
```

```python
from lizard import CodeSandbox

with CodeSandbox.create(project_id="proj_123", timeout_ms=300_000) as sandbox:
    sandbox.run_code('import pandas as pd\ndf = pd.DataFrame({"x": [1, 2, 3]})')
    run = sandbox.run_code("df.x.sum()")
    print(run.results[0].data)  # "6"
    print(sandbox.run_code("1/0").error.name)  # "ZeroDivisionError"
```

A code error is part of the result, not an exception. Request failures, such as a bad API key, throw.

## What does runCode return?

`runCode` (Python: `run_code`) returns an `Execution`:

| Field | Contents |
|---|---|
| `stdout`, `stderr` | Everything the code printed |
| `results` | Rich output: the last expression's value as `text/plain`, matplotlib charts and images as base64 `image/png`, HTML, and other types, each with `mime` and `data` |
| `error` | `name`, `message`, and `traceback` when the code raised an exception |
| `executionCount` | The kernel's cell counter (Python: `execution_count`) |

Pass `onStdout`, `onStderr`, `onResult` or `onError` (Python: `on_stdout` and so on) to receive output while the code runs.

## Use it as an LLM tool

Give the model one tool, for example `run_python` with a single string argument `code`. When the model calls it, run the code in the sandbox and send the text back as the tool result. Keep one sandbox for the whole conversation so the model's variables survive between calls.

```ts
import { CodeSandbox } from '@lizard-build/sdk';

const sandbox = await CodeSandbox.create({ projectId: 'proj_123', timeoutMs: 600_000 });

// Call this when the model asks to run Python. Return the text to send back to it.
async function runPython(code: string): Promise {
  const run = await sandbox.runCode(code, { timeoutMs: 30_000 });
  if (run.error) return `${run.error.name}: ${run.error.message}\n${run.error.traceback}`;
  const values = run.results.flatMap((r) =>
    r.type === 'result' && r.mime === 'text/plain' ? [r.data] : [],
  );
  return [run.stdout, ...values].filter(Boolean).join('\n');
}

// When the conversation ends:
await sandbox.kill();
```

```python
from lizard import CodeSandbox

sandbox = CodeSandbox.create(project_id="proj_123", timeout_ms=600_000)

def run_python(code: str) -> str:
    """Call this when the model asks to run Python. Return the text to send back to it."""
    run = sandbox.run_code(code, timeout_ms=30_000)
    if run.error:
        return f"{run.error.name}: {run.error.message}\n{run.error.traceback}"
    values = [r.data for r in run.results if r.mime == "text/plain"]
    return "\n".join(filter(None, [run.stdout, *values]))

# When the conversation ends:
sandbox.kill()
```

The sandbox lifetime here is ten minutes. Raise it for longer conversations with `setTimeout` (Python: `set_timeout`), or set `timeoutMs: 0` and kill the sandbox yourself.

## Charts

When the code draws a matplotlib chart, `results` holds an item with `mime` set to `image/png` and the image as base64 in `data`:

```python
import base64
from pathlib import Path

run = sandbox.run_code("import matplotlib.pyplot as plt\nplt.plot([1, 4, 9])\nplt.show()")
png = next(r.data for r in run.results if r.mime == "image/png")
Path("chart.png").write_bytes(base64.b64decode(png))
```

## Contexts, timeouts, and languages

- **Contexts.** `createContext()` (Python: `create_context()`) starts a separate kernel with its own variables and working directory; pass it as `context` to `runCode`. `listContexts()`, `restartContext(ctx)` and `deleteContext(ctx)` manage them. Without a context, each language uses its own default one.
- **Timeouts.** `timeoutMs` (Python: `timeout_ms`) bounds one call and defaults to 60 seconds. A Python kernel that runs past it is interrupted and keeps its state; the result's `error.name` is `TimeoutError`.
- **Other languages.** JavaScript and bash run as a fresh process on each call, so no state carries over and `results` stays empty. JavaScript needs `node`, which the `interpreter` template does not include.
- **Pause and snapshots.** Kernels live inside the sandbox, so `pause()` and `resume()` keep their variables, and `snapshot()` and `fork()` copy them.

## Run commands

`process.exec` starts a separate process for each command, so Python variables do not survive between calls. Use it for shell steps such as `pip install`, and save results you need later to files. See [run a command](https://lizard.build/docs/sandboxes/quickstart.md#run-a-command) and the [SDK reference](https://lizard.build/docs/sandboxes/sdk-reference.md#run-commands).

## Untrusted code

Treat everything the model writes as untrusted. Do not pass `lizardToken` unless the code needs Lizard CLI; anything in the sandbox can read it, so use a workspace-scoped key if you do. Keep other secrets out of `envs`. A sandbox that uses a pod-backed volume shares the host kernel; see [runtime and templates](https://lizard.build/docs/sandboxes.md#runtime-and-templates).

## Storage and billing

Variables and files last only as long as the sandbox. Billing and storage rules are in [storage and billing](https://lizard.build/docs/sandboxes.md#storage-and-billing); sizes and prices are in [limits](https://lizard.build/docs/platform/limits.md#sandbox-sizes).

[Sandboxes overview](https://lizard.build/docs/sandboxes.md) · [Quickstart](https://lizard.build/docs/sandboxes/quickstart.md) · [SDK reference](https://lizard.build/docs/sandboxes/sdk-reference.md) · [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md) · [Sandboxes product page](https://lizard.build/sandboxes)


---
title: "Preview URLs: open a port from a sandbox"
description: "Give a port in a Lizard sandbox a private HTTPS preview URL with exposePort or lizard sandbox expose: URL format, access token, cookie, WebSockets and errors."
url: https://lizard.build/docs/sandboxes/preview-urls/
updated: 2026-10-12
---

# Preview URLs: open a port from a sandbox

A preview URL gives a port inside a sandbox on Lizard a private HTTPS address. Start a server that listens on `0.0.0.0`, then call `sandbox.exposePort(3000)` (Python: `expose_port`) or run `lizard sandbox expose  3000`. You get `https://-3000.sandbox..onlizard.com/?lizard_token=…`; a request without the sandbox's access token gets HTTP 401.

**Test status:** we checked this page against the source of Lizard SDK, Lizard CLI 4.0.42 and the sandbox router on 2026-10-12. We did not run a new live test for it.

## From the CLI

```bash
lizard sandbox exec $SB -- sh -c "nohup python3 -m http.server 3000 --bind 0.0.0.0 >/tmp/http.log 2>&1 &"
lizard sandbox expose $SB 3000
```

`expose` prints the URL with the token in it; open it in a browser. Add `--json` to get `hostname`, `url`, `port` and `accessToken` for a script. Remove the URL with `lizard sandbox unexpose $SB 3000`.

## From Lizard SDK

`exposePort` returns the browser `url`, the bare `hostname` and the `accessToken`:

```ts
import { Sandbox } from '@lizard-build/sdk';

const sandbox = await Sandbox.create('base', { projectId: 'proj_123', timeoutMs: 600_000 });
await sandbox.process.exec(
  "nohup python3 -m http.server 3000 --bind 0.0.0.0 --directory /tmp >/tmp/http.log 2>&1 /tmp/http.log 2>&1 -.sandbox..onlizard.com
```

The URL works only while the sandbox runs. The platform reserves port `49983`; you cannot expose it. A sandbox from the SDK or CLI lives five minutes by default; pass a longer `timeoutMs` or `--timeout` for a preview you want to keep open. See [lifetime limits](https://lizard.build/docs/platform/limits.md#sandbox-lifetime).

## Errors

| Status | Message | Fix |
|---|---|---|
| 401 | `This sandbox port is private (missing or wrong access token)` | Open the full URL from `expose`, or send `X-Lizard-Access-Token`. |
| 403 | `Port 3000 is not published for sandbox "…". Published: …` | Run `lizard sandbox expose` or `exposePort` for that port. |
| 404 | `No running sandbox "…"` | The sandbox is not running. Check `lizard sandbox list`. |
| 502 | `Sandbox is not listening on port 3000` | Start the server, and bind it to `0.0.0.0`, not `127.0.0.1`. |

## Dev servers

Most dev servers listen on `127.0.0.1` unless you tell them otherwise. Pass the host and a fixed port:

```bash
npm run dev -- --host 0.0.0.0 --port 5173
```

Vite also checks the `Host` header and answers `Blocked request. This host (…) is not allowed.` for hosts it does not know. Allow the sandbox hostnames in `vite.config.js`:

```js
import { defineConfig } from 'vite';

export default defineConfig({
  server: { allowedHosts: ['.onlizard.com'] },
});
```

A leading dot allows every subdomain. Then expose `5173`.

## Related

- [Run Claude Code in a sandbox](https://lizard.build/docs/sandboxes/guides/claude-code.md)
- [Run OpenCode in a sandbox](https://lizard.build/docs/sandboxes/guides/opencode.md), including its web UI
- [Sandboxes quickstart](https://lizard.build/docs/sandboxes/quickstart.md)
- [SDK reference](https://lizard.build/docs/sandboxes/sdk-reference.md)
- [`lizard sandbox`](https://lizard.build/docs/cli/sandbox.md)


---
title: "Run Claude Code in a sandbox"
description: "Run Claude Code in a Lizard sandbox: connect a Claude subscription or API key with lizard agents, create a claude sandbox, send prompts and get changes back."
url: https://lizard.build/docs/sandboxes/guides/claude-code/
updated: 2026-10-12
---

# Run Claude Code in a sandbox

To run Claude Code in a sandbox on Lizard, connect a credential once with `lizard agents add claude` (a token from `claude setup-token`) or `lizard agents add anthropic` (an API key). Then create a sandbox from the `claude` template and send it work with `lizard sandbox prompt  --agent claude "…"`. Claude Code runs on its own Linux machine with `--dangerously-skip-permissions`: it edits files and runs commands without asking, and it has no access to your computer.

**Test status:** the commands and behaviour on this page come from Lizard CLI 4.0.42 and the platform's agent runner as of 2026-10-12. We have not run this walkthrough end to end in a live sandbox for this page. We checked the `git bundle` and patch steps locally.

## Before you start

- Lizard CLI, signed in with `lizard login`, and a project: run `lizard init` in a folder, or pass `-p ` to the sandbox commands.
- Lizard Pro or the 7-day Pro trial (card required). Sandboxes bill per second by size while they run; see [limits](https://lizard.build/docs/platform/limits.md#sandbox-sizes) and [pricing](https://lizard.build/pricing).
- For a Claude subscription: Claude Code on your computer, to run `claude setup-token`.

## Connect a credential

| Credential | Command | What a run gets |
|---|---|---|
| Claude subscription | `claude setup-token`, then `lizard agents add claude` and paste the `sk-ant-oat01-…` token | `CLAUDE_CODE_OAUTH_TOKEN` |
| Anthropic API key | `lizard agents add anthropic` | `ANTHROPIC_API_KEY` |
| OpenRouter API key | `lizard agents add openrouter` | OpenRouter's Anthropic-compatible endpoint |

Credentials belong to your Lizard account, not to a project, and every sandbox you start can use them. Claude Code uses one credential at a time. If you connect several credentials, choose one. You can also set a default model:

```bash
lizard agents use claude --credential anthropic
lizard agents use claude -m sonnet
lizard agents status
```

See [`lizard agents`](https://lizard.build/docs/cli/agents.md) for every option.

## Create a sandbox

```bash
lizard sandbox create -t claude --timeout 3600000
```

The command prints the sandbox ID, such as `sb_abc123`. Keep it in a variable for the steps below: `SB=sb_abc123`.

- `--timeout 3600000` gives the sandbox one hour. The CLI default is five minutes, after which Lizard deletes the sandbox and its files, even in the middle of a task. Extend a running one with `lizard sandbox timeout $SB 3600000`.
- The `claude` template has Node.js 22, a pinned Claude Code release, git, Python 3, ripgrep, curl, jq, unzip and Lizard CLI. Check the Claude Code version with `lizard sandbox exec $SB -- claude --version`.
- The default size is Medium (4 vCPU, 8 GB). Pass `--size small` or `--size large` to change it.

## Put your code in the sandbox

From a public repository:

```bash
lizard sandbox exec $SB -- git clone https://github.com/your-org/your-app /workspace/app
```

From a local git repository, private ones included:

```bash
git bundle create /tmp/app.bundle HEAD
lizard sandbox files put $SB /tmp/app.bundle /workspace/app.bundle
lizard sandbox exec $SB -- git clone -q /workspace/app.bundle /workspace/app
```

A bundle holds committed files only, so `.env` and other untracked files stay on your computer.

## Send a prompt

```bash
lizard sandbox prompt $SB --agent claude --cwd /workspace/app "run the tests and fix any that fail"
```

The CLI streams each tool call, then Claude's answer, the token count and the conversation ID. Useful flags: `-m `, `--effort `, `-v` to print command output, and `--no-wait` to return at once. Ctrl-C stops watching; Claude keeps working. Stop it with `lizard sandbox interrupt $SB`.

To continue the same Claude Code session, pass the conversation ID:

```bash
lizard sandbox prompt $SB --agent claude -c conv_0123456789abcdef --cwd /workspace/app "now add a test for an empty list"
lizard sandbox conversations $SB
```

Pass `--agent claude` and `--cwd` on every prompt, follow-ups included. Without `--agent` the CLI asks for Codex, and without `--cwd` the agent works in `/workspace`.

## What Claude Code can reach

- **Permissions.** Each turn runs `claude -p` with `--dangerously-skip-permissions` as root inside the sandbox. Claude edits files, installs packages and runs commands without asking. These permissions apply inside the sandbox.
- **Files.** Everything in the sandbox, and nothing on your computer.
- **Network.** Outbound internet access, so it can fetch packages and call APIs. The sandbox price includes egress up to [2 TB per account](https://lizard.build/docs/platform/limits.md#sandbox-egress) over a rolling 30 days.
- **Your credential.** During a run, the token sits in Claude Code's environment as `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY`, and commands it runs inherit it. For a subscription this is the long-lived token from `claude setup-token`; only ChatGPT logins get a short-lived copy per run. Assume code that runs in the sandbox can read it, and remove it with `lizard agents remove claude` when you no longer need it.
- **Your Lizard account.** None by default. Create the sandbox with `--with-token ` to install a `liz_` key so that `lizard` works inside it, for example to deploy what Claude built. Anything in the sandbox can read that key, so use one scoped to a single workspace or project; see [`lizard keys`](https://lizard.build/docs/cli/keys.md).

## Get the changes back

```bash
lizard sandbox exec $SB -- sh -c 'cd /workspace/app && git add -A && git diff --cached > /tmp/claude.patch'
lizard sandbox files get $SB /tmp/claude.patch claude.patch
git apply claude.patch
```

Review the patch before you commit it. To try a web app Claude started, open a [preview URL](https://lizard.build/docs/sandboxes/preview-urls.md) for its port, for example `lizard sandbox expose $SB 3000`.

## Use Claude Code by hand

`lizard sandbox ssh $SB` opens a shell in the sandbox. Credentials from `lizard agents` reach only `lizard sandbox prompt` runs, so export your own `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY` in that shell, then run `claude`.

## Pause or remove the sandbox

```bash
lizard sandbox pause $SB
lizard sandbox resume $SB
lizard sandbox rm $SB
```

Lizard does not bill a paused sandbox, and it resumes with its files and processes. Conversation state lives in `/workspace/.lizard-agent`, inside the sandbox. To keep files after you remove the sandbox, attach a [Persistent Volume](https://lizard.build/docs/sandboxes/volumes.md) at create time with `--volume`.

## Common problems

| Message or symptom | Fix |
|---|---|
| `Claude Code has no credential. Connect one of: …` | Run `lizard agents add claude` or `lizard agents add anthropic`. |
| `Claude rejected the credential (authentication_failed)` | The token or key is no longer valid. Create a new one and add it again. |
| HTTP 402 with a Checkout link on `create` | Start the trial or Pro: `lizard billing start`. |
| The sandbox is gone in the middle of a task | Its lifetime ran out. Create the next one with `--timeout`, or extend it with `lizard sandbox timeout`. |
| The output mentions Codex | The prompt went out without `--agent claude`. |
| A step says `installing Claude Code` | The sandbox was not created from the `claude` template, so the runner installs Claude Code with npm first. |

## Related

- [Run OpenCode in a sandbox](https://lizard.build/docs/sandboxes/guides/opencode.md)
- [Sandboxes](https://lizard.build/docs/sandboxes.md)
- [`lizard sandbox`](https://lizard.build/docs/cli/sandbox.md#coding-agents)
- [Deploy from a coding agent](https://lizard.build/docs/guides/deploy-from-coding-agent.md)


---
title: "Run OpenCode in a sandbox"
description: "Run OpenCode in a Lizard sandbox: connect ChatGPT or an API key, send prompts with lizard sandbox prompt, or open OpenCode's web UI on a private preview URL."
url: https://lizard.build/docs/sandboxes/guides/opencode/
updated: 2026-10-12
---

# Run OpenCode in a sandbox

To run OpenCode in a sandbox on Lizard, connect a credential with `lizard agents` (a ChatGPT subscription, or an Anthropic, OpenAI, OpenRouter or LLM Gateway key), create a sandbox from the `opencode` template, and send prompts with `lizard sandbox prompt  --agent opencode "…"`. To use OpenCode's web UI instead, start `opencode web --hostname 0.0.0.0 --port 4096` in the sandbox and open the port with `lizard sandbox expose`.

**Test status:** the commands come from Lizard CLI 4.0.42 and the platform's agent runner as of 2026-10-12. We checked the web UI steps against the source of OpenCode 1.18.34, which serves its UI and API from one port, and of Lizard's sandbox router, which keeps the access token in a cookie and passes WebSockets through. We have not yet run either path end to end in a live sandbox for this page.

## Before you start

- Lizard CLI, signed in with `lizard login`, and a project: run `lizard init` in a folder, or pass `-p ` to the sandbox commands.
- Lizard Pro or the 7-day Pro trial (card required). Sandboxes bill per second by size while they run; see [limits](https://lizard.build/docs/platform/limits.md#sandbox-sizes) and [pricing](https://lizard.build/pricing).

## Connect credentials

| Credential | Command |
|---|---|
| ChatGPT subscription | `lizard agents login chatgpt --open` |
| Anthropic API key | `lizard agents add anthropic` |
| OpenAI API key | `lizard agents add openai` |
| OpenRouter API key | `lizard agents add openrouter` |
| LLM Gateway API key | `lizard agents add llmgateway` |

OpenCode gets every connected credential it accepts to access those providers' models. It does not take a Claude subscription token; for that, use [Claude Code](https://lizard.build/docs/sandboxes/guides/claude-code.md). A ChatGPT login reaches a run as a short-lived access token, never the refresh token. API keys reach a run as environment variables such as `ANTHROPIC_API_KEY`.

OpenCode names models `provider/model`. Set a default, or pass `-m` per prompt:

```bash
lizard agents use opencode -m openrouter/anthropic/claude-sonnet-4.5
lizard agents status
```

With only an OpenRouter key connected and no model chosen, Lizard runs `openrouter/openai/gpt-5.3-codex`.

## Create a sandbox

```bash
lizard sandbox create -t opencode --timeout 3600000
```

Keep the printed ID in a variable: `SB=sb_abc123`. The CLI default lifetime is five minutes, after which Lizard deletes the sandbox and its files; `--timeout 3600000` gives it one hour. The `opencode` template has Node.js 22, a pinned OpenCode release, git, Python 3, ripgrep, curl, jq, unzip and Lizard CLI.

Copy your code in the same way as for Claude Code:

```bash
lizard sandbox exec $SB -- git clone https://github.com/your-org/your-app /workspace/app
```

For a private or local repository, see [put your code in the sandbox](https://lizard.build/docs/sandboxes/guides/claude-code.md#put-your-code-in-the-sandbox).

## Send a prompt

```bash
lizard sandbox prompt $SB --agent opencode --cwd /workspace/app "add input validation to the signup form"
```

Each turn runs `opencode run --auto`, which approves every permission request that is not explicitly denied: OpenCode edits files and runs commands without asking. `--effort ` becomes OpenCode's `--variant`. Continue a conversation with `-c `, and pass `--agent opencode` and `--cwd` again on every follow-up. Ctrl-C stops watching; `lizard sandbox interrupt $SB` stops the agent.

To get the changes back as a patch, see [get the changes back](https://lizard.build/docs/sandboxes/guides/claude-code.md#get-the-changes-back).

## Open the OpenCode web UI

`opencode web` serves OpenCode's browser interface and its API from one port. Run it in the sandbox and open that port through a [preview URL](https://lizard.build/docs/sandboxes/preview-urls.md).

Credentials from `lizard agents` reach only `lizard sandbox prompt` runs, so this server needs its own key. Copy it in as a file, so it does not appear in a command line:

```bash
printf 'export ANTHROPIC_API_KEY=%s\n' "$ANTHROPIC_API_KEY" > opencode.env
lizard sandbox files put $SB opencode.env /root/opencode.env
rm opencode.env
```

Start the server and open the port:

```bash
lizard sandbox exec $SB -- sh -c '. /root/opencode.env && cd /workspace/app && nohup opencode web --hostname 0.0.0.0 --port 4096 >/tmp/opencode.log 2>&1  /tmp/opencode.pid'
lizard sandbox expose $SB 4096
```

Open the URL that `expose` prints. It carries `?lizard_token=`; Lizard moves the token into a cookie for that host and redirects to the same page without it. The cookie authenticates the UI's requests, event stream and terminal WebSockets.

- You need `--hostname 0.0.0.0`. OpenCode listens on `127.0.0.1` by default, and the preview URL then answers `Sandbox is not listening on port 4096`.
- Give a fixed `--port`. Without one, OpenCode picks a random unused port.
- The URL works like a password: whoever has it can run commands in the sandbox through OpenCode. To require a password as well, add `export OPENCODE_SERVER_PASSWORD=…` to `opencode.env`; the user name is `opencode`.
- Read the server's log with `lizard sandbox exec $SB -- tail -n 50 /tmp/opencode.log`. Stop the server with `lizard sandbox exec $SB -- sh -c 'kill $(cat /tmp/opencode.pid)'`.
- The UI stops when the sandbox stops. Extend it with `lizard sandbox timeout $SB 3600000`.

## Common problems

| Message or symptom | Fix |
|---|---|
| `OpenCode has no credential. Connect one of: …` | Connect one of the credentials above with `lizard agents`. |
| `Your ChatGPT login expired` | Run `lizard agents login chatgpt` again. |
| The output mentions Codex | The prompt went out without `--agent opencode`. |
| Preview URL answers 401 | Open the full URL that `expose` printed, with its `lizard_token`. See [preview URLs](https://lizard.build/docs/sandboxes/preview-urls.md). |
| Preview URL answers 502 | The server is not running or listens on `127.0.0.1`. Check `/tmp/opencode.log`. |
| The web UI loads but every model fails | The server has no provider key. Fix `/root/opencode.env`, stop the server and start it again. |

## Related

- [Run Claude Code in a sandbox](https://lizard.build/docs/sandboxes/guides/claude-code.md)
- [Preview URLs](https://lizard.build/docs/sandboxes/preview-urls.md)
- [`lizard agents`](https://lizard.build/docs/cli/agents.md)
- [`lizard sandbox`](https://lizard.build/docs/cli/sandbox.md#coding-agents)
- [Sandboxes](https://lizard.build/docs/sandboxes.md)


---
title: "Persistent Volumes"
description: "Mount a named volume at /workspace to keep files between sandbox runs. Size limits, storage pricing, and examples for TypeScript, Python and the CLI."
url: https://lizard.build/docs/sandboxes/volumes/
updated: 2026-10-12
---

# Persistent Volumes

Persistent Volumes keep data after a sandbox stops. Each volume mounts at **`/workspace`** and attaches to **one sandbox at a time**. The sandbox runs on the same node as its volume.

## Create and attach by name

Names are unique within a project. Use lowercase letters, digits and dashes, starting and ending with a letter or digit, up to 64 characters.

Use `getOrCreate` to reuse a named volume. Its size applies only when creating a new volume; it never resizes an existing one. `Volume.create` returns a conflict if the name is taken.

```ts
import { Sandbox, Volume } from '@lizard-build/sdk';

await Volume.getOrCreate('proj_123', 'agent-workdir', { sizeGb: 10 });
const sandbox = await Sandbox.create('base', {
  projectId: 'proj_123', volumeName: 'agent-workdir',
});
try {
  await sandbox.fs.write('/workspace/state.json', JSON.stringify({ step: 1 }));
} finally {
  await sandbox.kill();
}

// A later sandbox mounts the same data by name.
const next = await Sandbox.create('base', {
  projectId: 'proj_123', volumeName: 'agent-workdir',
});
try {
  console.log(await next.fs.read('/workspace/state.json'));
} finally {
  await next.kill();
}
```

```python
from lizard import Sandbox, Volume

Volume.get_or_create("proj_123", "agent-workdir", size_gb=10)
sandbox = Sandbox.create("base", project_id="proj_123", volume_name="agent-workdir")
try:
    sandbox.process.exec_("echo hello > /workspace/state.txt")
finally:
    sandbox.kill()
```

## Size and price

New volumes allow **1 to 50 GB**, in whole GB, with **5 GB** as the default. The server can override the maximum through `VOLUME_MAX_SIZE_GB`; the dashboard and CLI read the current limits and storage price from `GET /api/projects/:projectId/volume-limits`.

You pay for **used space**, not the full allocation. Existing volumes larger than the new maximum keep their size and remain accessible. Creating a new volume requires enough unreserved disk space on a healthy worker in the chosen region. If none has room, the API returns `503` with code `volume_capacity_unavailable`.

## Manage volumes

```ts
await Volume.list('proj_123');
const volume = await Volume.get('proj_123', 'agent-workdir');
await volume.getInfo('proj_123');
await Volume.resize('proj_123', 'agent-workdir', 20);
await Volume.delete('proj_123', 'agent-workdir'); // detach first
```

| TypeScript | Python | Purpose |
|---|---|---|
| `Volume.getOrCreate(projectId, name, options)` | `Volume.get_or_create(project_id, name, size_gb=5)` | Reuse a volume or create it |
| `Volume.create(projectId, name, options)` | `Volume.create(project_id, name, size_gb=5)` | Create; fail if the name is taken |
| `Volume.list(projectId)` | `Volume.list(project_id)` | List the project's volumes |
| `Volume.get(projectId, nameOrId)` | `Volume.get(project_id, name_or_id)` | Look up a volume |
| `Volume.resize(projectId, nameOrId, sizeGb)` | `Volume.resize(project_id, name_or_id, size_gb)` | Change the size |
| `Volume.delete(projectId, nameOrId)` | `Volume.remove(project_id, name_or_id)` | Delete a detached volume |

Generated IDs and the `volumeId` / `volume_id` fields still work for existing callers. Use names in new code.

You can resize a volume in place while it is attached. A Firecracker volume can only grow. A pod-backed volume can also shrink if at least 10% of the new size stays free.

## CLI

```bash
lizard volume create agent-workdir --size 10 --project proj_123
lizard volume list --project proj_123
lizard sandbox create --volume agent-workdir --project proj_123
lizard volume resize agent-workdir --size 20 --project proj_123
# After stopping the sandbox:
lizard volume rm agent-workdir --project proj_123
```

`--size` accepts whole GB and checks the current server limit before creating a volume. `volume list` shows names; use `--json` when you need IDs.

## Dashboard

Create and attach volumes with the SDK or CLI. Once a project has a volume, **Sandboxes → Persistent Volumes** lists used and allocated space, status, and the attached sandbox. From there you can browse files, resize, or delete a volume.

## Storage lifecycle

- Killing or expiring a sandbox detaches its volume and keeps the data.
- An attached volume cannot attach to another sandbox or be deleted.
- Deleting a volume removes its data. Volumes use node-local storage; export data you need after a node failure. See [storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md).

- Without a volume, `/workspace` is temporary: its files are gone when the sandbox stops.
- A sandbox with a volume attached cannot be forked (HTTP 409), and a snapshot does not capture an attached volume.

See the [SDK reference](https://lizard.build/docs/sandboxes/sdk-reference.md) for sandbox options and [lifecycle](https://lizard.build/docs/sandboxes.md#lifecycle) for pause, snapshots and fork.


---
title: "Sandboxes dashboard"
description: "Create, inspect, pause and stop Sandboxes in the Lizard dashboard: pick a template, region, size and lifetime, open a terminal, and manage volumes."
url: https://lizard.build/docs/sandboxes/dashboard/
updated: 2026-10-12
---

# Sandboxes dashboard

The dashboard lets you create and manage Sandboxes without code. Open a project and select Sandboxes. Create a sandbox with a template from the catalog, a region, and a size: Small, Medium (the default), or Large. Choose the region you need; the default may not be the nearest one. Pick a lifetime under **Delete after**: 15 minutes, 1 hour, 4 hours, 1 day, 7 days, or Never (the default).

## API keys

To create sandboxes from code, create an API key in Settings → API keys and store it in `LIZARD_API_KEY`. Lizard SDK and the [HTTP API](https://lizard.build/docs/sandboxes/sdk-reference.md#http-api) read it; see the [quickstart](https://lizard.build/docs/sandboxes/quickstart.md).

## Inspect and stop

The detail view shows status, template, region, measured usage, and exposed ports. Its header has Pause and Resume buttons, and the Terminal tab opens a shell in a running sandbox.

A running sandbox bills by the second at its size's hourly price, idle time included; a paused one does not, so pause or kill it when the task ends. See [storage and billing](https://lizard.build/docs/sandboxes.md#storage-and-billing).

## Templates and volumes

The Templates tab lists platform templates and your project's private snapshots; start a new sandbox from either. Create snapshots with the SDK or CLI. Kill Sandbox stops the sandbox and detaches its volume.

Create and attach volumes with the SDK or CLI; the create form has no volume option. The Persistent Volumes tab appears once a project has a volume. It shows each volume's size and attached sandbox, and lets you browse files, resize, or delete it. Stop the current sandbox before attaching the volume to another.

[Sandboxes overview](https://lizard.build/docs/sandboxes.md) · [Quickstart](https://lizard.build/docs/sandboxes/quickstart.md) · [SDK reference](https://lizard.build/docs/sandboxes/sdk-reference.md) · [Code interpreter](https://lizard.build/docs/sandboxes/code-interpreter.md) · [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md)


---
title: "Observability: logs, metrics and events"
description: "See what your services are doing: stream logs, track metrics and cost, and review deploy and replica history from the CLI or dashboard."
url: https://lizard.build/docs/observability/
updated: 2026-10-12
---

# Observability

See what your services are doing: stream logs, track metrics and cost, and review deploy and replica history.

## In this section

- [Logs](https://lizard.build/docs/observability/logs.md): live and historical logs, including build and restart logs.
- [Metrics & Cost](https://lizard.build/docs/observability/metrics.md): resource usage and what it costs.
- [Events & History](https://lizard.build/docs/observability/events.md): deploy timeline and per-replica status.

The CLI commands also accept `--json`, so coding agents can read their output when a deploy fails. See [deploying from Claude Code](https://lizard.build/blog/deploy-from-claude-code).


---
title: "Logs: runtime, build and crash logs"
description: "Stream runtime logs, read build logs after a failed deploy, and pull the log tail around a restart, from the CLI or the dashboard."
url: https://lizard.build/docs/observability/logs/
updated: 2026-10-12
---

# Logs

Lizard captures **runtime** logs, **build** logs, and the log tail around **restarts**. Stream them live in your terminal or browse them in the dashboard.

Lizard collects runtime logs while a log stream is open (`lizard logs` or the dashboard Logs page, plus 60 seconds after it closes), around deploys and restarts, and while a service is crash-looping. Opening a stream fills in the last 500 lines per replica.

## Runtime logs

```bash
lizard logs                       # last 200 runtime lines, then live tail
lizard logs --service api         # a specific service
lizard logs --tail 1000           # print the last 1000 lines and exit (max 1000)
lizard logs --level error         # filter by level
```

### Behavior with `--json`

`lizard logs --json` returns the last 200 lines (override with `--tail N`, max 1000) and exits. Use it for snapshots and scripting. A snapshot taken while nobody was streaming can miss recent lines; open a stream first if you need them. Stream live only without `--json` when you want an interactive tail.

## Build logs

```bash
lizard logs --build               # the most recent build's logs
```

This is the first thing to check when a deploy fails: read it, fix the cause, then `lizard redeploy`. See [Build Pipeline](https://lizard.build/docs/concepts/build-pipeline.md).

## Restart / crash logs

When a replica crashes or restarts, grab the surrounding log tail:

```bash
lizard logs --restart latest      # the most recent restart
lizard logs --restart         # a specific restart
lizard logs --restarts            # list recent restarts
```

## Paging through history

`lizard service logs` supports paging older windows:

```bash
lizard service logs --service api --tail all     # last 2,000 lines (no follow)
lizard service logs --service api --page 2       # older window (implies --tail 200)
```

## Dashboard

The dashboard (`lizard open`) streams runtime and build logs with search and level filters. Open a service and click a deploy in its history to see that deploy's build output.

## See also

- [`lizard logs`](https://lizard.build/docs/cli/logs.md): the full command reference.
- [Events & History](https://lizard.build/docs/observability/events.md): deploy history and replica status.
- [Deploy from Claude Code](https://lizard.build/blog/deploy-from-claude-code): using logs to diagnose a deploy with an agent.


---
title: "Metrics & Cost"
description: "Track CPU, memory, network, and disk for a service, see what the current scale costs, and act on what the numbers show."
url: https://lizard.build/docs/observability/metrics/
updated: 2026-10-12
---

# Metrics & Cost

Check a service's CPU, memory, network, disk and cost with [`lizard metrics`](https://lizard.build/docs/cli/metrics.md) or in the dashboard.

## View metrics

```bash
lizard metrics                    # CPU / memory / network / disk
lizard metrics --service api      # a specific service
lizard metrics --range 24h        # time window: 1h (default), 6h, 24h, 7d, 14d, 30d
lizard metrics --watch            # live-updating view
```

## Include cost

```bash
lizard metrics --cost
```

This command shows cost instead of metrics: each running resource's price per hour, usage for the current billing period (egress included), and how much of Pro's included $19 you have used this month. It covers the whole project, ignores `--service` and `--range`, and only the workspace owner can see it.

## Dashboard

The dashboard (`lizard open`) renders these as charts in the **Metrics** view, and the **Usage** view breaks down consumption and billing across the project. Open it with:

```bash
lizard open
```

## Acting on what you see

- High CPU/memory → scale up: `lizard scale --service api --cpu 2 --memory 2048`.
- Sustained load → add replicas: `lizard scale --service api --replicas 3`. Pro allows 1 replica per service during the trial and 5 after the first paid invoice.
- Disk pressure on an addon → grow storage: `lizard scale --service postgres --storage 8192`.

See [Scaling](https://lizard.build/docs/deploy/scaling.md).


---
title: "Events & History"
description: "Read a service's deploy timeline and live replica status with lizard events, and find the same history in the dashboard's Deployments view."
url: https://lizard.build/docs/observability/events/
updated: 2026-10-10
---

# Events & History

`lizard events` shows a service's deploy history and the live status of its replicas — the timeline of what shipped and what's running right now.

## View events

```bash
lizard events                     # deploy history + replica status
lizard events --service api       # a specific service
lizard events --limit 5           # fewer entries (default and maximum 10)
```

Each entry is a deploy: status, trigger, commit, and restart count. The Replicas table below shows the current state of each pod, so you can answer "what changed, and is it healthy?" at a glance.

## Quick status

For a fast snapshot of all services in the project — status and URL — use:

```bash
lizard ps
lizard status        # the current directory's workspace/project/service link
```

## Dashboard

Open a service in the dashboard (`lizard open`) to see its deploy history. Click a deploy for its build logs, commit, and crash events. The service panel shows running and desired replicas.

## See also

- [Deployments](https://lizard.build/docs/concepts/deployments.md) — the deploy lifecycle.
- [Logs](https://lizard.build/docs/observability/logs.md) — runtime, build, and restart logs.
- [Metrics & Cost](https://lizard.build/docs/observability/metrics.md) — resource usage and billing.
- [`lizard events`](https://lizard.build/docs/cli/events.md) — the full command reference.


---
title: "Lizard CLI reference: install, flags, exit codes"
description: "Install and upgrade the Lizard CLI, authenticate, and look up global flags, exit codes, and runtime schema discovery for every command."
url: https://lizard.build/docs/cli/
updated: 2026-10-12
---

# CLI Reference

Lizard CLI (`lizard`) deploys and manages apps, databases and Sandboxes on Lizard from a terminal, a CI job or a coding agent. This page covers installation, global flags, exit codes, and runtime discovery. Each command has a reference page in the sidebar. See [`lizard up`](https://lizard.build/docs/cli/up.md), [`lizard service`](https://lizard.build/docs/cli/service.md), [`lizard secrets`](https://lizard.build/docs/cli/secrets.md), and the rest. For an overview of what the CLI does, see the [Lizard CLI page](https://lizard.build/cli).

> Reference written for Lizard CLI **v4.0.34**. Run `lizard  --help --json` for the exact, version-matched schema of any command.

## Install & upgrade

```bash
curl -fsSL https://lizard.build/install.sh | bash   # macOS and Linux
lizard --version
lizard upgrade            # update to the latest version
lizard upgrade --check    # check without installing
```

See [`lizard upgrade`](https://lizard.build/docs/cli/upgrade.md) for the flags.

On Windows, run `irm https://lizard.build/api/cli/install.ps1 | iex` in PowerShell. With Node.js you can install from npm instead: `npm install -g @lizard-build/cli`.

On macOS and Linux, the binary from the install script also updates itself in the background, at most once every 6 hours. It skips the check when `CI` or `LIZARD_NO_UPDATE=1` is set. The Windows binary does not update itself: run the installer again. An npm install updates through npm: `lizard upgrade` prints the command.

Use the globally installed `lizard` binary rather than `npx`. See [Quickstart](https://lizard.build/docs/getting-started.md#1-install-the-cli) for permission-error fixes.

## Authentication

```bash
lizard login                  # browser OAuth
lizard login --token liz_xxx  # API key auth
lizard logout
lizard whoami                 # current user, workspace, linked project
```

Reference pages: [`lizard login`](https://lizard.build/docs/cli/login.md), [`lizard logout`](https://lizard.build/docs/cli/logout.md), [`lizard whoami`](https://lizard.build/docs/cli/whoami.md), and [`lizard workspace`](https://lizard.build/docs/cli/workspace.md) to list and create workspaces.

API keys start with `liz_`. Create one with [`lizard keys create `](https://lizard.build/docs/cli/keys.md). In CI, set `LIZARD_TOKEN` (or `LIZARD_API_KEY`) in the environment instead of running `login`.

## Global flags

| Flag | Description |
|------|-------------|
| `-V, --version` | Print the CLI version |
| `--json` | Machine-readable output. Combine with `--help` to dump a command's schema |

Most commands also accept `-p, --project` and `-s, --service` to target a specific resource instead of the linked one. `init`, `link`, and `project` also take `-w, --workspace`. `lizard add` always uses the linked project.

## Exit codes

| Code | Meaning | What to do |
|------|---------|------------|
| `0` | success | continue |
| `1` | generic error | read the message |
| `2` | auth (401/403) | run `lizard login` |
| `3` | not found (404) | check the name with `lizard ps` / `lizard project list` |
| `4` | timeout (408/504) | retry |
| `5` | cancelled by user | stop |

## Runtime discovery

The CLI is self-documenting. To see the full command tree, global flags, and exit codes:

```bash
lizard --help --json
```

For any command or subcommand's exact arguments and options:

```bash
lizard  --help --json
lizard   --help --json     # e.g. lizard service set --help --json
```

The output lists the arguments and flags your installed version accepts. It is also how an agent learns the CLI without being trained on it: see [deploying from Claude Code](https://lizard.build/blog/deploy-from-claude-code).

## Configuration & state

- The CLI stores auth and the current directory's project link in `~/.lizard/config.json`.
- [`lizard status`](https://lizard.build/docs/cli/status.md) shows the cwd's workspace/project/service link (no auth required). [`lizard link`](https://lizard.build/docs/cli/link.md) sets that link, [`lizard unlink`](https://lizard.build/docs/cli/unlink.md) removes it, and [`lizard project`](https://lizard.build/docs/cli/project.md) lists, creates, and deletes projects.
- [`lizard config apply`](https://lizard.build/docs/cli/config.md) applies a `lizard-config.json` file to a project (use `--dry-run` to preview).
- [`lizard open`](https://lizard.build/docs/cli/open.md) opens the linked project in the dashboard, and [`lizard docs`](https://lizard.build/docs/cli/docs.md) opens these docs.

## Environment variables

| Variable | Effect |
|----------|--------|
| `LIZARD_TOKEN` | API key or token to use instead of the stored login |
| `LIZARD_API_KEY` | Same as `LIZARD_TOKEN` |
| `LIZARD_API_URL` | API base URL (default `https://lizard.build`) |
| `LIZARD_HOME` | Keep CLI state in `$LIZARD_HOME/.lizard` instead of `~/.lizard` |
| `LIZARD_NO_UPDATE` | Turn off background self-update |
| `CI` | Also turns off background self-update |
| `LIZARD_SKILLS_DIR` | Read skills from this directory instead of the ones built into the CLI |

## Sandbox volumes

Use names to manage [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md) within a project. Full reference: [`lizard volume`](https://lizard.build/docs/cli/volume.md) and [`lizard sandbox`](https://lizard.build/docs/cli/sandbox.md).

```bash
lizard volume create agent-workdir --size 10 --project proj_123
lizard volume list --project proj_123
lizard volume resize agent-workdir --size 20 --project proj_123
lizard sandbox create --volume agent-workdir --project proj_123
```

Names are unique per project: lowercase letters, digits and dashes, starting and ending with a letter or digit, up to 64 characters. New volumes allow whole GB from 1 to 50, default 5. The CLI reads the server's current limits before creating a volume, so it follows config changes. Existing larger volumes keep their size.

After detaching a volume, `lizard volume rm agent-workdir --project proj_123` deletes it and its data. `volume list` shows names; `--json` includes generated IDs.


---
title: "JSON & Automation"
description: "Script the Lizard CLI: --json on every command, line-delimited events for streaming builds, schema discovery, and token auth in CI."
url: https://lizard.build/docs/cli/json/
updated: 2026-10-10
---

# JSON & Automation

The CLI is built to be scripted. Pass `--json` for machine-readable output, drive it from CI with a token, and discover any command's schema at runtime.

The other reader of this output is an AI coding agent — [deploying from Claude Code](https://lizard.build/blog/deploy-from-claude-code#check-the-result-from-the-outside) shows the JSON log commands it runs to check a deploy.

## `--json` everywhere

Add `--json` to any command for structured output. The CLI also switches to JSON automatically when stdout isn't a TTY.

```bash
lizard ps --json
lizard secrets list --json
lizard metrics --json
```

### Streaming commands

For streaming commands (`lizard up` without `--detach`), `--json` emits **one JSON object per line**:

```json
{ "event": "log", "line": "Step 1/8 : FROM node:20" }
{ "event": "log", "line": "..." }
{ "event": "deployed", "status": "running", "url": "https://app.onlizard.com" }
```

The build stream ends with `done` or `error`. Without `--ci`, `lizard up` then emits a final `deployed`, `failed`, or `deploying` event with `status`. Only `deployed` carries `url` (which may be `null`).

### `logs --json` is a snapshot, not a stream

`lizard logs --json` returns the **last 200 lines** (override with `--tail N`, max 1000) and exits. Don't wait on it expecting more output. For a specific incident, use `--restart latest` or `--restart `.

## Schema discovery

Dump any command's exact arguments, options, and exit codes:

```bash
lizard --help --json                 # whole tree + global flags + exit codes
lizard service set --help --json     # one command
```

The response shape is `{ cli, version, agents, command: { arguments, options, subcommands }, globalOptions, exitCodes }`. Because it's generated from your installed binary, it always matches your version — prefer it over hard-coding flags.

## CI / headless usage

Authenticate with a token and link the project explicitly (headless `up` won't auto-create a project):

```bash
# Set LIZARD_TOKEN through your CI secret store.
lizard init --name my-project
lizard add --service api   # first run only; skip when the service exists
lizard up --ci --service api --detach
```

Check exit codes to branch your pipeline:

| Code | Meaning |
|------|---------|
| `0` | success |
| `1` | generic error |
| `2` | auth — token missing/expired |
| `3` | not found |
| `4` | timeout |
| `5` | cancelled |

`--wait` makes `redeploy` wait for the build and for the new deploy to become ready. Without it, JSON mode exits `0` as soon as Lizard accepts the build request.

```bash
if lizard redeploy --service api --wait --json; then
  echo "Deploy succeeded"
else
  status=$?
  echo "Deploy failed with code $status" >&2
  lizard logs --build --json || true
  exit "$status"
fi
```


---
title: "lizard login"
description: "lizard login authenticates through your browser or an API token, plus how to authenticate non-interactively in CI with LIZARD_TOKEN."
url: https://lizard.build/docs/cli/login/
updated: 2026-10-10
---

# lizard login

Log in to Lizard.

## Usage

```bash
lizard login [flags]
```

By default this opens the sign-in page in your browser, prints its URL, and exits. After you sign in, run your next command (for example `lizard whoami`); it finishes the login and saves your credentials. With `--json`, `login` prints `{ "status": "pending", "authUrl": "…" }` and does not open a browser.

## Flags

| Flag | Description |
|------|-------------|
| `--token ` | Authenticate with an API key (`liz_…`) |

## Examples

### Log in via browser

```bash
lizard login
```

### Log in with an API key

Create a key with [`lizard keys create `](https://lizard.build/docs/cli/keys.md), then:

```bash
lizard login --token liz_xxx
```

### Log in non-interactively in CI

In CI or other non-interactive environments, set `LIZARD_TOKEN` (or `LIZARD_API_KEY`) in the environment instead of running `login`:

```bash
export LIZARD_TOKEN=liz_xxx
```

## See also

- [lizard logout](https://lizard.build/docs/cli/logout.md) — log out
- [lizard whoami](https://lizard.build/docs/cli/whoami.md) — show the current user, active workspace, and linked project
- [CLI reference](https://lizard.build/docs/cli.md) — global flags, exit codes, and authentication overview


---
title: "lizard logout"
description: "lizard logout signs you out of the Lizard CLI and clears the credentials stored on this machine. Log back in with lizard login."
url: https://lizard.build/docs/cli/logout/
updated: 2026-08-09
---

# lizard logout

Log out.

## Usage

```bash
lizard logout
```

## See also

- [lizard login](https://lizard.build/docs/cli/login.md) — authenticate with Lizard
- [lizard whoami](https://lizard.build/docs/cli/whoami.md) — show the current user, active workspace, and linked project


---
title: "lizard whoami"
description: "lizard whoami prints the account you are logged in as, your active workspace, and the project linked to this directory."
url: https://lizard.build/docs/cli/whoami/
updated: 2026-08-09
---

# lizard whoami

Show the current user, active workspace, and linked project.

## Usage

```bash
lizard whoami
```

## Examples

### Check who you're logged in as

```bash
lizard whoami
```

## See also

- [lizard login](https://lizard.build/docs/cli/login.md) — authenticate before checking your identity
- [lizard status](https://lizard.build/docs/cli/status.md) — show the linked workspace, project, and service for the current directory
- [lizard workspace](https://lizard.build/docs/cli/workspace.md) — list the workspaces you can access


---
title: "lizard workspace"
description: "lizard workspace creates, lists, and deletes workspaces, and shows how workspaces relate to projects and services."
url: https://lizard.build/docs/cli/workspace/
updated: 2026-10-10
---

# lizard workspace

Create, list, and delete workspaces.

## Usage

```bash
lizard workspace list
lizard workspace create 
lizard workspace rm  [flags]
```

## Subcommands

### `lizard workspace list`

List the workspaces you belong to, with your role and project count (alias `ls`).

### `lizard workspace create `

Create a workspace. Add projects to it with `lizard project create  --workspace `.

### `lizard workspace rm `

Delete a workspace (alias `delete`). Only the owner can delete it, and only when it has no projects or sandboxes left. You can't delete your personal workspace.

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |

## Examples

### List your workspaces

```bash
lizard workspace list
```

### Create a workspace

```bash
lizard workspace create my-team
```

### Delete an empty workspace

```bash
lizard workspace rm my-team --yes
```

## See also

- [lizard whoami](https://lizard.build/docs/cli/whoami.md) — shows your active workspace
- [lizard project](https://lizard.build/docs/cli/project.md) — list and create projects in a workspace
- [Architecture: Workspace](https://lizard.build/docs/concepts/architecture.md) — how workspaces relate to projects and services


---
title: "lizard keys"
description: "lizard keys creates, lists, and deletes liz_ API keys for CI, the Lizard SDK, and sandboxes, optionally scoped to projects or workspaces."
url: https://lizard.build/docs/cli/keys/
updated: 2026-10-10
---

# lizard keys

Create, list, and delete API keys. Alias: `lizard apikey`.

## Usage

```bash
lizard keys create  [flags]
lizard keys list
lizard keys rm  [flags]
```

API keys start with `liz_`. Use one wherever you can't log in through a browser:

- `LIZARD_TOKEN` or `LIZARD_API_KEY` for the CLI in CI
- `lizard login --token liz_…` on a machine
- `LIZARD_API_KEY` for the [Lizard SDK](https://lizard.build/docs/sandboxes/sdk-reference.md)
- `lizard sandbox create --with-token liz_…` to use `lizard` inside a sandbox

## Subcommands

### `lizard keys create `

Create a key and print it once. Copy it then; Lizard does not show it again. A key without a scope has full access to every workspace and project you can reach. Scope it with these flags, each repeatable:

| Flag | Description |
|------|-------------|
| `-p, --project ` | Limit the key to this project (name, slug, or ID) |
| `-w, --workspace ` | Limit the key to this workspace (name, slug, or ID) |

A scoped key can't see account billing or settings.

### `lizard keys list`

List your keys with a short preview, scope, creation time, and last use (alias `ls`).

### `lizard keys rm `

Delete a key (alias `delete`). If two keys share a name, delete by ID.

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |

## Examples

### Create a key for CI

```bash
lizard keys create github-actions --project my-project
```

Store the printed key as a CI secret named `LIZARD_TOKEN`.

### Create a key for a sandbox

```bash
lizard keys create sandbox-agent --workspace my-team
lizard sandbox create --with-token liz_xxx
```

### Delete a key

```bash
lizard keys rm github-actions --yes
```

## See also

- [lizard login](https://lizard.build/docs/cli/login.md) — log in with a key on a machine
- [JSON & Automation](https://lizard.build/docs/cli/json.md) — using the CLI from CI
- [lizard sandbox](https://lizard.build/docs/cli/sandbox.md) — `--with-token` installs a key in a sandbox


---
title: "lizard billing"
description: "lizard billing shows your Pro plan, trial and usage, starts or cancels Pro, redeems promo codes, and lists Enterprise balance history and saved cards."
url: https://lizard.build/docs/cli/billing/
updated: 2026-10-10
---

# lizard billing

Show and manage your Lizard (lizard.build) plan: the trial, Pro, this month's usage and promo codes. `lizard credits` is the old name. It still works but prints a warning, and a later release will remove it.

## Usage

```bash
lizard billing [subcommand] [flags]
```

With no subcommand, `lizard billing` shows the same as `lizard billing status`. The commands act on the plan of the account you are signed in with. See [Billing](https://lizard.build/docs/platform/billing.md) for how Pro, the trial and overage work.

Commands that charge, cancel or remove a card ask you to confirm. Without a terminal, for example in CI or an agent, pass `-y` instead. Commands that open a page always print its link, and open your browser only in an interactive terminal.

## Subcommands

### `lizard billing status`

Show your plan and where it stands: days and trial credits left, how much of the $19 in monthly credits you have used, overage so far, the next charge and its date, and any unpaid invoice.

| Flag | Description |
|------|-------------|
| `-w, --workspace ` | Show the plan of this workspace's owner |

### `lizard billing open`

Open the Billing page in your browser. Pay an open invoice, add or change your card, or see past invoices there.

| Flag | Description |
|------|-------------|
| `--no-open` | Print the link instead of opening a browser |

### `lizard billing start`

Start Pro in Stripe Checkout, where you add a card. The command opens Checkout in your browser. A new account gets the 7-day trial with $5 in trial credits, and nothing is charged until the trial ends. If the account had a trial before, Pro starts at once and charges $19. If the card had one, the trial does not start and nothing is charged; start Pro at $19 on the Billing page.

| Flag | Description |
|------|-------------|
| `--return-url ` | Dashboard path to come back to after Checkout, for example `/projects/abc` |
| `--no-open` | Print the Checkout link instead of opening a browser |

### `lizard billing start-now`

End the trial and start your first paid month now. Lizard charges $19 and adds $19 in monthly credits. Use it when trial credits run out. If your bank asks you to confirm the payment, the command gives you the invoice page to do so; the trial goes on until the payment goes through.

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |
| `--no-open` | Print the payment link instead of opening a browser |

### `lizard billing cancel`

Cancel at the end of the current month or trial. Pro stays active until then. Cancelling during the trial costs nothing.

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |

### `lizard billing resume`

Undo a cancel before the end date.

### `lizard billing promo `

Redeem a promo code for a longer trial and more trial credits. Before the trial starts, Lizard saves the code for it. During the trial, the code extends it. Codes work only before your first payment, one per account.

### `lizard billing balance`

Show the balance of an Enterprise account.

### `lizard billing transactions`

List recent balance transactions of an Enterprise account (alias `ledger`).

| Flag | Description |
|------|-------------|
| `-l, --limit ` | Max results, `1`–`100` (default `20`) |
| `-c, --cursor ` | Page cursor from a previous call |
| `-u, --usage` | Include daily usage rows |

### `lizard billing payment-methods list`

List saved cards. `pm` is short for `payment-methods`. Add, change or remove a Pro card on the Billing page.

### `lizard billing payment-methods remove `

Remove a saved card (alias `rm`). This works only for Enterprise accounts; a Pro account gets HTTP 409 and removes its card on the Billing page.

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |

## Examples

### Check your plan and usage

```bash
lizard billing status
lizard billing status --json
```

### Start the trial with a promo code

```bash
lizard billing promo YOURCODE
lizard billing start
```

### Cancel, then change your mind

```bash
lizard billing cancel --yes
lizard billing resume
```

## Payment required

If you try to create a resource while the account has no plan, has used up its trial credits or has an unpaid invoice, Lizard answers with HTTP 402. Lizard CLI prints the reason and a link to the Billing page, for example:

```text
Start your 7-day Pro trial with $5 in credits to deploy. No charge today, then $19/month.
```

## See also

- [Billing](https://lizard.build/docs/platform/billing.md) — Pro, the trial, overage, payment failures and cancelling
- [Limits](https://lizard.build/docs/platform/limits.md) — trial and Pro limits for Sandboxes and replicas
- [lizard whoami](https://lizard.build/docs/cli/whoami.md) — shows the account you are signed in with


---
title: "lizard init"
description: "lizard init creates or selects a project and links it to the current directory, including the explicit form you need in CI."
url: https://lizard.build/docs/cli/init/
updated: 2026-10-10
---

# lizard init

Create or select a project and link it to the current directory.

## Usage

```bash
lizard init [flags]
```

## Flags

| Flag | Description |
|------|-------------|
| `-n, --name ` | Project name (create if missing) |
| `-w, --workspace ` | Workspace id, slug, or name |
| `--force` | Relink even if already linked |

## Examples

### Link the current directory interactively

```bash
lizard init
```

### Link explicitly in CI

`lizard up` will run `init` for you on a TTY, but in a non-TTY (CI) environment it errors instead of silently creating a project. Link explicitly first:

```bash
export LIZARD_TOKEN=liz_xxx
lizard init --name my-project
lizard add --service api   # first run only; skip when the service exists
lizard up --ci --service api
```

### Relink a directory that's already linked

```bash
lizard init --name my-project --force
```

## See also

- [lizard link](https://lizard.build/docs/cli/link.md) — associate the current directory with an existing project instead of creating one
- [lizard status](https://lizard.build/docs/cli/status.md) — show the linked workspace, project, and service for the current directory
- [lizard up](https://lizard.build/docs/cli/up.md) — upload and deploy the current directory
- [Core Concepts](https://lizard.build/docs/concepts/architecture.md) — projects, services, and the build pipeline


---
title: "lizard link"
description: "lizard link associates the current directory with an existing project, and optionally a specific service or workspace."
url: https://lizard.build/docs/cli/link/
updated: 2026-08-09
---

# lizard link

Associate the current directory with an existing project (and optionally a service).

## Usage

```bash
lizard link [service] [flags]
```

## Flags

| Flag | Description |
|------|-------------|
| `-p, --project ` | Project name, slug, or ID |
| `-s, --service ` | Service to link |
| `-w, --workspace ` | Workspace |

## Examples

### Link to an existing project

```bash
lizard link --project my-project
```

### Link to a project and a specific service

```bash
lizard link my-service --project my-project
```

### Link within a specific workspace

```bash
lizard link --project my-project --workspace my-workspace
```

## See also

- [lizard init](https://lizard.build/docs/cli/init.md) — create or select a project and link it in one step
- [lizard unlink](https://lizard.build/docs/cli/unlink.md) — remove the link from the current directory
- [lizard status](https://lizard.build/docs/cli/status.md) — show the linked workspace, project, and service
- [Architecture: Project](https://lizard.build/docs/concepts/architecture.md) — how the directory-to-project link works


---
title: "lizard unlink"
description: "lizard unlink removes the link between the current directory and any project, leaving the project itself untouched."
url: https://lizard.build/docs/cli/unlink/
updated: 2026-08-09
---

# lizard unlink

Disassociate the current directory from any project.

## Usage

```bash
lizard unlink
```

## Examples

### Unlink the current directory

```bash
lizard unlink
```

## See also

- [lizard link](https://lizard.build/docs/cli/link.md) — associate the current directory with a project
- [lizard status](https://lizard.build/docs/cli/status.md) — check the linked workspace, project, and service


---
title: "lizard status"
description: "lizard status shows which workspace, project, and service the current directory is linked to, before you deploy to the wrong one."
url: https://lizard.build/docs/cli/status/
updated: 2026-08-09
---

# lizard status

Show the linked workspace, project, and service for the current directory.

## Usage

```bash
lizard status
```

## Examples

### Check the current directory's link

```bash
lizard status
```

## See also

- [lizard init](https://lizard.build/docs/cli/init.md) — create or select a project and link it to the current directory
- [lizard link](https://lizard.build/docs/cli/link.md) — associate the current directory with an existing project
- [lizard whoami](https://lizard.build/docs/cli/whoami.md) — show the current user, active workspace, and linked project


---
title: "lizard project"
description: "lizard project lists your projects, creates a new one, or deletes one, with examples for each subcommand."
url: https://lizard.build/docs/cli/project/
updated: 2026-10-10
---

# lizard project

List, create, or delete projects.

## Usage

```bash
lizard project list [flags]
lizard project create  [flags]
lizard project delete [project] [flags]
```

## Subcommands

### `lizard project list`

List your projects across all workspaces, or in one workspace with `-w`.

| Flag | Description |
|------|-------------|
| `-w, --workspace ` | Only this workspace (id, slug, or name) |

### `lizard project create `

Create a project. It does not link the current directory; use [`lizard init`](https://lizard.build/docs/cli/init.md) for that.

| Flag | Description |
|------|-------------|
| `-w, --workspace ` | Workspace to create the project in |

### `lizard project delete [project]`

Move a project to trash (alias `rm`). Its services stop. You can recover it for 3 days; after that Lizard deletes it for good. Without an argument it deletes the project linked to this directory.

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation (required without a TTY) |

## Examples

### List projects

```bash
lizard project list
lizard project list --workspace my-team
```

### Create a new project

```bash
lizard project create my-project
lizard project create my-project --workspace my-team
```

### Delete a project

```bash
lizard project delete my-project --yes
```

## See also

- [lizard init](https://lizard.build/docs/cli/init.md) — create or select a project and link it to the current directory
- [lizard link](https://lizard.build/docs/cli/link.md) — associate the current directory with an existing project
- [Architecture](https://lizard.build/docs/concepts/architecture.md) — how projects relate to workspaces and services


---
title: "lizard config"
description: "lizard config apply pushes a lizard-config.json file to your project, with a --dry-run flag to preview the changes first."
url: https://lizard.build/docs/cli/config/
updated: 2026-10-10
---

# lizard config

Apply a `lizard-config.json` file to the project.

## Usage

```bash
lizard config apply [flags]
```

## Subcommands

### `lizard config apply`

Apply a `lizard-config.json` file to the project.

| Flag | Description |
|------|-------------|
| `-f, --file ` | Config file (default `lizard-config.json`) |
| `-p, --project ` | Project name, slug, or ID |
| `--dry-run` | Print the parsed file and target project without applying. It does not compare with the current config |

## Examples

### Apply the default config file

```bash
lizard config apply
```

### Apply a config file from a custom path

```bash
lizard config apply -f ./configs/prod.json
```

### Preview changes without applying them

```bash
lizard config apply --dry-run
```

## See also

- [lizard service](https://lizard.build/docs/cli/service.md) — manage individual services
- [Architecture](https://lizard.build/docs/concepts/architecture.md) — how workspaces, projects, and services relate


---
title: "lizard add"
description: "lizard add creates a service from a GitHub repo, an empty service, or a managed addon. Full flag reference with worked examples."
url: https://lizard.build/docs/cli/add/
updated: 2026-10-10
---

# lizard add

Add a database, service, or repo to the project.

## Usage

```bash
lizard add [types...] [flags]
```

`add` works on the project linked to the current directory. Run [`lizard init`](https://lizard.build/docs/cli/init.md) or [`lizard link`](https://lizard.build/docs/cli/link.md) first.

## Flags

| Flag | Description |
|------|-------------|
| `-r, --repo ` | Create a service from a GitHub repo |
| `-s, --service ` | Create an empty service |
| `-a, --addon ` | Add managed addons (`postgres` / `redis` / `s3`) |
| `-n, --name ` | Name used in `${{name.KEY}}` refs |
| `-v, --variables ` | Seed an env var (repeatable) |
| `--region ` | Region to provision in (default `us-east-1`) |
| `--no-deploy` | Attach repo but skip the first build |
| `--list` | Show available database types |

## Examples

### Create a service from a GitHub repo

```bash
lizard add -r your-org/your-app
```

This creates a `github`-source service and starts the first build, which clones the repo and auto-detects the stack. The command returns once the service exists; follow the build with `lizard logs --build`.

### Provision addons

```bash
lizard add postgres            # one addon
lizard add postgres redis s3   # several at once
lizard add --list              # show available types
```

### Create an empty service

```bash
lizard add -s worker
```

### Name a service and seed variables

```bash
lizard add -r your-org/monorepo -n api -v NODE_ENV=production -v LOG_LEVEL=info
```

## See also

- [Deploy from GitHub](https://lizard.build/docs/deploy/github.md) — connecting repos, private access, and auto-redeploy on push
- [Managed Addons](https://lizard.build/docs/addons.md) — provisioning, naming, and using Managed Postgres, Managed Redis, and Managed Object Storage
- [lizard up](https://lizard.build/docs/cli/up.md) — upload and deploy the current directory instead of linking a repo


---
title: "lizard up"
description: "lizard up packages the current directory, builds it on Lizard's build nodes, and returns a live URL. Flags, CI behavior, and examples."
url: https://lizard.build/docs/cli/up/
updated: 2026-10-10
---

# lizard up

Upload the current directory and deploy it.

## Usage

```bash
lizard up [path] [flags]
```

`up` packages your working tree (or `path`) as a tarball, sends it to the build nodes, and deploys it. It forces `sourceType=upload` on the target service, streams build logs over SSE, and prints the live URL when finished.

If the current directory isn't linked to a project yet, `up` runs `init` first. On a TTY that's interactive; in a non-TTY (CI) environment it will **not** silently create a project — it errors and asks you to run `lizard init --name ` (or pass `--project ` to `up`), to guard against a typo spawning an empty project.

## Flags

| Flag | Description |
|------|-------------|
| `-s, --service ` | Target an existing service |
| `-p, --project ` | Project name, slug, or ID |
| `--build-command ` | Override the build command |
| `--start-command ` | Override the start command |
| `--pre-deploy-command ` | Run before the start command each time a container starts (every replica, every restart). Needs a start command; make it safe to run many times |
| `--port ` | Container port (`0` = worker mode). New services default to `3000` unless a Dockerfile `EXPOSE` sets it |
| `--region ` | Region for a new service; ignored for an existing one |
| `-d, --detach` | Start the deploy and exit without streaming logs |
| `-c, --ci` | Stream build logs only and exit when the build ends |
| `--no-gitignore` | Upload everything, ignoring `.gitignore` |

## Subcommands

### `lizard up status `

Show a service's status, URL, and latest build status. Pass the service ID that `up --detach` prints.

## Examples

### Deploy the current directory

```bash
lizard up
```

### Deploy to a specific service with a custom start command and port

Create the named service with `lizard add --service api` first if it does not exist.

```bash
lizard up --service api --start-command "node server.js" --port 8080
```

### Deploy headlessly in CI

```bash
export LIZARD_TOKEN=liz_xxx
lizard init --name my-project
lizard add --service api
lizard up --ci --service api
```

Skip `lizard add` when the service already exists. A failed build or deploy exits with code `1`. With `--ci`, the exit code covers the build only, so check `lizard ps --json` before you mark the release done. `--detach` exits `0` once the upload is accepted. See [framework setup](https://lizard.build/docs/framework-guides.md#prepare-the-project).

## See also

- [Deploy from local code](https://lizard.build/docs/deploy/upload.md) — full walkthrough of upload-based deploys
- [lizard redeploy](https://lizard.build/docs/cli/redeploy.md) — rebuild the last upload without re-uploading
- [lizard init](https://lizard.build/docs/cli/init.md) — create or link a project explicitly before deploying
- [Worker services](https://lizard.build/docs/deploy/workers.md) — what `--port 0` means
- [Container as a service](https://lizard.build/blog/container-as-a-service#what-happens-when-you-deploy-a-container) — what a platform does when you deploy a container


---
title: "lizard redeploy"
description: "lizard redeploy triggers a fresh build from the latest commit or upload using current variables, streaming logs unless you pass --detach."
url: https://lizard.build/docs/cli/redeploy/
updated: 2026-10-10
---

# lizard redeploy

Trigger a fresh build (latest commit / last upload) with current variables.

## Usage

```bash
lizard redeploy [nameOrId] [flags]
```

## Flags

| Flag | Description |
|------|--------------|
| `-s, --service ` | Service |
| `--detach` | Return after the build is accepted |
| `--wait` | Wait for the build and deployment readiness; works with `--json` |
| `--timeout ` | Readiness wait budget after the build; default `120` |
| `--json` | Return JSON for scripts |

## Examples

### Rebuild and redeploy a service

```bash
lizard redeploy --service api
```

### Redeploy without streaming logs

```bash
lizard redeploy --service api --detach
```

### Wait in a script

```bash
lizard --json redeploy --service api --wait --timeout 120
```

Without `--wait`, JSON mode returns after the build request is accepted. With `--wait`, the CLI follows that build and then waits for deployment readiness. The JSON result includes `buildId`, `ok`, and `status`. A build failure, failed readiness check, or timeout returns a nonzero exit code.

The timeout applies to readiness after the build finishes; it does not limit build duration or cancel the deployment. Workers with `containerPort=0` use the readiness status from the backend without an HTTP check. After a successful command, check an application endpoint or stored data if your workflow needs to verify application behavior.

## See also

- [lizard restart](https://lizard.build/docs/cli/restart.md) — rolling restart of the current build, no rebuild
- [lizard up](https://lizard.build/docs/cli/up.md) — upload the current directory and deploy it
- [Deployments](https://lizard.build/docs/concepts/deployments.md) — how deploys, restarts, and rebuilds work


---
title: "lizard restart"
description: "lizard restart performs a rolling restart of a service's current build with no rebuild, useful for recovering from a crash."
url: https://lizard.build/docs/cli/restart/
updated: 2026-10-10
---

# lizard restart

Rolling restart of the current build — no rebuild.

## Usage

```bash
lizard restart [nameOrId] [flags]
```

## Flags

| Flag | Description |
|------|--------------|
| `-s, --service ` | Service |
| `--detach` | Return after the restart is accepted, without polling its status |
| `--wait` | Wait for the new restart attempt to become ready; works with `--json` |
| `--timeout ` | Readiness wait budget with `--wait`; default `120` |
| `--json` | Return JSON for scripts |

## Examples

### Restart a service

```bash
lizard restart --service api
```

### Return without waiting

```bash
lizard restart --service api --detach
```

### Wait before running the next check

```bash
lizard --json restart --service api --wait --timeout 120
```

Without `--wait`, JSON mode returns after the restart is accepted. With `--wait`, the result includes `ok`, `status`, `attemptId`, and `waitedMs`. The CLI waits for a new restart attempt, so an unchanged status from before the request does not count as success. A failed wait or timeout returns a nonzero exit code.

For HTTP services, the CLI also checks the service domain twice. Workers with `containerPort=0` use the readiness status from the backend and do not need an HTTP listener, even when they have a generated domain.

```bash
lizard --json restart --service worker --wait --timeout 60
```

The timeout applies to the readiness wait after the restart request. It does not cancel the restart, and network calls can make the total command take longer. Check application data or a health endpoint after the command when your workflow needs more than process readiness.

## See also

- [lizard redeploy](https://lizard.build/docs/cli/redeploy.md) — trigger a fresh build instead of recycling the current one
- [lizard logs](https://lizard.build/docs/cli/logs.md) — inspect logs around a restart with `--restart latest` or `--restart `
- [Deployments](https://lizard.build/docs/concepts/deployments.md) — restarts vs. redeploys, and recovering from a crash


---
title: "lizard service"
description: "lizard service reads and edits a service's build, start, source, and variable settings, and handles rename, delete, logs, and linking."
url: https://lizard.build/docs/cli/service/
updated: 2026-10-10
---

# lizard service

Manage an individual service's configuration, source, and lifecycle.

## Usage

```bash
lizard service set [service] [flags]
lizard service show [service] [flags]
lizard service rename  [flags]
lizard service delete [service] [flags]
lizard service logs [service] [flags]
lizard service link [name]
```

## Subcommands

### `lizard service set`

Apply build/start/source/variables/rename changes to a service.

| Flag | Description |
|------|-------------|
| `--set =` | Set a field (repeatable; needs the `service` argument) |
| `-f, --file ` | JSON config file to apply |
| `--force` | Overwrite even if changed remotely |

Common fields are flat and map 1:1 to the wire schema: `sourceType`, `repoUrl`, `branch`, `rootDirectory`, `dockerfilePath`, `buildCommand`, `startCommand`, `preDeployCommand`, `watchPatterns`, `containerPort`, `cpuLimit` (for example `2000m`), `memoryLimit` (for example `2Gi`), `name`. `variables.=` sets a service secret, the same as `lizard secrets set --service`.

`service set` uses optimistic concurrency via `configRevision`. On a `409`, re-read the config with `lizard service show`, reconcile, and retry — or pass `--force`.

### `lizard service show`

Show the current service configuration as JSON. Use `-s` to limit to one service.

### `lizard service rename `

Rename a service or addon: the linked one, or the one you pass with `-s`. References use the target's current name, so update every `${{old-name.KEY}}` to the new name after a rename.

### `lizard service delete`

Delete a service (alias `rm`). `-y, --yes` skips confirmation; without a TTY it is required.

### `lizard service logs`

Stream a service's logs (see also [lizard logs](https://lizard.build/docs/cli/logs.md)).

| Flag | Description |
|------|-------------|
| `--build` | Show the latest build's logs |
| `-n, --tail ` | Print the last N lines (up to 2000) and exit; `--tail all` prints 2000 |
| `--page ` | Page through older logs, 200 lines per page (1 = most recent) |

### `lizard service link`

Link a service to the current directory.

## Examples

### Update a service's build and start commands

```bash
lizard service set api --set branch=main --set buildCommand="npm run build"
```

### Inspect a service's full configuration

```bash
lizard service show -s api
```

### Delete a service without a confirmation prompt

```bash
lizard service delete -y
```

## See also

- [lizard port](https://lizard.build/docs/cli/port.md) — show or change a service's container port
- [lizard scale](https://lizard.build/docs/cli/scale.md) — scale a service's replicas, CPU, memory, or storage
- [Architecture](https://lizard.build/docs/concepts/architecture.md) — how services relate to projects and addons
- [Build pipeline](https://lizard.build/docs/concepts/build-pipeline.md) — how `buildCommand`, `startCommand`, and source settings are used at build time


---
title: "lizard port"
description: "lizard port shows or changes a service's container port. Setting it to 0 puts the service in worker mode with no inbound routing."
url: https://lizard.build/docs/cli/port/
updated: 2026-10-10
---

# lizard port

Show or change the container port for a service. No argument prints the current port (`worker mode` when it's `0`). A new port takes effect on the next deploy; run [`lizard redeploy`](https://lizard.build/docs/cli/redeploy.md) to apply it.

## Usage

```bash
lizard port [value] [flags]
```

## Flags

| Flag | Description |
|------|-------------|
| `-s, --service ` | Service to target |
| `-p, --project ` | Project name, slug, or ID |

## Examples

### Show the current port

```bash
lizard port
```

### Set the port for a specific service

```bash
lizard port 0 -s worker
```

### Check the port on a worker service

```bash
lizard port --service worker
```

## See also

- [Background workers](https://lizard.build/docs/deploy/workers.md) — set the port to `0` to run a service without inbound routing
- [lizard service](https://lizard.build/docs/cli/service.md) — manage the rest of a service's configuration
- [Python app build and start commands](https://lizard.build/blog/python-app-hosting#build-and-start-commands) — why a `gunicorn` or `uvicorn` process has to bind `$PORT` on `0.0.0.0`


---
title: "lizard scale"
description: "lizard scale changes a service's replicas, CPU, and memory, and grows addon storage. Allowed values and examples for every flag."
url: https://lizard.build/docs/cli/scale/
updated: 2026-10-10
---

# lizard scale

Scale a service (replicas / CPU / memory / storage).

## Usage

```bash
lizard scale [flags]
```

## Flags

| Flag | Description |
|------|-------------|
| `--replicas ` | Replicas (apps), up to your plan's [limit](https://lizard.build/docs/platform/limits.md#applications): `1` until the first paid Pro invoice (so during the trial), `5` after it and on Enterprise |
| `--cpu ` | `1`, `2`, `3`, or `4` |
| `--memory ` | `128`–`8192` MB |
| `--storage ` | Addon volume size, grow-only: `512`, `1024`, `2048`, `4096`, `8192`, or `16384` |

## Examples

### Scale replicas horizontally

```bash
lizard scale --service api --replicas 5
```

Each replica is its own pod behind the load balancer. Make sure your app is stateless so replicas are interchangeable.

### Scale CPU and memory vertically

```bash
lizard scale --service api --cpu 4 --memory 8192
```

### Grow addon storage

```bash
lizard scale --service postgres --storage 8192
```

Addon data volumes are grow-only — you can increase storage but not shrink it.

## See also

- [Scaling](https://lizard.build/docs/deploy/scaling.md) — horizontal vs. vertical scaling and growing addon storage
- [lizard metrics](https://lizard.build/docs/cli/metrics.md) — check CPU / memory / network / disk before and after scaling
- [lizard ps](https://lizard.build/docs/cli/ps.md) — check replica status after a scale change
- [Observability → Metrics](https://lizard.build/docs/observability/metrics.md) — resource and cost monitoring


---
title: "lizard secrets"
description: "lizard secrets sets, lists, deletes, and imports environment variables at service or project scope, including dotenv import from stdin."
url: https://lizard.build/docs/cli/secrets/
updated: 2026-10-10
---

# lizard secrets

Manage a project's or service's secrets (environment variables).

## Usage

```bash
lizard secrets  [args] [flags]
```

## Subcommands

### `lizard secrets set ...`

Set one or more secrets (variadic). Default scope is the service; `--global` targets the project.

| Flag | Description |
|------|-------------|
| `--global` | Project scope |
| `-s, --service ` | Service scope |

### `lizard secrets list`

List secrets in scope.

| Flag | Description |
|------|-------------|
| `--show` | Reveal values |
| `--ref` | List the `${{…}}` references available in this scope |

### `lizard secrets delete ...`

Delete one or more secrets (variadic, alias `rm`). It asks for confirmation; without a TTY, pass `-y`.

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |

### `lizard secrets import`

Import `KEY=value` lines from stdin. It skips blank lines and lines starting with `#`. Quotes stay part of the value; remove any `export ` prefixes first.

## Examples

### Set secrets on the linked service

```bash
lizard secrets set DATABASE_URL=postgres://… LOG_LEVEL=info
lizard secrets set API_KEY=sk_live_… --service api
```

### Set a project-wide secret

```bash
lizard secrets set NODE_ENV=production --global
```

### List and reveal secrets

```bash
lizard secrets list
lizard secrets list --show
```

### Delete secrets

```bash
lizard secrets delete OLD_KEY ANOTHER_KEY
lizard secrets delete OLD_KEY --yes   # in CI or a script
```

### Import a `.env` file

```bash
lizard secrets import < .env
```

## See also

- [Variables & secrets](https://lizard.build/docs/variables.md) — scoping, precedence, and build-time vs. runtime behavior
- [Cross-service references](https://lizard.build/docs/variables/references.md) — read one service's values from another
- [Troubleshooting: a secret or reference isn't showing up](https://lizard.build/docs/variables/troubleshooting/reference-not-applied.md)
- [lizard run](https://lizard.build/docs/cli/run.md) — run a command locally with project + service secrets injected


---
title: "lizard domain"
description: "lizard domain shows a service's domain, generates an onlizard.com subdomain, or attaches and verifies a custom hostname with automatic TLS."
url: https://lizard.build/docs/cli/domain/
updated: 2026-10-10
---

# lizard domain

Show the current domain, or attach a custom one.

## Usage

```bash
lizard domain [hostname] [flags]
```

The hostname is a positional argument — there is no `add` subcommand. Running `lizard domain` with no arguments shows the service's current domain, or generates one if it has none. Alias: `lizard domains`.

## Flags

| Flag | Description |
|------|-------------|
| `-s, --service ` | Service |
| `-p, --project ` | Project name, slug, or ID |
| `--port ` | Port to expose |
| `-f, --force` | Move the domain here if it's attached to another of your services |

The subcommands use the same `-s` and `-p` flags. Verify and delete a domain on the service you attached it to.

## Subcommands

### `lizard domain generate`

Generate a fresh `*.onlizard.com` subdomain.

### `lizard domain verify `

Check the TXT record and activate the domain. TLS is provisioned automatically.

### `lizard domain delete `

Remove a domain (alias `rm`).

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |

## Examples

### Show the current domain

```bash
lizard domain
```

### Generate a subdomain

```bash
lizard domain generate
```

### Attach a custom domain

```bash
lizard domain app.example.com --service web
```

Lizard returns the DNS records to create: a `CNAME` for the hostname and, unless you already verified the domain, a `TXT` record at `_lizard-verify.` (for `app.example.com`, that is `_lizard-verify.example.com`). Add them at your DNS provider, then verify on the same service:

```bash
lizard domain verify app.example.com --service web
```

### Attach a domain to a specific port

```bash
lizard domain app.example.com --service web --port 8080
```

### Move a domain from another of your services

```bash
lizard domain app.example.com --service web --force
```

### Remove a domain

```bash
lizard domain delete app.example.com --service web
lizard domain rm app.example.com --service web --yes
```

## See also

- [Networking](https://lizard.build/docs/networking.md) — generated domains, TLS, and worker services
- [Deploy to regions](https://lizard.build/docs/deploy/regions.md) — region selection for services


---
title: "lizard s3"
description: "lizard s3 uploads a local file to a Managed Object Storage bucket and lists the objects in a bucket, without an AWS SDK."
url: https://lizard.build/docs/cli/s3/
updated: 2026-10-10
---

# lizard s3

Upload and list objects in a [Managed Object Storage](https://lizard.build/docs/addons/storage.md) bucket.

## Usage

```bash
lizard s3 upload  [flags]
lizard s3 list [flags]
```

Both commands work on the project's only S3 addon and its `default` bucket unless you pass `--addon` and `--bucket`. Add the addon first with `lizard add s3`.

## Subcommands

### `lizard s3 upload `

Upload a local file. Prints the key, ETag, size, and a URL when the bucket is public. The `default` bucket is private; make a bucket public from the S3 browser in the dashboard.

| Flag | Description |
|------|-------------|
| `--addon ` | S3 addon name or ID (default: the project's only S3 addon) |
| `--bucket ` | Target bucket (default `default`) |
| `--key ` | Object key (default: the file's name) |
| `--content-type ` | Content-Type header (default: guessed from the file extension) |
| `-p, --project ` | Project name, slug, or ID |

### `lizard s3 list`

List objects in a bucket (alias `ls`).

| Flag | Description |
|------|-------------|
| `--addon ` | S3 addon name or ID |
| `--bucket ` | Bucket to list (default `default`) |
| `--prefix ` | Only keys under this prefix |
| `-p, --project ` | Project name, slug, or ID |

Each account can store up to 16 GiB across its addons. Storage is billed by stored bytes at $0.0135 per GB-month.

## Examples

### Upload a file

```bash
lizard s3 upload ./logo.png --key assets/logo.png
```

### List objects under a prefix

```bash
lizard s3 list --prefix assets/
```

### Use a second S3 addon

```bash
lizard s3 upload ./backup.tar.gz --addon s3-autumn-bear
```

## See also

- [Managed Object Storage](https://lizard.build/docs/addons/storage.md) — environment variables, AWS SDK setup, and public files
- [lizard add](https://lizard.build/docs/cli/add.md) — add an S3 addon with `lizard add s3`


---
title: "lizard logs"
description: "lizard logs streams a service's runtime logs, or fetches build logs, restart logs, and filtered history with --build, --tail, and --level."
url: https://lizard.build/docs/cli/logs/
updated: 2026-10-10
---

# lizard logs

Stream a service's runtime logs, the last 200 lines followed by a live tail. For runtime logs without `-s`, `logs` does not use the linked service: on a TTY it asks which service to show, otherwise it shows all services in the project. `--build` and `--restarts` use the linked service.

## Usage

```bash
lizard logs [flags]
```

## Flags

| Flag | Description |
|------|-------------|
| `--build` | Show build logs instead |
| `--tail ` | Print the last N lines (max 1000) and exit |
| `-l, --level ` | Runtime logs at this level only: `error`, `warn`, `info`, or `debug` |
| `--restarts [n]` | List the last N restarts (default 20) |
| `--restart ` | Logs around a specific restart (`latest` for the most recent) |
| `-s, --service ` | Service |
| `-p, --project ` | Project name, slug, or ID |

With `--json`, `lizard logs` is not a stream — it returns the last 200 lines (override with `--tail N`, max 1000) and exits. Use it for snapshots and scripting; stream live only without `--json`.

## Examples

### Tail runtime logs

```bash
lizard logs
```

### Tail logs for a specific service

```bash
lizard logs --service api
```

### Get more history

```bash
lizard logs --tail 1000
```

### Filter by log level

```bash
lizard logs --level error
```

### Check the most recent build

```bash
lizard logs --build
```

### Inspect a crash or restart

```bash
lizard logs --restarts
lizard logs --restart latest
lizard logs --restart 
```

## See also

- [Logs](https://lizard.build/docs/observability/logs.md) — runtime, build, and restart log behavior, plus paging through history with `lizard service logs`
- [Incomplete Dockerfile](https://lizard.build/docs/deploy/troubleshooting/incomplete-dockerfile.md) — diagnosing a failed build from `lizard logs --build`
- [lizard events](https://lizard.build/docs/cli/events.md) — deploy history and replica status
- [Deploy from Claude Code](https://lizard.build/blog/deploy-from-claude-code#check-the-result-from-the-outside) — the log commands an agent runs to check a deploy


---
title: "lizard metrics"
description: "lizard metrics reports CPU, memory, network, and disk for a service, with a live --watch view and optional cost figures."
url: https://lizard.build/docs/cli/metrics/
updated: 2026-10-10
---

# lizard metrics

Show CPU / memory / network / disk and cost.

## Usage

```bash
lizard metrics [flags]
```

## Flags

| Flag | Description |
|------|-------------|
| `-s, --service ` | Service (defaults to the linked service) |
| `-p, --project ` | Project name, slug, or ID |
| `--all` | All services in the project, ignoring the linked service; can't be combined with `-s` |
| `-r, --range ` | Time window: `1h` (default), `6h`, `24h`, `7d`, `14d`, or `30d` |
| `-w, --watch` | Live view, refreshed every 3 seconds; can't be combined with `--json` |
| `--cost` | Running resources, cost per hour, this billing period's usage, and Pro usage this month. Workspace owner only |

Without a linked service or `-s`, `metrics` shows an overview of every service in the project.

## Examples

### View metrics for the current service

```bash
lizard metrics
```

### View metrics for a specific service

```bash
lizard metrics --service api
```

### Watch metrics live

```bash
lizard metrics --watch
```

### Include cost

```bash
lizard metrics --cost
```

## See also

- [Metrics & cost](https://lizard.build/docs/observability/metrics.md) — dashboard views and acting on usage
- [Scaling](https://lizard.build/docs/deploy/scaling.md) — resize or add replicas in response to metrics


---
title: "lizard events"
description: "lizard events prints a service's deploy history and current replica status, with flags to limit entries or target one service."
url: https://lizard.build/docs/cli/events/
updated: 2026-08-09
---

# lizard events

Show deploy history and replica status.

## Usage

```bash
lizard events [flags]
```

## Flags

| Flag | Description |
|------|-------------|
| `-n, --limit ` | Number of entries to show (default 10) |
| `-s, --service ` | Show events for a specific service |

## Examples

### Show deploy history and replica status

```bash
lizard events
```

### Show events for a specific service

```bash
lizard events --service api
```

### Show more entries

```bash
lizard events --limit 25
```

## See also

- [Events & History](https://lizard.build/docs/observability/events.md) — what each entry covers and how it maps to the dashboard's Deployments view
- [lizard ps](https://lizard.build/docs/cli/ps.md) — a fast snapshot of all services' status and URL
- [Deployments](https://lizard.build/docs/concepts/deployments.md) — the deploy lifecycle


---
title: "lizard ps"
description: "lizard ps lists every service in the linked project with its status and live URL, a fast snapshot of what is running."
url: https://lizard.build/docs/cli/ps/
updated: 2026-08-09
---

# lizard ps

List all services in the project with status and URL.

## Usage

```bash
lizard ps
```

## Examples

### List services in the linked project

```bash
lizard ps
```

## See also

- [lizard status](https://lizard.build/docs/cli/status.md) — show the linked workspace, project, and service for the current directory
- [lizard events](https://lizard.build/docs/cli/events.md) — show deploy history and replica status


---
title: "lizard run"
description: "lizard run executes a command on your machine with a service's project and service secrets injected. What it injects, and how to check what the app sees."
url: https://lizard.build/docs/cli/run/
updated: 2026-10-12
---

# lizard run

Run a command locally with project and service secrets injected.

## What `run` injects

`lizard run` loads the project's secrets, then the service's secrets on top, and runs your command on your machine with them in its environment.

- For an app service, it injects the stored values. References such as `${{postgres.DATABASE_URL}}` arrive as that literal text, not the resolved value. Platform variables such as `PORT` and `LIZARD_*` are not injected.
- For an addon (`--service postgres`, `--service redis`, `--service s3`), it injects the addon's real connection values, such as `DATABASE_URL`.

To run a migration or database client locally, point `--service` at the addon, not at the app that references it.

Lizard disables `lizard ssh` into running services (the API answers 403 "Shell access to services is disabled"). To check what the running app gets, use:

| Check | Command |
|-------|---------|
| Empty references | `lizard logs --build --service api` (the build log warns about every `${{…}}` reference that resolved to an empty value) |
| Stored values | `lizard secrets list --service api --show` |
| An addon's real connection values | `lizard run --service postgres -- printenv DATABASE_URL` |

## Usage

```bash
lizard run [flags] -- 
```

## Flags

| Flag | Description |
|------|-------------|
| `-s, --service ` | Service whose env to inject (defaults to the linked service) |
| `--no-service` | Don't inject service env, project secrets only |
| `-p, --project ` | Project (defaults to the linked one) |

## Examples

### Run a migration against Managed Postgres

```bash
lizard run --service postgres -- node scripts/migrate.js
```

### Print an injected environment variable

```bash
lizard run --service postgres -- printenv DATABASE_URL
```

### Run a local tool with an app's stored secrets

```bash
lizard run --service api -- node scripts/report.js
```

## See also

- [lizard ssh](https://lizard.build/docs/cli/ssh.md): why shell access to services is off, and what to use instead
- [lizard secrets](https://lizard.build/docs/cli/secrets.md): list and set the values `run` injects
- [Variables](https://lizard.build/docs/variables.md): how project and service variables are defined and referenced
- [Python app hosting](https://lizard.build/blog/python-app-hosting): running `manage.py` and other framework tooling against a deployed database


---
title: "lizard ssh"
description: "lizard ssh into services is turned off: the API answers 403. How to check a service's values and references instead, and how to get a shell in a sandbox."
url: https://lizard.build/docs/cli/ssh/
updated: 2026-10-10
---

# lizard ssh

> **Turned off.** Shell access to services is disabled on Lizard. `lizard ssh` gets HTTP 403 `Shell access to services is disabled` and exits with code `1`.

The command still ships with the CLI, but the platform refuses it for every service. Use these instead:

| To check | Command |
|----------|---------|
| References that resolved to an empty value | `lizard logs --build --service api` (the build log warns about every empty `${{…}}` reference) |
| The values stored on a service | `lizard secrets list --service api --show` |
| A command with an addon's real connection values | `lizard run --service postgres -- ` |
| What the app prints at runtime | `lizard logs --service api` |

For a shell in a sandbox, use [`lizard sandbox ssh `](https://lizard.build/docs/cli/sandbox.md#sandbox-ssh). It still works.

## Usage

```bash
lizard ssh --service  -- 
```

| Flag | Description |
|------|-------------|
| `-s, --service ` | Service name or ID |
| `-p, --project ` | Project name, slug, or ID |

## See also

- [lizard run](https://lizard.build/docs/cli/run.md) — run a command locally with a service's or an addon's values injected
- [lizard secrets](https://lizard.build/docs/cli/secrets.md) — list the values stored on a service
- [lizard logs](https://lizard.build/docs/cli/logs.md) — build and runtime logs
- [lizard sandbox](https://lizard.build/docs/cli/sandbox.md) — interactive shells and commands in sandboxes


---
title: "lizard sandbox"
description: "lizard sandbox creates, lists, and deletes Sandboxes, runs commands and coding agents in them, opens shells and desktops, and manages snapshots and files."
url: https://lizard.build/docs/cli/sandbox/
updated: 2026-10-10
---

# lizard sandbox

Create and manage [Sandboxes](https://lizard.build/docs/sandboxes.md) from the terminal. Alias: `lizard sb`.

## Usage

```bash
lizard sandbox create [flags]
lizard sandbox list [flags]
lizard sandbox exec  -- 
lizard sandbox ssh 
lizard sandbox rm  [flags]
```

Every sandbox belongs to a project: the one linked to the current directory, or the one you pass with `-p`.

## Subcommands

### `lizard sandbox create`

Create a sandbox and print its ID.

| Flag | Description |
|------|-------------|
| `-t, --template ` | Template (default `base`). Enabled templates: `base`, `interpreter`, `codex`, `desktop`, `opencode`, `pi`, `prime`, `claude`. The server checks the region's list |
| `-s, --size ` | `small`, `medium` (default), or `large` |
| `--timeout ` | Lifetime in milliseconds (default `300000`, five minutes); `0` disables expiration |
| `--region ` | Region to create the sandbox in |
| `--snapshot ` | Start from a saved snapshot; can't be combined with `--size` |
| `--volume ` | Attach a [Persistent Volume](https://lizard.build/docs/cli/volume.md) |
| `--with-token [key]` | Install a `liz_` API key in the sandbox so `lizard` works there. Without a value it reuses the `liz_` key you run with; a browser login has none |
| `-p, --project ` | Project (defaults to the linked one) |

Lizard bills each size per second at a flat rate. The price includes disk and up to 2 TB of egress per account over a rolling 30 days. A paused sandbox costs nothing; an attached volume costs the [volume rate](https://lizard.build/docs/cli/volume.md).

| Size | vCPU | Memory | Disk | Price |
|------|------|--------|------|-------|
| `small` | 2 | 4 GiB | 5 GB | $0.0162/h |
| `medium` | 4 | 8 GiB | 20 GB | $0.0324/h |
| `large` | 8 | 16 GiB | 50 GB | $0.0648/h |

Anything in the sandbox can read a key installed with `--with-token`. Use a key scoped to one workspace or project: `lizard keys create sandbox-key -w my-team`.

Until your trial or Pro plan starts, `create` returns HTTP 402 with a Checkout link; run `lizard billing start`. During the trial you can run 5 sandboxes at once, and 100 after the first paid invoice. Paused sandboxes don't count. See [Limits](https://lizard.build/docs/platform/limits.md#sandbox-account-limits).

### `lizard sandbox list`

List sandboxes in the linked project (alias `ls`).

| Flag | Description |
|------|-------------|
| `-p, --project ` | List this project instead |
| `--all` | List every sandbox across all your workspaces |

### `lizard sandbox rm `

Delete a sandbox (alias `delete`).

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |

### `lizard sandbox timeout  `

Set a new lifetime, counted from now, in milliseconds (at least `1000`). It is not an idle timer: running commands does not reset it.

### `lizard sandbox exec  -- `

Run one command inside the sandbox through `sh -c`, stream its output, and exit with its exit code.

### `lizard sandbox ssh `

Open an interactive shell in a running sandbox (alias `shell`). It uses your Lizard login, so there are no SSH keys to set up; a scoped API key reaches only sandboxes in its scope. Ctrl-C, arrows, and tab completion go to the sandbox. Type `exit` or press Ctrl-D to leave.

It needs an interactive terminal; in a script, use `lizard sandbox exec`. It is not the SSH protocol, so SSH clients, `scp`, and port forwarding don't work.

### `lizard sandbox expose  `

Give a port in the sandbox an HTTPS URL. The port is private: requests need the sandbox's access token, either in the `X-Lizard-Access-Token` header or as the `lizard_token` query parameter that the printed URL already carries. Requests without it get HTTP 401. With `--json`, the result also has `hostname` and `accessToken`.

`lizard sandbox unexpose  ` removes the URL.

### `lizard sandbox desktop `

Start the graphical desktop of a sandbox created with `-t desktop` and print its browser URL and a view-only URL. Treat both links like passwords.

| Flag | Description |
|------|-------------|
| `--view-only` | Print or open only the view-only URL |
| `--resolution ` | Screen size, from 640x480 to 3840x2160 |
| `--open` | Open the desktop in your browser |
| `--status` | Show the desktop's state and URLs without starting it |
| `--stop` | Stop the desktop and remove its URL |

### `lizard sandbox vnc `

Connect a VNC app (macOS Screen Sharing, TigerVNC, RealVNC) to the desktop of a sandbox created with `-t desktop`. The desktop has no public VNC port, so the CLI listens on `localhost` and carries each connection to the sandbox over a secure WebSocket. It starts the desktop if needed, prints the address and the VNC password, and runs until Ctrl-C.

| Flag | Description |
|------|-------------|
| `--port ` | Local port (default `5900`; if it's taken, the next free one) |
| `--view-only` | Use the view-only password: watch, no control |
| `--open` | Open the address in your VNC app (`vnc://`, Screen Sharing on macOS) |

### Pause, resume, and snapshots

| Command | What it does |
|---------|--------------|
| `lizard sandbox pause ` | Save the sandbox's memory and processes, then stop it. A paused sandbox is free |
| `lizard sandbox resume ` | Start a paused sandbox again with its saved memory and files |
| `lizard sandbox snapshot  --name ` | Save a running sandbox as a private snapshot in its project and keep warm copies ready (`--warm`, 1 to 10, default 5) |
| `lizard sandbox restore ` | Start a new sandbox from a snapshot (`--timeout `, default `300000`) |
| `lizard sandbox snapshots` | List the project's snapshots (`-p` for another project) |
| `lizard sandbox snapshot-pause ` | Release a snapshot's warm copies and keep its saved state |
| `lizard sandbox snapshot-resume ` | Refill a paused snapshot's warm copies |
| `lizard sandbox snapshot-warm  ` | Change how many copies stay warm (1 to 10) |
| `lizard sandbox snapshot-rm ` | Delete a snapshot (alias `snapshot-delete`, `-y` skips confirmation) |

`pause`, `resume`, and `snapshot` wait until the work is done; pass `--no-wait` to return once it is queued. See [Pause, resume, and snapshots](https://lizard.build/docs/sandboxes/quickstart.md#pause-and-resume) for what each step keeps.

### `lizard sandbox files`

| Command | What it does |
|---------|--------------|
| `lizard sandbox files ls  [path]` | List a directory (default `/`) |
| `lizard sandbox files cat  ` | Print a file |
| `lizard sandbox files put   ` | Upload a local file |
| `lizard sandbox files get   ` | Download a file |
| `lizard sandbox files rm  ` | Delete a file or directory |

### Coding agents

Run a coding agent inside a sandbox on your own credentials. Connect them first with [`lizard agents`](https://lizard.build/docs/cli/agents.md). Create the sandbox from an agent template (`codex`, `claude`, `opencode`, `pi`, or `prime`); the `base` template has no Node.js and can't run agents.

`lizard sandbox prompt  ` sends a prompt and streams what the agent does.

| Flag | Description |
|------|-------------|
| `--agent ` | `codex` (default), `claude`, `pi`, `opencode`, or `prime` |
| `-c, --conversation ` | Continue this conversation |
| `--new` | Start a new conversation |
| `-m, --model ` | Model to use |
| `--effort ` | Reasoning effort: `minimal`, `low`, `medium`, `high`, or `xhigh` |
| `--cwd ` | Working directory in the sandbox (default `/workspace`) |
| `-v, --verbose` | Also print command output |
| `--no-wait` | Return once the prompt is accepted |

Ctrl-C stops watching; the agent keeps working. `lizard sandbox interrupt  [-c ]` stops its running turn. `lizard sandbox conversations ` lists the conversations, newest first.

## Examples

### Create a sandbox and run a command

```bash
lizard sandbox create --size small --timeout 600000
lizard sandbox exec sb_abc123 -- python3 -c "print(1 + 1)"
```

### Open a shell

```bash
lizard sandbox ssh sb_abc123
```

### Attach a Persistent Volume

```bash
lizard volume create agent-workdir --size 10
lizard sandbox create --volume agent-workdir
```

### Serve a port

```bash
lizard sandbox exec sb_abc123 -- sh -c "nohup python3 -m http.server 3000 --bind 0.0.0.0 >/tmp/http.log 2>&1 &"
lizard sandbox expose sb_abc123 3000
```

### Use a desktop from a VNC app

```bash
lizard sandbox create -t desktop
lizard sandbox vnc sb_abc123 --open
```

### Run Codex in a sandbox

```bash
lizard agents login chatgpt
lizard sandbox create -t codex
lizard sandbox prompt sb_abc123 "add a health check endpoint and test it"
```

### Save and restore a sandbox

```bash
lizard sandbox snapshot sb_abc123 --name my-app
lizard sandbox restore    # ID from `lizard sandbox snapshots`
```

## See also

- [Sandboxes](https://lizard.build/docs/sandboxes.md) — runtime, templates, and lifecycle
- [Sandboxes quickstart](https://lizard.build/docs/sandboxes/quickstart.md) — the same steps from the Lizard SDK
- [lizard volume](https://lizard.build/docs/cli/volume.md) — Persistent Volumes to attach with `--volume`
- [lizard agents](https://lizard.build/docs/cli/agents.md) — credentials for `sandbox prompt`
- [Limits](https://lizard.build/docs/platform/limits.md#sandbox-lifetime) — sizes, running limits, egress, and lifetime


---
title: "lizard volume"
description: "lizard volume creates, lists, resizes, and deletes Persistent Volumes that sandboxes attach by name."
url: https://lizard.build/docs/cli/volume/
updated: 2026-10-10
---

# lizard volume

Manage [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md) for sandboxes. Alias: `lizard vol`.

## Usage

```bash
lizard volume list [flags]
lizard volume create  [flags]
lizard volume resize  --size  [flags]
lizard volume rm  [flags]
```

A volume's name is its key within the project. Every command takes a name or an ID, and `-p, --project` to target a project other than the linked one.

## Subcommands

### `lizard volume list`

List the project's volumes with size, status, and the sandbox they're attached to (alias `ls`). `--json` also includes the generated IDs.

### `lizard volume create `

Create a volume. Names use lowercase letters, digits, and dashes, start and end with a letter or digit, are up to 64 characters, and are unique in the project.

| Flag | Description |
|------|-------------|
| `--size ` | Size in whole GB, 1 to 50 (default 5). The CLI reads the server's current limits first |
| `--region ` | Region for the volume; it must match the sandbox that will attach it |
| `-p, --project ` | Project name, slug, or ID |

### `lizard volume resize  --size `

Change a volume's size in place. An attached sandbox keeps running and sees the new size. A Firecracker volume (the default) can only grow. A pod-backed volume can also shrink if at least 10% of the new size stays free.

### `lizard volume rm `

Delete a volume and its data (alias `delete`). It fails while the volume is attached; delete the sandbox first.

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip confirmation |

Volume storage costs $0.14 per GB-month of used space. See [Size and price](https://lizard.build/docs/sandboxes/volumes.md#size-and-price).

## Examples

### Create a volume and attach it

```bash
lizard volume create agent-workdir --size 10
lizard sandbox create --volume agent-workdir
```

### Grow a volume

```bash
lizard volume resize agent-workdir --size 20
```

### Delete a volume

```bash
lizard volume rm agent-workdir --yes
```

## See also

- [Persistent Volumes](https://lizard.build/docs/sandboxes/volumes.md) — lifecycle, SDK methods, and the dashboard view
- [lizard sandbox](https://lizard.build/docs/cli/sandbox.md) — attach a volume with `sandbox create --volume`
- [Storage and recovery](https://lizard.build/docs/platform/storage-and-recovery.md) — what happens to volume data when a node fails


---
title: "lizard agents"
description: "lizard agents connects your ChatGPT, Claude, or API-key credentials for coding agents that run in sandboxes, and sets each agent's default model."
url: https://lizard.build/docs/cli/agents/
updated: 2026-10-12
---

# lizard agents

Connect the credentials that coding agents use in sandboxes, and set each agent's defaults. Run the agents with [`lizard sandbox prompt`](https://lizard.build/docs/cli/sandbox.md#coding-agents).

## Usage

```bash
lizard agents status
lizard agents login [chatgpt] [flags]
lizard agents add  [flags]
lizard agents remove 
lizard agents use  [flags]
```

Credentials belong to your account. Connect a credential once to use it for agent runs in any sandbox. For a ChatGPT login, the platform keeps the long-lived token and hands a sandbox a short-lived access token for each run. Lizard passes API keys and the stored Claude subscription token unchanged in each run's environment. Use credentials you can revoke. These commands accept `-p, --project` but ignore it.

## Agents and credentials

| Agent | Name | Accepts |
|-------|------|---------|
| Codex | `codex` | one of: `chatgpt`, `openai`, `deepseek`, `openrouter` |
| Claude Code | `claude` | one of: `claude`, `anthropic`, `openrouter` |
| pi | `pi` | any of: `claude`, `chatgpt`, `anthropic`, `openai`, `openrouter`, `llmgateway` |
| OpenCode | `opencode` | any of: `chatgpt`, `anthropic`, `openai`, `openrouter`, `llmgateway` |
| Prime Agent | `prime` | any of: `claude`, `chatgpt`, `anthropic`, `openai`, `openrouter`, `llmgateway` |

Kimi Code (`kimi`, with `kimi` or `moonshot` credentials) is listed but can't run yet.

## Subcommands

### `lizard agents status`

Show which credentials are connected and which agents can run. Plain `lizard agents` does the same.

### `lizard agents login [chatgpt]`

Connect your ChatGPT subscription with a device code. The CLI prints a URL and a code; open the URL, sign in, and enter the code within 15 minutes. The command waits until you approve.

| Flag | Description |
|------|-------------|
| `--open` | Open the sign-in page in your browser |

### `lizard agents add `

Add an API key: `anthropic`, `openai`, `openrouter`, `llmgateway`, `deepseek`, or `moonshot`. For a Claude subscription, run `claude setup-token` on your machine and add its token as `claude`.

| Flag | Description |
|------|-------------|
| `--key ` | The key or token. Without it, the CLI reads stdin, or asks |

### `lizard agents remove `

Remove a connected credential (alias `logout`).

### `lizard agents use `

Set an agent's credential and defaults.

| Flag | Description |
|------|-------------|
| `--credential ` | For Codex and Claude Code: which credential to use |
| `-m, --model ` | Default model (`none` clears it) |
| `--effort ` | Default reasoning effort (`none` clears it) |

## Examples

### Connect ChatGPT and run Codex

```bash
lizard agents login chatgpt --open
lizard sandbox create -t codex
lizard sandbox prompt sb_abc123 "add a health check endpoint and test it"
```

### Add a Claude subscription for Claude Code

```bash
claude setup-token
lizard agents add claude
lizard sandbox create -t claude
lizard sandbox prompt sb_abc123 --agent claude "fix the failing tests"
```

### Run Codex on an OpenAI API key

```bash
lizard agents add openai < openai.key
lizard agents use codex --credential openai --effort high
```

## See also

- [lizard sandbox](https://lizard.build/docs/cli/sandbox.md#coding-agents): `sandbox prompt`, `conversations`, and `interrupt`
- [Sandboxes](https://lizard.build/docs/sandboxes.md): templates and lifecycle


---
title: "lizard git"
description: "lizard git connects the GitHub App, reports repository status, and switches a service to another branch with a redeploy."
url: https://lizard.build/docs/cli/git/
updated: 2026-08-09
---

# lizard git

Manage the GitHub connection for the linked project and its services.

## Usage

```bash
lizard git connect
lizard git status
lizard git checkout   [flags]
```

## Subcommands

### `lizard git connect`

Connect the GitHub App to access private repositories.

### `lizard git status`

Show GitHub connection and repository status.

### `lizard git checkout  `

Switch a service to a different branch and redeploy.

| Flag | Description |
|------|-------------|
| `--detach` | Skip log streaming |

## Examples

### Connect the GitHub App

```bash
lizard git connect
```

### Check connection and repository status

```bash
lizard git status
```

### Switch a service to a different branch

```bash
lizard git checkout api staging
```

### Switch branches without streaming logs

```bash
lizard git checkout api staging --detach
```

## See also

- [Deploy from GitHub](https://lizard.build/docs/deploy/github.md) — connecting a repo, auto-redeploy on push, and monorepo setup
- [lizard add](https://lizard.build/docs/cli/add.md) — create a service from a GitHub repo


---
title: "lizard regions"
description: "lizard regions lists the regions available to your account, and shows how to pass --region when creating a service or addon."
url: https://lizard.build/docs/cli/regions/
updated: 2026-10-10
---

# lizard regions

List available regions.

## Usage

```bash
lizard regions [flags]
```

## Flags

| Flag | Description |
|------|-------------|
| `--json` | Output as JSON |

## Examples

### List available regions

```bash
lizard regions
```

### List regions as JSON

```bash
lizard regions --json
```

### Choose a region when you create a resource

Pass `--region ` to `lizard add`, `lizard up`, `lizard sandbox create`, or `lizard volume create`:

```bash
lizard add -r your-org/your-app --region 
lizard add postgres --region 
lizard up --region 
```

Without `--region`, `lizard add` uses `us-east-1` and `lizard up` uses the platform's default region. On `lizard up`, `--region` only applies when it creates a new service.

## See also

- [Regions](https://lizard.build/docs/deploy/regions.md) — region selection, co-location, and generated domains
- [`lizard add`](https://lizard.build/docs/cli/add.md) — add a service, repo, or addon with `--region`


---
title: "lizard open"
description: "lizard open launches the current project's dashboard in your browser, the visual companion to the CLI."
url: https://lizard.build/docs/cli/open/
updated: 2026-10-10
---

# lizard open

Open the project in the browser.

## Usage

```bash
lizard open
```

## Examples

### Open the current project's dashboard

```bash
lizard open
```

## See also

- [Dashboard](https://lizard.build/docs/dashboard.md) — the visual companion to the CLI that this command opens
- [Deploy an Antigravity app to production](https://lizard.build/blog/google-antigravity-how-to-deploy-your-app-to-production#deploy-from-the-antigravity-chat) — watching an agent-run deploy land, without typing a command yourself


---
title: "lizard docs"
description: "lizard docs opens the Lizard documentation in your default browser straight from the terminal. Usage and examples."
url: https://lizard.build/docs/cli/docs/
updated: 2026-08-09
---

# lizard docs

Open the documentation in the browser.

## Usage

```bash
lizard docs
```

## Examples

### Open the documentation site

```bash
lizard docs
```

## See also

- [CLI reference](https://lizard.build/docs/cli.md) — installation, global flags, and exit codes


---
title: "lizard upgrade"
description: "lizard upgrade replaces the CLI binary with the latest release on macOS and Linux, or checks for a newer version with --check."
url: https://lizard.build/docs/cli/upgrade/
updated: 2026-10-10
---

# lizard upgrade

Upgrade the CLI to the latest version.

## Usage

```bash
lizard upgrade [flags]
```

How it upgrades depends on how you installed the CLI:

- **Install script on macOS or Linux:** downloads the latest release and replaces the binary.
- **npm:** prints the command to run, `npm install -g @lizard-build/cli@latest`.
- **Windows:** the binary can't replace itself. Run the installer again: `irm https://lizard.build/api/cli/install.ps1 | iex`.

## Flags

| Flag | Description |
|------|-------------|
| `--check` | Check for a newer version without installing |

## Examples

### Update to the latest version

```bash
lizard upgrade
```

### Check for updates without installing

```bash
lizard upgrade --check
```

## See also

- [CLI reference](https://lizard.build/docs/cli.md) — install, global flags, exit codes
- [Getting started](https://lizard.build/docs/getting-started.md) — installing the CLI for the first time


---
title: "lizard skills"
description: "lizard skills reads the agent guides bundled with your CLI version, so an AI agent always gets instructions that match the binary."
url: https://lizard.build/docs/cli/skills/
updated: 2026-10-10
---

# lizard skills

Read the embedded agent skills shipped with this CLI version. Alias: `lizard skill`.

## Usage

```bash
lizard skills list
lizard skills get core
lizard skills path
```

## Subcommands

### `lizard skills list`

List the available embedded skills.

### `lizard skills get core`

Read the core guide — the authoritative usage guide for the platform, versioned with the CLI. With `--json` it returns `{ name, path, frontmatter, content }`, where `content` is the complete guide (build pipeline, env precedence, addons, discovery, exit codes).

### `lizard skills path [name]`

Print where a skill comes from. Skills are built into the CLI, so this prints `embedded://skills` unless `LIZARD_SKILLS_DIR` points the CLI at a directory on disk.

## Examples

### Load the core guide as an agent

```bash
lizard skills get core --json
```

### List available embedded skills

```bash
lizard skills list
```

## See also

- [AI agents & MCP](https://lizard.build/docs/agents.md) — how agents bootstrap with the embedded skill and self-describing commands
- [JSON & automation](https://lizard.build/docs/cli/json.md) — the `--json` output format used by `lizard skills get core --json`
- [Deploy from Claude Code](https://lizard.build/blog/deploy-from-claude-code) — installing the skill and handing the deploy to the agent, end to end