<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yura Oak</title>
    <description>The latest articles on DEV Community by Yura Oak (@yuraoak).</description>
    <link>https://dev.to/yuraoak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4073661%2F3acf3a81-d1c5-4a50-bfd0-70062f090746.png</url>
      <title>DEV Community: Yura Oak</title>
      <link>https://dev.to/yuraoak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuraoak"/>
    <language>en</language>
    <item>
      <title>Claude Code --dangerously-skip-permissions: usage and limits</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Wed, 07 Oct 2026 13:13:40 +0000</pubDate>
      <link>https://dev.to/yuraoak/claude-code-dangerously-skip-permissions-usage-and-limits-4hek</link>
      <guid>https://dev.to/yuraoak/claude-code-dangerously-skip-permissions-usage-and-limits-4hek</guid>
      <description>&lt;p&gt;&lt;code&gt;claude --dangerously-skip-permissions&lt;/code&gt; starts Claude Code in &lt;code&gt;bypassPermissions&lt;/code&gt; mode. It removes routine approval prompts for file changes, shell commands and tool calls. Use it for a bounded task inside a disposable environment whose files, credentials and network access you have already limited. The flag itself creates no sandbox.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the command. Before adding it to an unattended job, check what the process can reach. A missed approval on a throwaway checkout has a different cost from a missed approval on a laptop that holds production credentials.&lt;/p&gt;

&lt;p&gt;This guide covers the command, alternatives and a concrete run-and-review workflow. We checked Anthropic's documentation on October 6, 2026. The examples describe a setup; they are not results from a live Claude Code security test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does dangerously-skip-permissions do?
&lt;/h2&gt;

&lt;p&gt;The flag selects the same permission mode as &lt;code&gt;claude --permission-mode bypassPermissions&lt;/code&gt;. Claude can continue working without the usual approval stops. It keeps the operating-system identity and access of the process you launched: it does not grant root privileges, invent credentials or create access to a network your environment blocks. &lt;a href="https://code.claude.com/docs/en/permission-modes" rel="noopener noreferrer"&gt;Claude Code permission modes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are still exceptions. Current releases retain explicit deny rules and ask rules, checks for certain critical-path removals, and actions that require user interaction. Do not treat those exceptions as protection for arbitrary files or cloud resources. A command can harm a project without deleting a filesystem root.&lt;/p&gt;

&lt;p&gt;In particular, &lt;strong&gt;bypass mode can write to &lt;code&gt;.git&lt;/code&gt; and &lt;code&gt;.claude&lt;/code&gt; without the protected-path prompt&lt;/strong&gt;. Some search summaries suggest that these folders always stay protected. That is not what the current documentation says. &lt;a href="https://code.claude.com/docs/en/permissions" rel="noopener noreferrer"&gt;Permissions reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Consider a routine request: “Fix the failing tests.” Claude might edit a test, install a package or change configuration. If the same process can also read a cloud key and call its API, a disposable local folder alone does not restrict those remote actions. Your setup must limit that access before the run begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands for interactive and unattended use
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Start an interactive session
&lt;/h3&gt;

&lt;p&gt;Open a terminal inside your prepared environment and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the displayed permission mode before giving the task. On the first interactive launch, Claude Code can show a warning that you must accept. For a root or sudo error, use a non-root account inside the environment instead of changing the host's protections. &lt;a href="https://code.claude.com/docs/en/permission-modes" rel="noopener noreferrer"&gt;Mode setup and root restrictions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run one task and capture its result
&lt;/h3&gt;

&lt;p&gt;For a current Claude Code release with API-key authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--bare&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Fix the failing unit test in this checkout. Run the relevant tests. Report changed files and failures. Do not commit or push."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-turns&lt;/span&gt; 12 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-format&lt;/span&gt; json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/claude-result.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;-p&lt;/code&gt; runs a non-interactive task, and &lt;code&gt;--output-format json&lt;/code&gt; makes its response easier for a runner to collect. &lt;code&gt;--max-turns&lt;/code&gt; limits the agent loop; it does not impose a wall-clock deadline on the whole job. Configure that deadline in your runner too. &lt;a href="https://code.claude.com/docs/en/cli-reference" rel="noopener noreferrer"&gt;CLI flag reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--bare&lt;/code&gt; avoids loading the usual ambient hooks, plugins, memory and repository instructions. For Anthropic's API it needs &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;; it does not use your subscription's OAuth login. Supply that key through the runner's secret settings. On older releases, check support before copying this example. &lt;a href="https://code.claude.com/docs/en/headless" rel="noopener noreferrer"&gt;Programmatic runs and bare mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The prompt's “Do not commit or push” describes the task. Enforce the restriction by withholding write access to the remote repository. A sentence in a prompt cannot replace a credential scope or network policy.&lt;/p&gt;

&lt;p&gt;For more on collecting results and handling failed jobs, see our &lt;a href="https://lizard.build/blog/claude-code-headless" rel="noopener noreferrer"&gt;Claude Code headless guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable bypass for later in the session
&lt;/h3&gt;

&lt;p&gt;The similarly named flag has a different effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; default &lt;span class="nt"&gt;--allow-dangerously-skip-permissions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--allow-dangerously-skip-permissions&lt;/code&gt; makes bypass selectable later; it does not start in bypass. Use the terminal's &lt;code&gt;Shift+Tab&lt;/code&gt; mode cycle to switch. If the session did not enable bypass at launch, restart with the enabling flag. &lt;a href="https://code.claude.com/docs/en/cli-reference" rel="noopener noreferrer"&gt;CLI reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto mode vs dangerously-skip-permissions
&lt;/h2&gt;

&lt;p&gt;Choose the least permissive mode that lets the task finish:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Approval behavior&lt;/th&gt;
&lt;th&gt;A practical use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manual approval for actions that need permission&lt;/td&gt;
&lt;td&gt;Work involving sensitive files or shared systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;acceptEdits&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Approves file edits and common filesystem operations&lt;/td&gt;
&lt;td&gt;Local coding with review of other actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A classifier reviews actions beyond routine approvals&lt;/td&gt;
&lt;td&gt;Longer coding tasks with fewer interruptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dontAsk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Denies actions that would need a prompt&lt;/td&gt;
&lt;td&gt;A job with a narrow set of allowed tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bypassPermissions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Skips routine checks, with documented exceptions&lt;/td&gt;
&lt;td&gt;A bounded job in an isolated environment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mode names and behavior come from the &lt;a href="https://code.claude.com/docs/en/permissions" rel="noopener noreferrer"&gt;permissions reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Auto mode uses a separate classifier to review actions such as shell commands and external tool calls. It can block an action and let Claude try another approach. It can also make mistakes. Anthropic describes it as a way to reduce approval overhead, with tradeoffs that manual review and bypass do not share. &lt;a href="https://www.anthropic.com/engineering/claude-code-auto-mode" rel="noopener noreferrer"&gt;How Anthropic built auto mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If repetitive prompts are your main problem, start by trying auto mode where your account supports it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your job only needs known tools, consider &lt;code&gt;dontAsk&lt;/code&gt; with narrow allow rules. It has a useful property for automation: missing permission fails the action instead of leaving the job waiting for a person. Inspect denied actions before widening the rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Claude Code's sandbox make bypass safe?
&lt;/h2&gt;

&lt;p&gt;Claude Code's built-in Bash sandbox restricts shell access to files and the network. Its auto-allow option can approve sandboxed commands without a prompt. That option is separate from auto mode's classifier. &lt;a href="https://code.claude.com/docs/en/sandboxing" rel="noopener noreferrer"&gt;Bash sandbox configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The built-in Bash sandbox does not contain the whole application. File tools, MCP servers and hooks need their own boundary, or an outer environment that contains the entire Claude Code process. A VM and a container also provide different isolation. Choose based on what code you intend to run and what failure you can tolerate. &lt;a href="https://code.claude.com/docs/en/sandbox-environments" rel="noopener noreferrer"&gt;Sandbox environment comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check the retry behavior too. With bypass enabled, an unsandboxed retry can run without asking. For supported setups, the following sandbox settings prevent that retry and require the sandbox to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sandbox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"failIfUnavailable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowUnsandboxedCommands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review any &lt;code&gt;excludedCommands&lt;/code&gt; entries as well; they can still run outside the Bash sandbox. For centrally enforced settings, use managed policy that the agent cannot edit. &lt;a href="https://code.claude.com/docs/en/sandboxing" rel="noopener noreferrer"&gt;Strict sandbox settings&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical workflow for an unattended run
&lt;/h2&gt;

&lt;p&gt;Take a small failing-test task as an example. The output you want is a patch and a test report. There is no reason for this job to hold deployment access.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prepare a separate checkout
&lt;/h3&gt;

&lt;p&gt;Create a fresh container or VM and copy in the repository at a known commit. Prefer a copy to a writable mount of your main working directory. A worktree helps separate changes, but does not isolate the operating system or credentials.&lt;/p&gt;

&lt;p&gt;Install the required runtime and packages during setup. Record the starting commit and tool versions. Use a non-root account for the agent. If you start with a dev container, inspect its mounts: a writable host directory exposes those host files to edits inside the container. Anthropic's &lt;a href="https://code.claude.com/docs/en/devcontainer" rel="noopener noreferrer"&gt;dev container guide&lt;/a&gt; explains the setup and its limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Give the job only the access it needs
&lt;/h3&gt;

&lt;p&gt;Pass the model credential separately. If the repository is private, use read-only repository access for checkout. Keep deployment keys, your SSH agent, the Docker socket and unrelated home-directory files outside the environment.&lt;/p&gt;

&lt;p&gt;Allow the model endpoint and the package or source hosts the task needs. Restrict other outbound access in the environment's network controls. If a dependency download fails, identify the required host before opening more destinations.&lt;/p&gt;

&lt;p&gt;This is also where you decide which MCP servers, if any, the task needs. A database or cloud MCP connection adds remote capabilities even when the local filesystem is disposable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Run a task with a clear end condition
&lt;/h3&gt;

&lt;p&gt;Use the headless command above once authentication and dependencies are ready. Put the job under a runner timeout and capture its exit status, stdout and stderr. Keep the test command under your control so that the result does not depend only on the agent's summary.&lt;/p&gt;

&lt;p&gt;Start with a small change: one failing test or one documented bug. “Clean up the entire repository” leaves too much room for edits you did not intend to review.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Review the actual files
&lt;/h3&gt;

&lt;p&gt;After the job ends, inspect the checkout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status &lt;span class="nt"&gt;--short&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
git diff &lt;span class="nt"&gt;--check&lt;/span&gt;
git diff
git ls-files &lt;span class="nt"&gt;--others&lt;/span&gt; &lt;span class="nt"&gt;--exclude-standard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last command lists untracked files, which a plain diff misses. Inspect them too. Compare the final commit with the starting commit so an unexpected commit does not hide changes from the working-tree diff. Re-run the relevant tests from your trusted runner, then review the patch before merging it.&lt;/p&gt;

&lt;p&gt;Export the files you need before stopping the environment. Keep credentials out of logs and artifacts. Expire the task's credentials and destroy the disposable workspace after the review material is safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the task remotely
&lt;/h2&gt;

&lt;p&gt;Lizard (lizard.build) offers &lt;a href="https://lizard.build/sandboxes" rel="noopener noreferrer"&gt;Lizard Sandboxes&lt;/a&gt;, which can provide the remote workspace for this pattern: create an environment, execute a task, collect its files and stop it. Set an explicit lifetime so a failed runner does not leave the environment running indefinitely. Start with the &lt;a href="https://lizard.build/docs/sandboxes/quickstart/" rel="noopener noreferrer"&gt;Sandboxes quickstart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check the runtime before choosing it for your threat model. Lizard's documented default uses Kubernetes pods with runc and shares the host kernel. Firecracker support has a limited rollout for selected accounts and templates; do not assume that every sandbox uses a microVM. Use the runtime your account actually provides and keep credentials scoped accordingly. See the &lt;a href="https://lizard.build/docs/sandboxes/" rel="noopener noreferrer"&gt;runtime documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Remote execution helps when you need a fresh workspace for each job or many jobs at once. It still needs a network policy and a review step. A sandbox with a production database token can reach that database regardless of where the sandbox runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I use dangerously-skip-permissions by default?
&lt;/h3&gt;

&lt;p&gt;Yes. For a dedicated container or VM worker, merge this into that worker user's &lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"defaultMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bypassPermissions"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;New terminal sessions then start in bypass mode. Keep this default confined to the disposable worker; for individual jobs, use the explicit launch flag. In current releases, this value does not enable bypass from a repository's &lt;code&gt;.claude/settings.json&lt;/code&gt; or &lt;code&gt;.claude/settings.local.json&lt;/code&gt;. Managed policy may also disable the mode. &lt;a href="https://code.claude.com/docs/en/settings" rel="noopener noreferrer"&gt;Settings scope and precedence&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do subagents inherit bypass permissions?
&lt;/h3&gt;

&lt;p&gt;Yes. In the current documented behavior, a main conversation in bypass mode makes its subagents use the same mode, overriding their own permission-mode setting. A subagent is not a separate security boundary. &lt;a href="https://code.claude.com/docs/en/sub-agents" rel="noopener noreferrer"&gt;Subagent permissions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Claude still ask a question?
&lt;/h3&gt;

&lt;p&gt;An approval prompt and a question about the task are different. Some actions require interaction even in bypass mode, and configured ask rules can still apply. Read the actual prompt before assuming the flag failed. For unattended jobs, specify the expected output and failure behavior up front.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the flag make Claude Code free or remove rate limits?
&lt;/h3&gt;

&lt;p&gt;No. It changes permission handling. Your model provider still controls authentication, usage charges and rate limits. Repeated retries can use more tokens even when no approval prompt appears.&lt;/p&gt;

&lt;p&gt;For daily work, try auto mode or the Bash sandbox's auto-allow option first. For unattended bypass runs, make the environment disposable, restrict what it can reach, and review the resulting files before they affect your main project.&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://lizard.build/blog/dangerously-skip-permissions" rel="noopener noreferrer"&gt;Lizard&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Vercel Sandbox pricing: costs, limits, and alternatives</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Tue, 06 Oct 2026 08:17:45 +0000</pubDate>
      <link>https://dev.to/yuraoak/vercel-sandbox-pricing-costs-limits-and-alternatives-58je</link>
      <guid>https://dev.to/yuraoak/vercel-sandbox-pricing-costs-limits-and-alternatives-58je</guid>
      <description>&lt;p&gt;Vercel Sandbox pricing depends on how much CPU your code uses and how long you keep memory allocated. In the default US region, Vercel charges $0.128 per active vCPU-hour and $0.0212 per GB-hour of memory. While your agent waits for an AI model, it can stop using CPU, but you still pay for its memory. See &lt;a href="https://vercel.com/docs/sandbox/pricing" rel="noopener noreferrer"&gt;Vercel's pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lizard (lizard.build) has the lowest paid compute price of the four providers we compare here. For 10,000 five-minute runs with 2 vCPUs and 4 GB of memory, we calculate &lt;strong&gt;$7.50 on Lizard versus $92.01 on Vercel&lt;/strong&gt;, assuming 10% CPU utilization. That's 91.8% less before credits, subscriptions and any extra storage or transfer charges.&lt;/p&gt;

&lt;p&gt;We checked prices on October 5, 2026. This is a Lizard guide, so we're comparing our own product with other providers. We used their official prices and checked Lizard's rate against its billing data. The calculations assume the same stated CPU and memory sizes; actual throughput and reliability need separate tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vercel Sandbox pricing by resource
&lt;/h2&gt;

&lt;p&gt;The Pro rates below apply to &lt;code&gt;iad1&lt;/code&gt;. If you plan to run elsewhere, check the &lt;a href="https://vercel.com/docs/sandbox/concepts/regions" rel="noopener noreferrer"&gt;regional prices&lt;/a&gt;: CPU, memory and some other charges change with location.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Charge&lt;/th&gt;
&lt;th&gt;Published rate&lt;/th&gt;
&lt;th&gt;What it means for your estimate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Active CPU&lt;/td&gt;
&lt;td&gt;$0.128 per vCPU-hour&lt;/td&gt;
&lt;td&gt;Count CPU execution across all allocated vCPUs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provisioned memory&lt;/td&gt;
&lt;td&gt;$0.0212 per GB-hour&lt;/td&gt;
&lt;td&gt;Count allocated memory throughout the running session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox creation&lt;/td&gt;
&lt;td&gt;$0.60 per million&lt;/td&gt;
&lt;td&gt;Add the number of creation calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshot storage&lt;/td&gt;
&lt;td&gt;$0.08 per GB-month&lt;/td&gt;
&lt;td&gt;Add retained filesystem snapshots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drive storage&lt;/td&gt;
&lt;td&gt;$0.05 per GB-month&lt;/td&gt;
&lt;td&gt;Add persistent drive data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drive reads / writes&lt;/td&gt;
&lt;td&gt;$0.0015 / $0.004 per GB&lt;/td&gt;
&lt;td&gt;Add data read from or written to drives&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: &lt;a href="https://vercel.com/docs/sandbox/pricing" rel="noopener noreferrer"&gt;Vercel Sandbox rates and quotas&lt;/a&gt;. Vercel bills memory in minimum increments of one minute. That doesn't affect our five-minute jobs. If your job finishes in a few seconds, include the minimum in your estimate.&lt;/p&gt;

&lt;p&gt;An agent waiting for an LLM may use little CPU. A compiler could use most of the available CPU during the same five-minute session. &lt;a href="https://vercel.com/kb/guide/vercel-sandbox-vs-e2b" rel="noopener noreferrer"&gt;Vercel's explanation of Active CPU billing&lt;/a&gt; covers the difference between running code and waiting on I/O.&lt;/p&gt;

&lt;p&gt;Record elapsed time and CPU time separately. A job that takes five minutes to finish won't necessarily use five minutes of CPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Vercel Sandbox free?
&lt;/h2&gt;

&lt;p&gt;Hobby includes 5 active CPU hours, 420 GB-hours of memory and 5,000 creations each month. See the &lt;a href="https://vercel.com/docs/sandbox/pricing" rel="noopener noreferrer"&gt;Hobby quotas&lt;/a&gt;. You also get 20 GB of data transfer. If you use up an allowance, Vercel pauses new sandbox creation until it resets. You can upgrade to Pro to keep running tasks.&lt;/p&gt;

&lt;p&gt;A small project that qualifies for Hobby and stays within those allowances can cost nothing. Keep that in mind when comparing paid rates. The estimates below help you budget for usage beyond free allowances and trial credits.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Pro credit changes your bill
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/plans/pro-plan" rel="noopener noreferrer"&gt;Vercel Pro&lt;/a&gt; costs $20 a month and includes one deploying seat and $20 in usage credit. You can spend that credit on eligible infrastructure across the account. Extra paid seats add to the bill.&lt;/p&gt;

&lt;p&gt;If you use the account only for Sandbox, with one seat and no extras, the bill is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monthly Pro bill = $20 + max(0, eligible usage charges - $20)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With $9.20 of eligible usage, you pay $20. With $92.01 of usage, you pay $92.01. Simply adding $20 to your usage estimate would count the portion covered by the credit twice.&lt;/p&gt;

&lt;p&gt;If you already pay for Pro, check how much credit your other services use. You may have enough left to cover a small sandbox workload. In that case, adding those tasks won't increase your bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: 10,000 agent tasks per month
&lt;/h2&gt;

&lt;p&gt;Each task in this example creates a sandbox, runs for five minutes and stops. We use 2 vCPUs and 4 GB of memory per instance. Vercel runs in &lt;code&gt;iad1&lt;/code&gt;, with CPU utilization averaging 10% across the allocation. Each job fits within 5 GB of local workspace storage.&lt;/p&gt;

&lt;p&gt;We leave out model tokens, taxes, retained snapshots, persistent drives and billable network traffic. The example assumes a disposable workspace. If you need to keep the result, export it before stopping the sandbox and add any transfer charge to your estimate.&lt;/p&gt;

&lt;p&gt;Using the &lt;a href="https://vercel.com/docs/sandbox/pricing" rel="noopener noreferrer"&gt;published Vercel rates&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Elapsed sandbox hours = 10,000 × 5 / 60 = 833.333...
Active vCPU-hours     = 833.333... × 2 × 0.10 = 166.666...
Memory GB-hours      = 833.333... × 4 = 3,333.333...

CPU cost             = 166.666... × $0.128 = $21.333...
Memory cost          = 3,333.333... × $0.0212 = $70.666...
Creation cost        = 10,000 × $0.60 / 1,000,000 = $0.006
Vercel usage total   = $92.006, rounded to $92.01

Lizard Small         = 833.333... × $0.009 = $7.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the Vercel cost here comes from memory. Even if the agent used less CPU, that memory charge would remain. Lizard Small charges a fixed rate for the running sandbox, with CPU, memory and idle time included.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changes as task volume grows?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Five-minute runs per month&lt;/th&gt;
&lt;th&gt;Total sandbox hours&lt;/th&gt;
&lt;th&gt;Vercel usage estimate&lt;/th&gt;
&lt;th&gt;Vercel Pro bill, one seat*&lt;/th&gt;
&lt;th&gt;Lizard running cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;83.33&lt;/td&gt;
&lt;td&gt;$9.20&lt;/td&gt;
&lt;td&gt;$20.00&lt;/td&gt;
&lt;td&gt;$0.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;833.33&lt;/td&gt;
&lt;td&gt;$92.01&lt;/td&gt;
&lt;td&gt;$92.01&lt;/td&gt;
&lt;td&gt;$7.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;8,333.33&lt;/td&gt;
&lt;td&gt;$920.06&lt;/td&gt;
&lt;td&gt;$920.06&lt;/td&gt;
&lt;td&gt;$75.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The Pro bill assumes the full $20 credit is available for these tasks and you incur no other charges. Lizard figures exclude trial credits. We use the same task duration for both providers; test your application to find out how long it takes on each.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You don't need a worker for every task. A small pool can process 100,000 tasks over time. If those tasks arrive at once and need an immediate response, check concurrency limits as well as monthly cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if CPU usage is higher?
&lt;/h3&gt;

&lt;p&gt;Keeping 2 vCPUs and 4 GB allocated for one hour gives these compute costs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Average CPU utilization&lt;/th&gt;
&lt;th&gt;Vercel CPU + memory&lt;/th&gt;
&lt;th&gt;Lizard Small&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;$0.0848&lt;/td&gt;
&lt;td&gt;$0.009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;$0.1104&lt;/td&gt;
&lt;td&gt;$0.009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;$0.2128&lt;/td&gt;
&lt;td&gt;$0.009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$0.3408&lt;/td&gt;
&lt;td&gt;$0.009&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Creation fees and extras sit outside this table. Lizard costs less at every CPU level shown, including when Vercel charges nothing for CPU. In that case, Vercel's memory charge alone exceeds Lizard Small's running rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vercel Sandbox alternatives: compare the paid rates
&lt;/h2&gt;

&lt;p&gt;The table compares the advertised 2 vCPU / 4 GB memory class on each service. E2B and Daytona price memory in GiB, so keep their units when working out your own bill.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Running rate for this allocation&lt;/th&gt;
&lt;th&gt;Usage for 10,000 five-minute runs&lt;/th&gt;
&lt;th&gt;Plan or allowance to consider&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://lizard.build/sandboxes" rel="noopener noreferrer"&gt;Lizard Sandboxes&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.009/hour&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$7.50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No monthly subscription; temporary trial credits excluded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vercel.com/docs/sandbox/pricing" rel="noopener noreferrer"&gt;Vercel Sandbox&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$0.1104/hour at 10% CPU&lt;/td&gt;
&lt;td&gt;$92.01 including creation calls&lt;/td&gt;
&lt;td&gt;Pro fee and credit as explained above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://e2b.dev/pricing" rel="noopener noreferrer"&gt;E2B&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$0.1656/hour&lt;/td&gt;
&lt;td&gt;$138.00&lt;/td&gt;
&lt;td&gt;Hobby supports the default size; Pro adds $150/month for higher limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.daytona.io/pricing" rel="noopener noreferrer"&gt;Daytona&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$0.1656/hour&lt;/td&gt;
&lt;td&gt;$138.00&lt;/td&gt;
&lt;td&gt;Temporary credits excluded; extra storage adds cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The E2B and Daytona calculations use $0.0504 per vCPU-hour and $0.0162 per GiB-hour: &lt;code&gt;2 × $0.0504 + 4 × $0.0162 = $0.1656/hour&lt;/code&gt;. E2B publishes its rates per second; we converted them to hours. Their totals exclude storage or network charges beyond any included allocation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwm909p8she1mtjf7qod.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwm909p8she1mtjf7qod.jpg" alt="Vercel Sandbox pricing compared with Lizard, E2B and Daytona for 10,000 five-minute runs" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this paid workload, Lizard has the lowest price of the four providers. Your bill may differ if you have unused credits, qualify for free usage or negotiate a contract. Check the disk space and regions you need, along with the provider's security and lifecycle features, before choosing on price.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lizard: a fixed rate for each sandbox size
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://lizard.build/sandboxes" rel="noopener noreferrer"&gt;Lizard Sandboxes&lt;/a&gt; has three size tiers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;vCPUs&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Local workspace storage&lt;/th&gt;
&lt;th&gt;Running price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4 GB&lt;/td&gt;
&lt;td&gt;5 GB&lt;/td&gt;
&lt;td&gt;$0.009/hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;td&gt;20 GB&lt;/td&gt;
&lt;td&gt;$0.018/hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;td&gt;50 GB&lt;/td&gt;
&lt;td&gt;$0.036/hour&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You pay by the second while the sandbox runs, including when it's idle. Each account gets up to 2 TB of sandbox egress over a rolling 30 days. Persistent Volumes cost extra. Standard accounts use pay as you go with no monthly subscription, and purchased credits don't expire. The &lt;a href="https://lizard.build/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; lists the trial terms.&lt;/p&gt;

&lt;p&gt;Lizard Sandboxes also support Firecracker microVMs for selected accounts and supported templates. Those microVMs each have their own kernel. Access is currently limited: the default runtime uses Kubernetes pods with runc and shares the host kernel. If your application requires a separate kernel, confirm which runtime your account can use before you choose a size or compare its cost.&lt;/p&gt;

&lt;p&gt;Once you've checked the runtime and size, you can budget from the time your tasks take to run. Use &lt;a href="https://lizard.build/cli" rel="noopener noreferrer"&gt;Lizard CLI&lt;/a&gt; from your terminal, or call the API described in the &lt;a href="https://lizard.build/docs/sandboxes/" rel="noopener noreferrer"&gt;Sandboxes documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  E2B: longer sessions can mean a Pro subscription
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://e2b.dev/pricing" rel="noopener noreferrer"&gt;E2B's pricing page&lt;/a&gt; lists default-size sandboxes on Hobby, with sessions up to one hour and 20 concurrent instances. Pro adds custom CPU and RAM, sessions up to 24 hours, and higher concurrency for a $150 monthly fee plus usage.&lt;/p&gt;

&lt;p&gt;Our short tasks fit Hobby's default size and session limit, provided you schedule them within its concurrency limit. If you need Pro, the same usage plus the base fee totals $288 before extras or credits. Our &lt;a href="https://lizard.build/blog/e2b-sandbox" rel="noopener noreferrer"&gt;E2B sandbox guide&lt;/a&gt; covers the lifecycle details to check before choosing a plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Daytona: budget for the files you keep
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.daytona.io/pricing" rel="noopener noreferrer"&gt;Daytona&lt;/a&gt; lists CPU, memory and storage separately and bills by the second. Its first 5 GiB of storage are free; additional storage costs $0.000108 per GiB-hour. It also publishes GPU options.&lt;/p&gt;

&lt;p&gt;Our example fits within the included storage. For a job with a large working directory, add the cost of the space it uses and the time you keep it. If the job needs a GPU, add that rate too.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Vercel Sandbox is worth the higher paid rate
&lt;/h2&gt;

&lt;p&gt;If your app already runs on Vercel, you may prefer to keep its sandbox tasks in the same account and tools, especially when unused credit covers them. Moving providers means spending time on authentication, retries and file handling. Include that work in the decision.&lt;/p&gt;

&lt;p&gt;Vercel has &lt;a href="https://vercel.com/docs/sandbox/concepts/regions" rel="noopener noreferrer"&gt;19 regions and region failover on paid plans&lt;/a&gt;. If your agent needs to run near your users or data, check those locations before comparing hourly rates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/sandbox/concepts" rel="noopener noreferrer"&gt;Vercel runs sandboxes in Firecracker microVMs&lt;/a&gt;. Its persistent sandboxes save the filesystem when they stop and restore it for the next session. Check whether your application needs those files or also needs to preserve running processes.&lt;/p&gt;

&lt;p&gt;The current quotas permit 45-minute sessions on Hobby and 24-hour sessions on Pro, with up to 10 and 10,000 concurrent sandboxes respectively. The Pro limit is 8 vCPUs and 16 GB memory per instance. Current SDK configurations include 64 GB of ephemeral disk, more than Lizard Small's 5 GB workspace. See &lt;a href="https://vercel.com/docs/sandbox/pricing" rel="noopener noreferrer"&gt;Vercel's resource limits&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to measure before switching
&lt;/h2&gt;

&lt;p&gt;Run a task you expect to use often and record:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How long it takes to get a usable result, including setup, package installation and retries.&lt;/li&gt;
&lt;li&gt;How much CPU time and allocated memory it uses, so you can calculate Active CPU charges.&lt;/li&gt;
&lt;li&gt;Which files you keep afterward and what retention and recovery will cost.&lt;/li&gt;
&lt;li&gt;How much network traffic it generates, including result downloads, preview ports and calls to other services.&lt;/li&gt;
&lt;li&gt;How many tasks need to run at once and how long each session lasts. Check both against the account's limits.&lt;/li&gt;
&lt;li&gt;Which runtime runs the task and what it can access. Give it only the credentials it needs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stop workers when they finish and remove stored data you no longer need. Vercel can still charge for &lt;a href="https://vercel.com/docs/sandbox/concepts/snapshots" rel="noopener noreferrer"&gt;snapshots&lt;/a&gt; after a sandbox stops. Lizard charges the size's running rate for as long as you leave it running, even when it's idle.&lt;/p&gt;

&lt;p&gt;Try a small batch first. Divide its cost by the number of tasks that succeeded, then compare that figure with what you pay now before moving more traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a Small sandbox
&lt;/h2&gt;

&lt;p&gt;For our paid task example, Lizard costs $7.50 against Vercel's $92.01. You can estimate its running cost from the size and duration alone. Firecracker support is available through the limited rollout described above.&lt;/p&gt;

&lt;p&gt;If the runtime, disk space and account limits fit your project, &lt;a href="https://lizard.build/sandboxes" rel="noopener noreferrer"&gt;try Lizard Sandboxes&lt;/a&gt; with one of your own tasks. Check the result and the charges. The &lt;a href="https://lizard.build/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; covers the costs to include when you increase usage.&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://lizard.build/blog/vercel-sandbox-pricing" rel="noopener noreferrer"&gt;Lizard&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Codex Exec: Automate Deployment Checks from the Terminal</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:57:09 +0000</pubDate>
      <link>https://dev.to/yuraoak/codex-exec-automate-deployment-checks-from-the-terminal-4bg2</link>
      <guid>https://dev.to/yuraoak/codex-exec-automate-deployment-checks-from-the-terminal-4bg2</guid>
      <description>&lt;p&gt;Originally published on &lt;a href="https://lizard.build/blog/codex-exec" rel="noopener noreferrer"&gt;Lizard (lizard.build)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;codex exec&lt;/code&gt; runs Codex without the interactive terminal interface. Give it a prompt or pipe in data, then save the answer for your next script step. This guide builds a deployment report with JSONL events, a schema-constrained result and an exit code based on real HTTP checks.&lt;/p&gt;

&lt;p&gt;You will test two versions of a small quote API: one works, and one returns HTTP 200 from its health endpoint while the quote route fails. The complete example includes the collector, report schema and validation script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run Codex without the interactive interface
&lt;/h2&gt;

&lt;p&gt;From a trusted Git repository, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--sandbox&lt;/span&gt; read-only &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Summarize this repository in three sentences. Do not change files."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex writes progress to stderr and its final answer to stdout. You can redirect that answer to a file. The &lt;a href="https://learn.chatgpt.com/docs/non-interactive-mode" rel="noopener noreferrer"&gt;official non-interactive guide&lt;/a&gt; covers this behavior and the supported automation patterns.&lt;/p&gt;

&lt;p&gt;For a deployment report, we will supply the observations on stdin. A small Python collector makes the network requests. Codex explains the evidence, and a separate function checks the report against those observations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6f4w94oiojxsiz8q4eg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6f4w94oiojxsiz8q4eg.png" alt="Codex exec: HTTP evidence, JSONL events and a validated final report" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the three output files
&lt;/h2&gt;

&lt;p&gt;These files have different roles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Contains&lt;/th&gt;
&lt;th&gt;How to read it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events.jsonl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run events from &lt;code&gt;--json&lt;/code&gt;, one JSON object per line&lt;/td&gt;
&lt;td&gt;Parse each nonempty line separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;report.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Final answer written by &lt;code&gt;-o&lt;/code&gt;, shaped by &lt;code&gt;--output-schema&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Parse one JSON object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stderr.log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Diagnostics from the CLI process&lt;/td&gt;
&lt;td&gt;Keep it for troubleshooting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;--json&lt;/code&gt; changes stdout into an event stream. It does not make the whole stream a single report object. &lt;code&gt;--output-schema&lt;/code&gt; describes the final answer, and &lt;code&gt;-o&lt;/code&gt; saves that answer separately. This distinction prevents a common automation bug: trying to parse the entire event log with one JSON read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare the example
&lt;/h2&gt;

&lt;p&gt;Use Python 3.10 or later and an authenticated Codex CLI. The Python files need no third-party packages. The shell commands below target Bash or Zsh on macOS or Linux.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex &lt;span class="nt"&gt;--version&lt;/span&gt;
codex login status
&lt;span class="nb"&gt;mkdir &lt;/span&gt;codex-deployment-check
&lt;span class="nb"&gt;cd &lt;/span&gt;codex-deployment-check
&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in &lt;/span&gt;demo.py collect.py gate.py run.py report.schema.json verify.py&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;--fail&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"https://lizard.build/blog-examples/agent-deployment-checks/&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the downloaded files before executing them. Start the demo and keep it running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 demo.py &lt;span class="nt"&gt;--port&lt;/span&gt; 8787
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application has two GET routes. &lt;code&gt;/healthz&lt;/code&gt; returns &lt;code&gt;status: ok&lt;/code&gt;. &lt;code&gt;/api/quote?quantity=3&lt;/code&gt; calculates a quote for three items at 1,200 cents each. The expected result is $36.00 (3,600 cents). The example uses integer cents to avoid rounding in the assertion.&lt;/p&gt;

&lt;p&gt;Open another terminal in the same folder for the remaining commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Collect and inspect the HTTP evidence
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 collect.py http://127.0.0.1:8787 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; evidence.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The collector records the URL, check time, HTTP status and whether each response matches its expected fields. It makes two bounded requests, rejects redirects and caps each response at 64 KiB. It sends expected values and booleans to the model, without copying arbitrary response text.&lt;/p&gt;

&lt;p&gt;For your application, replace the demo paths and expected values in both &lt;code&gt;collect.py&lt;/code&gt; and &lt;code&gt;gate.py&lt;/code&gt;. Choose a route that exercises useful behavior: calculate a price, read a seeded database record or fetch an existing document. Match the response content as well as the status code.&lt;/p&gt;

&lt;p&gt;The collector's exit code says whether it collected evidence. A failed application check still produces an evidence file, so Codex can explain the failure. The final gate determines the job's result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request a structured report
&lt;/h2&gt;

&lt;p&gt;From a trusted Git repository containing the files, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--ignore-user-config&lt;/span&gt; &lt;span class="nt"&gt;--ephemeral&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sandbox&lt;/span&gt; read-only &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-schema&lt;/span&gt; report.schema.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; report.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Explain the evidence on stdin. Use no tools. Copy its verdict, list failed check names in failed_checks, and give a summary and next_step. Do not infer a root cause from HTTP status alone."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; evidence.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; events.jsonl 2&amp;gt; stderr.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use only the new download folder, add &lt;code&gt;--skip-git-repo-check&lt;/code&gt;. The complete wrapper does so in a temporary directory made for the report. Use that flag deliberately when no repository is needed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--ignore-user-config&lt;/code&gt; skips the user's main Codex configuration file while retaining normal authentication. &lt;code&gt;--ephemeral&lt;/code&gt; prevents saving the session rollout. The shell still saves the three explicit output files above. &lt;code&gt;--sandbox read-only&lt;/code&gt; limits model-generated commands; the shell's redirects write the artifacts.&lt;/p&gt;

&lt;p&gt;The schema requires &lt;code&gt;verdict&lt;/code&gt;, &lt;code&gt;failed_checks&lt;/code&gt;, &lt;code&gt;summary&lt;/code&gt; and &lt;code&gt;next_step&lt;/code&gt;, and disallows extra fields. The report should explain what the supplied checks establish. A response can suggest inspecting application logs after a 503, but the status code alone does not prove a database failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parse the stream and the final answer separately
&lt;/h2&gt;

&lt;p&gt;In our live test, Codex emitted these event types in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;thread.started
turn.started
item.completed
turn.completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The completed item was an agent message. These particular runs made no tool calls. Other tasks can produce more events, including command executions, tool calls and errors; do not require every successful run to have exactly four lines.&lt;/p&gt;

&lt;p&gt;The wrapper reads each event and rejects &lt;code&gt;turn.failed&lt;/code&gt; or &lt;code&gt;error&lt;/code&gt;. It also requires &lt;code&gt;turn.completed&lt;/code&gt;, a successful process exit and a final report that parses. Then it compares the report's verdict and failed-check names with the HTTP evidence.&lt;/p&gt;

&lt;p&gt;A stream that ends early is an incomplete job. A report file left over from a previous run is also unsafe to reuse. The wrapper creates a new output directory and refuses to overwrite an existing run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1dgupsmu01nnris5xust.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1dgupsmu01nnris5xust.png" alt="Codex events, final report and diagnostics saved to three separate files" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the complete workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 run.py codex http://127.0.0.1:8787 runs/healthy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command collects fresh evidence, launches Codex with a 180-second process timeout, saves its output and validates the report. The wrapper exits 0 only when both application checks pass and the report agrees.&lt;/p&gt;

&lt;p&gt;To reproduce the failure, start a second demo in another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 demo.py &lt;span class="nt"&gt;--port&lt;/span&gt; 8788 &lt;span class="nt"&gt;--broken&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the same workflow against that instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 run.py codex http://127.0.0.1:8788 runs/broken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The health route returns 200 and the quote route returns 503. Codex receives those observations and returns a failure report. The wrapper exits 1. The reporting process can complete successfully while the application check fails; your automation must use the wrapper's exit code for the application decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results from the working example
&lt;/h2&gt;

&lt;p&gt;We ran both scenarios with Codex CLI 0.156.1 and Python 3.12.10 on September 28, 2026. We also checked the Lizard CLI commands against version 4.0.8.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Health route&lt;/th&gt;
&lt;th&gt;Quote route&lt;/th&gt;
&lt;th&gt;Codex report&lt;/th&gt;
&lt;th&gt;Wrapper exit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Working demo&lt;/td&gt;
&lt;td&gt;200, expected body&lt;/td&gt;
&lt;td&gt;200, $36.00 (3,600 cents)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pass&lt;/code&gt;, no failed checks&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broken quote&lt;/td&gt;
&lt;td&gt;200, expected body&lt;/td&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fail&lt;/code&gt;, &lt;code&gt;quote&lt;/code&gt; failed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both model calls completed and produced a report that matched the collected evidence. The broken-run report suggested reviewing logs at the recorded time. It did not claim that the health endpoint established full application health.&lt;/p&gt;

&lt;p&gt;The local validation tests also reject a false success report and incomplete evidence. Run them without a model call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 verify.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a synthetic HTTP test on a local machine. It does not cover production traffic, public DNS, TLS, database migrations or every route. Add checks for the parts of your own deployment that matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Lizard service state and logs
&lt;/h2&gt;

&lt;p&gt;For an application on Lizard, confirm the target before interpreting failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lizard skills get core &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard status &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard ps &lt;span class="nt"&gt;--project&lt;/span&gt; YOUR_PROJECT &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard logs &lt;span class="nt"&gt;--project&lt;/span&gt; YOUR_PROJECT &lt;span class="nt"&gt;--service&lt;/span&gt; YOUR_SERVICE &lt;span class="nt"&gt;--tail&lt;/span&gt; 100 &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lizard CLI gives you machine-readable service information and a bounded log snapshot. The core guide matches the installed CLI version. Use it when you need more commands or flags, and use explicit project and service selectors in automation.&lt;/p&gt;

&lt;p&gt;Run the adapted HTTP collector against the service's public URL. A container marked as running does not prove that a price lookup or database read works. Compare the HTTP failure time with the service logs to narrow the next investigation. Review and redact log content before sending it to a model.&lt;/p&gt;

&lt;p&gt;If you need to deploy first, start with the &lt;a href="https://lizard.build/docs/guides/deploy-from-coding-agent/" rel="noopener noreferrer"&gt;coding-agent deployment guide&lt;/a&gt;. For apps with data dependencies, the &lt;a href="https://lizard.build/blog/postgres-mcp" rel="noopener noreferrer"&gt;Postgres MCP tutorial&lt;/a&gt; and &lt;a href="https://lizard.build/blog/redis-mcp" rel="noopener noreferrer"&gt;Redis MCP tutorial&lt;/a&gt; explain scoped access for inspecting test data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the result in automation
&lt;/h2&gt;

&lt;p&gt;Keep three outcomes explicit: application passed, application failed, and report job failed. This wrapper uses 0, 1 and 2 respectively. A timeout, CLI authentication problem, invalid report or contradiction in the output produces code 2.&lt;/p&gt;

&lt;p&gt;Store the evidence alongside the report. That lets a teammate verify the result without trusting a prose summary. Give scheduled runs distinct output paths, set a retention period and avoid storing credentials in artifacts.&lt;/p&gt;

&lt;p&gt;On your own trusted machine, &lt;code&gt;codex exec&lt;/code&gt; can reuse the saved CLI login. For GitHub Actions, follow the &lt;a href="https://learn.chatgpt.com/docs/github-action" rel="noopener noreferrer"&gt;official Codex action guide&lt;/a&gt; for authentication and permission setup. Keep API credentials out of repository files and avoid exposing them to untrusted build steps. Review those runner-specific requirements before transferring a local command into CI.&lt;/p&gt;

&lt;p&gt;Use fresh evidence for each independent check. &lt;code&gt;codex exec resume&lt;/code&gt; can continue a conversation, but it is not needed for this one-shot report. The example's ephemeral mode makes each run independent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;What to inspect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Not inside a Git repository&lt;/td&gt;
&lt;td&gt;Run from the intended repository, or deliberately use &lt;code&gt;--skip-git-repo-check&lt;/code&gt; for the isolated report folder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON parser reports extra data&lt;/td&gt;
&lt;td&gt;Parse &lt;code&gt;events.jsonl&lt;/code&gt; one line at a time; parse &lt;code&gt;report.json&lt;/code&gt; as one object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No final report&lt;/td&gt;
&lt;td&gt;Check the process exit, stderr and failure events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrapper exits 1 although Codex exited 0&lt;/td&gt;
&lt;td&gt;The application check failed; inspect &lt;code&gt;failed_checks&lt;/code&gt; and evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrapper exits 2&lt;/td&gt;
&lt;td&gt;Check authentication, timeout, schema, contradictory output or an existing output directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A hosted endpoint redirects&lt;/td&gt;
&lt;td&gt;Verify the route and required authentication; this collector rejects redirects&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;Use this pattern for a focused deployment check, then add assertions for your application's real dependencies. Keep the checks small enough that a failed result points to a useful next step.&lt;/p&gt;

&lt;p&gt;If your team uses Claude Code, the &lt;a href="https://lizard.build/blog/claude-code-headless" rel="noopener noreferrer"&gt;Claude Code headless guide&lt;/a&gt; shows the same HTTP checks with its result envelope. To run the application itself, use &lt;a href="https://lizard.build/docs/cli/" rel="noopener noreferrer"&gt;Lizard CLI&lt;/a&gt; and add the report after your deployment step.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>cli</category>
    </item>
    <item>
      <title>Claude Code Headless: Check Your Deployment from the Terminal</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:49:05 +0000</pubDate>
      <link>https://dev.to/yuraoak/claude-code-headless-check-your-deployment-from-the-terminal-2pmf</link>
      <guid>https://dev.to/yuraoak/claude-code-headless-check-your-deployment-from-the-terminal-2pmf</guid>
      <description>&lt;p&gt;Originally published on &lt;a href="https://lizard.build/blog/claude-code-headless" rel="noopener noreferrer"&gt;Lizard (lizard.build)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Claude Code headless mode runs a prompt from your terminal with &lt;code&gt;claude -p&lt;/code&gt; and exits when the task finishes. You can pipe in data, save the answer and call it from a script. This guide uses it to explain deployment checks: a health endpoint, a price quote and a report that your script can verify.&lt;/p&gt;

&lt;p&gt;The example catches a common failure: &lt;code&gt;/healthz&lt;/code&gt; returns HTTP 200 while a real application route returns 503. You will get a reproducible demo, a JSON report and a nonzero exit code when the application check fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one non-interactive command
&lt;/h2&gt;

&lt;p&gt;With Claude Code installed and signed in, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Explain what an HTTP 503 response tells me in two sentences."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tools&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-p&lt;/code&gt; means print the result and exit. &lt;code&gt;--tools ""&lt;/code&gt; disables the built-in tools for this question. Claude can answer from the prompt without reading your repository or running a shell command. The &lt;a href="https://code.claude.com/docs/en/cli-reference" rel="noopener noreferrer"&gt;Claude Code CLI reference&lt;/a&gt; lists the available flags.&lt;/p&gt;

&lt;p&gt;For a deployment check, give the model observations that your script has already collected. This makes each HTTP request and pass condition easy to inspect. It also keeps deployment credentials out of the model's input.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0m5rukpumemmnwp23u0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0m5rukpumemmnwp23u0.png" alt="Claude Code headless: HTTP checks, structured report and result validation" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;p&gt;Use Python 3.10 or later, a current Claude Code installation and an authenticated account. The demo uses Python's standard library, so it has no Python packages to install. Run the shell examples in Bash or Zsh on macOS or Linux.&lt;/p&gt;

&lt;p&gt;Check the installed CLI and its authentication state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--version&lt;/span&gt;
claude auth status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a request fails because the saved session has expired, run &lt;code&gt;claude auth login&lt;/code&gt; and retry. A saved login can exist even when the next API request cannot refresh it.&lt;/p&gt;

&lt;p&gt;For the optional hosted checks, install &lt;a href="https://lizard.build/docs/cli/" rel="noopener noreferrer"&gt;Lizard CLI&lt;/a&gt; and sign in to your own project. If you still need to deploy your application, follow &lt;a href="https://lizard.build/blog/deploy-from-claude-code" rel="noopener noreferrer"&gt;Deploy from Claude Code&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Download the example and start the demo
&lt;/h2&gt;

&lt;p&gt;Create a new folder and download the six files. Read them before running them. The demo accepts GET requests and keeps no customer data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;claude-deployment-check
&lt;span class="nb"&gt;cd &lt;/span&gt;claude-deployment-check
&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in &lt;/span&gt;demo.py collect.py gate.py run.py report.schema.json verify.py&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;--fail&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"https://lizard.build/blog-examples/agent-deployment-checks/&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done
&lt;/span&gt;python3 demo.py &lt;span class="nt"&gt;--port&lt;/span&gt; 8787
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave that terminal running. In another terminal, change to the same folder. The demo exposes &lt;code&gt;/healthz&lt;/code&gt; and &lt;code&gt;/api/quote?quantity=3&lt;/code&gt;. Each item costs 1,200 cents; a quote for three items must return &lt;code&gt;currency: USD&lt;/code&gt; and &lt;code&gt;total_cents: 3600&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These are demo routes. For your own application, edit the paths and expected fields in &lt;code&gt;collect.py&lt;/code&gt; and &lt;code&gt;gate.py&lt;/code&gt;. Choose a small operation a user actually needs: reading a seeded record, calculating a price or fetching a saved document. A route that only returns “OK” cannot verify those operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Collect evidence before asking Claude
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 collect.py http://127.0.0.1:8787 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; evidence.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The collector makes two requests with a five-second timeout for each. It rejects redirects, invalid JSON and responses larger than 64 KiB. It checks both the HTTP status and the expected JSON fields. It records the time, expected values and check results; it does not copy arbitrary response text into the prompt.&lt;/p&gt;

&lt;p&gt;That last choice matters when an endpoint contains user text. A support message or database row can contain instructions aimed at an agent. This example gives Claude a small set of measured fields to explain.&lt;/p&gt;

&lt;p&gt;The collector exits successfully when it writes evidence, including evidence of a failed check. The final check script determines whether the job passes. Keep those two meanings separate in your automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask for a structured report
&lt;/h2&gt;

&lt;p&gt;Run this from the folder containing the downloaded files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--safe-mode&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Explain the evidence on stdin. Use no tools. Copy its verdict. List the names of failed checks in failed_checks. Give a short summary and one next_step. Do not infer a root cause from HTTP status alone."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tools&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-session-persistence&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-format&lt;/span&gt; json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--json-schema&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;report.schema.json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; evidence.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; claude-result.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example uses &lt;code&gt;--safe-mode&lt;/code&gt; to disable customizations such as hooks, plugins and MCP servers during this report task. It uses the existing account login. Check your installed version's help if it does not recognize the flag.&lt;/p&gt;

&lt;p&gt;Claude's response has an outer result object. When you supply the schema, the report sits inside &lt;code&gt;structured_output&lt;/code&gt;. The outer object also carries run metadata and an &lt;code&gt;is_error&lt;/code&gt; field. A plain JSON response and a schema-constrained response serve different purposes; parse the field your command requested.&lt;/p&gt;

&lt;p&gt;The schema asks for four fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;verdict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pass&lt;/code&gt; or &lt;code&gt;fail&lt;/code&gt;, copied from the checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failed_checks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Names of the checks that failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A short explanation of the observed result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;next_step&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One concrete follow-up&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://code.claude.com/docs/en/headless" rel="noopener noreferrer"&gt;programmatic execution documentation&lt;/a&gt; describes these output formats. A valid JSON shape does not establish whether the explanation is correct, so the wrapper checks the report against the measured results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the complete check
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;run.py&lt;/code&gt; collects fresh evidence, calls Claude with a 180-second process timeout, extracts the report and checks it. Give each run a new output directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 run.py claude http://127.0.0.1:8787 runs/healthy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The directory contains &lt;code&gt;evidence.json&lt;/code&gt;, &lt;code&gt;claude-result.json&lt;/code&gt;, &lt;code&gt;report.json&lt;/code&gt; and &lt;code&gt;stderr.log&lt;/code&gt;. Keep the raw result when debugging a failure; an authentication error can appear in the result on stdout.&lt;/p&gt;

&lt;p&gt;The wrapper uses three exit codes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exit code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Both application checks passed and the report agreed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At least one application check failed and the report agreed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The reporting job failed, timed out or returned invalid or contradictory output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Code 0 from the Claude process means the agent run completed. The wrapper makes the separate decision about the application. An invalid report cannot turn a failed HTTP check into a successful job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce a failure that a health check misses
&lt;/h2&gt;

&lt;p&gt;Start a second demo in another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 demo.py &lt;span class="nt"&gt;--port&lt;/span&gt; 8788 &lt;span class="nt"&gt;--broken&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 run.py claude http://127.0.0.1:8788 runs/broken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second demo still answers &lt;code&gt;/healthz&lt;/code&gt; with HTTP 200. Its quote route returns HTTP 503. The evidence therefore records &lt;code&gt;fail&lt;/code&gt;, with &lt;code&gt;quote&lt;/code&gt; as the failed check. With a valid Claude response, the wrapper exits 1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl30kz750v99axr6dzu3f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl30kz750v99axr6dzu3f.png" alt="A health endpoint returns 200 while the quote route returns 503, so the deployment check fails" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also test the HTTP checks and validation logic without calling a model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 verify.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks the healthy and broken demos, then verifies that the gate rejects a false success report and incomplete evidence. It does not use Claude credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply the check to an application on Lizard
&lt;/h2&gt;

&lt;p&gt;Choose the application URL from your project. Inspect the current project context and service state with Lizard CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lizard skills get core &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard status &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard ps &lt;span class="nt"&gt;--project&lt;/span&gt; YOUR_PROJECT &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard logs &lt;span class="nt"&gt;--project&lt;/span&gt; YOUR_PROJECT &lt;span class="nt"&gt;--service&lt;/span&gt; YOUR_SERVICE &lt;span class="nt"&gt;--tail&lt;/span&gt; 100 &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lizard status&lt;/code&gt; shows the local folder's link. &lt;code&gt;lizard ps&lt;/code&gt; reads the service state for the selected project. JSON logs return a bounded snapshot and exit; they do not keep following the service.&lt;/p&gt;

&lt;p&gt;Use the service's public URL with the same checker after you adapt the two route assertions to your application. If you host the supplied demo itself, bind it to &lt;code&gt;0.0.0.0&lt;/code&gt; and configure the service's port to match. The local examples bind to &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Read logs around the recorded check time when a route fails. An HTTP 503 alone cannot tell you whether the cause was a database connection, a dependency or application code. Review and redact any logs before adding them to a model prompt.&lt;/p&gt;

&lt;p&gt;For applications that depend on stored data, add a test with a known record. The &lt;a href="https://lizard.build/blog/postgres-mcp" rel="noopener noreferrer"&gt;Postgres MCP guide&lt;/a&gt; explains scoped database access, and the &lt;a href="https://lizard.build/blog/redis-mcp" rel="noopener noreferrer"&gt;Redis MCP guide&lt;/a&gt; covers restricted Redis reads. Use test data and credentials suited to the check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless mode, authentication and unattended jobs
&lt;/h2&gt;

&lt;p&gt;Headless mode describes how you run the command. It does not create an unattended login or remove tool permissions. For a scheduled job, prepare authentication on the runner and use an explicit timeout.&lt;/p&gt;

&lt;p&gt;Claude also offers &lt;code&gt;--bare&lt;/code&gt;, which skips much of the normal startup context. Its Anthropic authentication path uses &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; or a configured API key helper; it does not read subscription OAuth credentials. Our account-login example therefore uses &lt;code&gt;--safe-mode&lt;/code&gt;. Review the current documentation before moving the job to CI, where the authentication setup may differ.&lt;/p&gt;

&lt;p&gt;For live progress, Claude supports &lt;code&gt;--output-format stream-json --verbose&lt;/code&gt;. Each line is an event. Use plain &lt;code&gt;json&lt;/code&gt; when you only need one final result, as this example does. Avoid resuming an old conversation for an independent deployment check; each run should use fresh observations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Check next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OAuth session expired&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;claude auth login&lt;/code&gt;, then retry the actual request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The report file contains an error&lt;/td&gt;
&lt;td&gt;Inspect the Claude exit code and outer &lt;code&gt;is_error&lt;/code&gt; field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;structured_output&lt;/code&gt; is missing&lt;/td&gt;
&lt;td&gt;Confirm both schema and JSON output flags reached the CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A tool waits for approval&lt;/td&gt;
&lt;td&gt;Decide which operation the job needs; this report task disables built-in tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/healthz&lt;/code&gt; passes but the wrapper exits 1&lt;/td&gt;
&lt;td&gt;Inspect the quote check and logs at the recorded time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The wrapper exits 2&lt;/td&gt;
&lt;td&gt;Inspect stderr, raw result and schema; use a new output directory on retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A hosted route redirects to a login page&lt;/td&gt;
&lt;td&gt;Use the correct test endpoint and its intended authentication; the demo checker rejects redirects&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Test scope and next step
&lt;/h2&gt;

&lt;p&gt;On September 28, 2026, we ran the local HTTP and report-validation tests with Python 3.12.10. They covered healthy responses, a broken quote route, a false success report and missing evidence. We checked the command flags against Claude Code 2.1.259, Lizard CLI 4.0.8 and the official documentation. The Claude model response was not part of the completed tests; the report behavior above describes the documented output contract. These synthetic tests do not establish the health of a production application.&lt;/p&gt;

&lt;p&gt;For the same pattern with JSONL events and a separate final report file, see &lt;a href="https://lizard.build/blog/codex-exec" rel="noopener noreferrer"&gt;Codex Exec deployment checks&lt;/a&gt;. To put your own app online, follow the &lt;a href="https://lizard.build/blog/deploy-from-claude-code" rel="noopener noreferrer"&gt;Claude Code deployment guide&lt;/a&gt;, then add a check for the operation your users need most.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>claude</category>
    </item>
    <item>
      <title>Redis MCP: Connect Your AI Agent to Your Database</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Sat, 26 Sep 2026 20:39:40 +0000</pubDate>
      <link>https://dev.to/yuraoak/redis-mcp-connect-your-ai-agent-to-your-database-3058</link>
      <guid>https://dev.to/yuraoak/redis-mcp-connect-your-ai-agent-to-your-database-3058</guid>
      <description>&lt;p&gt;Redis MCP lets an AI client call tools that read and change data in your Redis database. To connect your own database, run the official &lt;code&gt;redis-mcp-server&lt;/code&gt;, give it a Redis address and a restricted credential, then register that process in Cursor or Claude Desktop.&lt;/p&gt;

&lt;p&gt;This guide gets that connection working with two small demo keys. You will read a string and a hash, inspect a TTL, and check that Redis rejects a write. You can start on your computer, then use the same approach with a dedicated &lt;a href="https://lizard.build/redis" rel="noopener noreferrer"&gt;Managed Redis&lt;/a&gt; instance in Lizard (lizard.build).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tested September 27, 2026, Dubai time:&lt;/strong&gt; Python 3.12.10, Redis 8.8.0, &lt;code&gt;redis-mcp-server&lt;/code&gt; 0.5.1, MCP Python SDK 1.30.0 and redis-py 8.1.0. The downloadable test passed 14 checks against an isolated local Redis process through MCP over stdio. It did not test a hosted database, TLS or the Cursor and Claude Desktop interfaces. Those setup steps follow the linked product documentation. This guide was prepared with AI assistance; the test script and results are available below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the Redis MCP server that accesses data
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://redis.io/docs/latest/integrate/redis-mcp/" rel="noopener noreferrer"&gt;official Redis MCP server&lt;/a&gt; connects to a Redis endpoint. Its tools include string and hash reads, writes, key inspection and server information. Redis enforces the permissions of the credential you supply.&lt;/p&gt;

&lt;p&gt;There are several Redis tools with MCP in their names:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it connects to&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;redis/mcp-redis&lt;/code&gt;, packaged as &lt;code&gt;redis-mcp-server&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Your Redis database&lt;/td&gt;
&lt;td&gt;Reading or changing application data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis documentation MCP at &lt;code&gt;redis.io/mcp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Redis documentation&lt;/td&gt;
&lt;td&gt;Looking up commands and examples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cloud MCP&lt;/td&gt;
&lt;td&gt;The Redis Cloud management API&lt;/td&gt;
&lt;td&gt;Managing Redis Cloud resources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We use the first one. A documentation connection will not give your agent access to your keys. Redis describes the distinction in its &lt;a href="https://redis.io/docs/latest/develop/setup/build-with-an-agent/" rel="noopener noreferrer"&gt;agent setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F46zd4ofrhf2cla3o4378.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F46zd4ofrhf2cla3o4378.png" alt="Cursor or Claude Desktop connects to Redis through a local MCP server and a reader credential." width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The MCP process runs on the same computer as your client and communicates through standard input and output, or &lt;strong&gt;stdio&lt;/strong&gt;. It opens a separate network connection to Redis. This setup needs no public HTTP endpoint for MCP. A green connection indicator in the client only shows that the MCP process started; a tool call must still prove that Redis authentication and data access work.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Prepare a small Redis database
&lt;/h2&gt;

&lt;p&gt;Use a dedicated learning instance with synthetic data. You need Python 3.10 or later, &lt;a href="https://docs.astral.sh/uv/getting-started/installation/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, and access to a Redis server. The local route also needs &lt;code&gt;redis-server&lt;/code&gt; on your PATH.&lt;/p&gt;

&lt;p&gt;Download these files into a new demo directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://lizard.build/blog-examples/redis-mcp/requirements.txt" rel="noopener noreferrer"&gt;requirements.txt&lt;/a&gt;: the pinned Python packages.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lizard.build/blog-examples/redis-mcp/setup.py" rel="noopener noreferrer"&gt;setup.py&lt;/a&gt;: creates two keys and a restricted reader, then writes the MCP configuration.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lizard.build/blog-examples/redis-mcp/verify.py" rel="noopener noreferrer"&gt;verify.py&lt;/a&gt;: starts its own local Redis process and tests the MCP connection.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lizard.build/blog-examples/redis-mcp/validation.json" rel="noopener noreferrer"&gt;validation.json&lt;/a&gt;: the results from this guide's test run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On macOS or Linux, create the Python environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv venv .venv &lt;span class="nt"&gt;--python&lt;/span&gt; 3.12
uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--python&lt;/span&gt; .venv/bin/python &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a separate terminal, start a temporary Redis instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-server &lt;span class="nt"&gt;--bind&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 6391 &lt;span class="nt"&gt;--save&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;--appendonly&lt;/span&gt; no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave that terminal running. This local instance has no persistence and listens only on loopback. Stop it with Ctrl+C when you finish. If port 6391 already belongs to another process, pick a free port and set &lt;code&gt;ADMIN_REDIS_URL&lt;/code&gt; to match before running setup.&lt;/p&gt;

&lt;p&gt;In your demo directory, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.venv/bin/python setup.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup script connects to &lt;code&gt;redis://127.0.0.1:6391/0&lt;/code&gt; by default. It creates:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Initial TTL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcpdemo:status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ready&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,600 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcpdemo:session:42&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hash&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;user=demo-user&lt;/code&gt;, &lt;code&gt;language=english&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;3,600 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It also creates the user &lt;code&gt;mcp_reader&lt;/code&gt; with a random password. It refuses to replace an existing user or demo keys, so a repeat run on the same instance stops with an explanation.&lt;/p&gt;

&lt;p&gt;The script saves &lt;code&gt;mcp.local.json&lt;/code&gt; with an absolute path to the installed MCP server. The file contains the reader password. Keep it private and add these paths to the demo project's &lt;code&gt;.gitignore&lt;/code&gt; before committing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.venv/
.env
mcp.local.json
.cursor/mcp.json
test-runs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, use &lt;code&gt;.venv\Scripts\python.exe&lt;/code&gt; for the Python commands. The script chooses the matching executable path for the generated config. This guide's automated run used macOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understand the reader's permissions
&lt;/h2&gt;

&lt;p&gt;The script applies this Redis ACL policy. This is Redis command syntax, with a placeholder password; the setup script generates the real password for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ACL SETUSER mcp_reader reset on &amp;gt;REPLACE_WITH_RANDOM_PASSWORD ~mcpdemo:* -@all +ping +get +hget +hgetall +type +ttl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rules permit reads of known strings and hashes under &lt;code&gt;mcpdemo:*&lt;/code&gt;, plus type and TTL checks. They deny writes, administration commands, pub/sub and key enumeration. The &lt;a href="https://redis.io/docs/latest/commands/acl-setuser/" rel="noopener noreferrer"&gt;Redis ACL reference&lt;/a&gt; explains each rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A read-only prompt does not enforce read-only access.&lt;/strong&gt; The database credential does. The MCP server can still advertise write tools; Redis should reject their execution for this user. Keep the client's approval prompts enabled as another check on which calls run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgy2qf461pc3pl8ikd0ly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgy2qf461pc3pl8ikd0ly.png" alt="Redis allows reads and TTL checks for demo keys; writes, other prefixes and SCAN are denied." width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the policy does not allow SCAN
&lt;/h3&gt;

&lt;p&gt;An ACL key pattern restricts access to key values. It does not make &lt;code&gt;SCAN&lt;/code&gt; return only the key names under that pattern. In our test, granting &lt;code&gt;+scan&lt;/code&gt; let &lt;code&gt;mcp_reader&lt;/code&gt; discover the name &lt;code&gt;private:sentinel&lt;/code&gt;, even though it could not read that key's value. Revoking &lt;code&gt;SCAN&lt;/code&gt; blocked enumeration again.&lt;/p&gt;

&lt;p&gt;Start with known demo keys. If you later allow browsing on a separate instance that holds no unrelated data, use &lt;code&gt;scan_keys&lt;/code&gt; in small iterations and follow the returned cursor until it becomes zero. &lt;code&gt;COUNT&lt;/code&gt; is a work hint, not a hard result limit. See the &lt;a href="https://redis.io/docs/latest/commands/scan/" rel="noopener noreferrer"&gt;SCAN reference&lt;/a&gt;. The tutorial's default policy deliberately rejects both &lt;code&gt;scan_keys&lt;/code&gt; and &lt;code&gt;scan_all_keys&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Add Redis MCP to Cursor or Claude Desktop
&lt;/h2&gt;

&lt;p&gt;Open the generated &lt;code&gt;mcp.local.json&lt;/code&gt; locally. It has this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"redis-demo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/ABSOLUTE/PATH/redis-mcp-demo/.venv/bin/redis-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"--host"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6391"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--db"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"REDIS_USERNAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp_reader"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"REDIS_PWD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_GENERATED_READER_PASSWORD"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the actual generated file, not the placeholders above. Merge the &lt;code&gt;redis-demo&lt;/code&gt; entry into your client's existing &lt;code&gt;mcpServers&lt;/code&gt; object; keep any other servers already there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor:&lt;/strong&gt; use &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in the demo project, or &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; for a user-level setup. Check the server in the client's MCP settings and enable it for the project. Cursor documents the file locations in its &lt;a href="https://cursor.com/help/customization/mcp" rel="noopener noreferrer"&gt;MCP integration guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Desktop:&lt;/strong&gt; merge the entry into &lt;code&gt;claude_desktop_config.json&lt;/code&gt;. On macOS that file lives under &lt;code&gt;~/Library/Application Support/Claude/&lt;/code&gt;. Restart the app after saving. Follow the &lt;a href="https://redis.io/docs/latest/integrate/redis-mcp/client-conf/" rel="noopener noreferrer"&gt;Redis client configuration guide&lt;/a&gt; for the current client steps.&lt;/p&gt;

&lt;p&gt;This guide passes host, port and database as explicit arguments. In the tested 0.5.1 command-line entry point, default CLI values overwrite those settings if you supply only environment variables. Supplying &lt;code&gt;REDIS_HOST&lt;/code&gt; alone can therefore leave the process trying &lt;code&gt;127.0.0.1&lt;/code&gt;. The username and password in the configuration above use the supported &lt;code&gt;REDIS_USERNAME&lt;/code&gt; and &lt;code&gt;REDIS_PWD&lt;/code&gt; variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Prove that the connection works
&lt;/h2&gt;

&lt;p&gt;Ask your client to use the Redis tools explicitly. Approve the requested reads and inspect the tool output before relying on the model's summary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use redis-demo to get mcpdemo:status. Then use hgetall on
mcpdemo:session:42 and type on that same key. Report the raw
tool results and the remaining TTL. Do not change any data.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected results are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get&lt;/code&gt; returns &lt;code&gt;ready&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hgetall&lt;/code&gt; returns the two synthetic fields.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt; returns &lt;code&gt;hash&lt;/code&gt; and a positive TTL below 3,600 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official &lt;code&gt;type&lt;/code&gt; tool includes TTL in its response. In our run it returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcpdemo:session:42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ttl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3591&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your number will differ. A TTL of &lt;code&gt;-2&lt;/code&gt; means the key does not exist; &lt;code&gt;-1&lt;/code&gt; means it exists without expiry. If more than an hour has passed, the demo data may have expired. An authorised administrator can reseed the keys, or you can start a fresh local instance and run setup again.&lt;/p&gt;

&lt;p&gt;Next, use the disposable demo to check the restriction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use redis-demo to try setting mcpdemo:status to changed once.
Report the exact tool result. Then read mcpdemo:status again.
Do not retry with other tools or credentials.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our MCP call returned &lt;code&gt;User mcp_reader has no permissions to run the 'set' command&lt;/code&gt;, and a separate read still returned &lt;code&gt;ready&lt;/code&gt;. This verifies both the denial and the unchanged value. Some Redis MCP tools return an error as text, so inspect the response content even when the MCP call itself completes.&lt;/p&gt;

&lt;p&gt;You can reproduce the underlying protocol checks without a model API key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.venv/bin/python verify.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script starts its own Redis process on a free loopback port. It does not accept your database URL. It checks authentication, MCP startup, advertised tools, reads, TTL, blocked writes, unchanged data, another key prefix, missing keys, and the &lt;code&gt;SCAN&lt;/code&gt; behaviour above. It stops only the process it created and saves &lt;code&gt;validation.json&lt;/code&gt; beside the script.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Connect a dedicated Managed Redis instance
&lt;/h2&gt;

&lt;p&gt;For a shared application database, create &lt;a href="https://lizard.build/redis" rel="noopener noreferrer"&gt;Managed Redis&lt;/a&gt; in the project dashboard and follow the &lt;a href="https://lizard.build/docs/addons/redis/" rel="noopener noreferrer"&gt;connection guide&lt;/a&gt;. Use a separate learning instance for this exercise. Copy its connection URL into a private local &lt;code&gt;.env&lt;/code&gt; file under the name &lt;code&gt;ADMIN_REDIS_URL&lt;/code&gt;; never paste that administrator credential into an AI chat.&lt;/p&gt;

&lt;p&gt;Load your own trusted file and run setup from the demo directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; ./.env
&lt;span class="nb"&gt;set&lt;/span&gt; +a
.venv/bin/python setup.py
&lt;span class="nb"&gt;unset &lt;/span&gt;ADMIN_REDIS_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup script uses that credential to create the synthetic keys and reader. Its generated MCP configuration contains only the new reader credential. It extracts host, port and database from the URL, and enables TLS certificate verification for &lt;code&gt;rediss://&lt;/code&gt; endpoints. Query parameters and custom certificate paths need a separate configuration; the script stops rather than guessing them.&lt;/p&gt;

&lt;p&gt;The endpoint must be reachable from the computer running MCP. A plain &lt;code&gt;redis://&lt;/code&gt; URL has no transport encryption. Use a trusted private route or a verified TLS endpoint where available; changing the URL prefix does not add TLS support to a server. The current Managed Redis guide shows &lt;code&gt;redis://&lt;/code&gt; connections, so do not assume it supplies a public TLS endpoint.&lt;/p&gt;

&lt;p&gt;Creating an ACL user also requires the provider to permit &lt;code&gt;ACL SETUSER&lt;/code&gt;. If the provider denies it, use its supported user-management controls before connecting the agent. Do not put the administrator password into the MCP config as a workaround.&lt;/p&gt;

&lt;p&gt;Redis ACL changes made at runtime need a persistence mechanism to survive a Redis restart. The current Managed Redis startup configuration does not declare an ACL file, so treat this demo user as temporary and verify it after restarts. Do not assume AOF persistence saves ACL users. Keep the tested permissions in your setup process and review the &lt;a href="https://lizard.build/docs/platform/storage-and-recovery/" rel="noopener noreferrer"&gt;storage and recovery guide&lt;/a&gt; for the service's other limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix common Redis MCP connection errors
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MCP process fails to start&lt;/td&gt;
&lt;td&gt;Use the absolute executable path from the generated config. Confirm that the Python environment still exists.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection refused or timeout&lt;/td&gt;
&lt;td&gt;Check the explicit &lt;code&gt;--host&lt;/code&gt; and &lt;code&gt;--port&lt;/code&gt;, Redis availability and network access from the MCP computer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;WRONGPASS&lt;/code&gt; or authentication failed&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;REDIS_USERNAME&lt;/code&gt; and &lt;code&gt;REDIS_PWD&lt;/code&gt;. A password for &lt;code&gt;default&lt;/code&gt; does not authenticate &lt;code&gt;mcp_reader&lt;/code&gt;. Check whether a restart removed the temporary ACL user.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NOPERM&lt;/code&gt; or a permission error&lt;/td&gt;
&lt;td&gt;Compare the requested command and key with the ACL. A denied write, &lt;code&gt;SCAN&lt;/code&gt; or unrelated prefix is expected in this guide.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WRONGTYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;type&lt;/code&gt; first. Read strings with &lt;code&gt;get&lt;/code&gt;; read hashes with &lt;code&gt;hgetall&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing key or TTL &lt;code&gt;-2&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Check the database number, exact key name and expiry.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS certificate failure&lt;/td&gt;
&lt;td&gt;Confirm the server actually supports TLS and supply its trusted CA through the server's documented SSL options. Keep certificate checks enabled.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;JSON.GET&lt;/code&gt; or &lt;code&gt;FT.SEARCH&lt;/code&gt; is unknown&lt;/td&gt;
&lt;td&gt;Those tools need the matching Redis JSON or search capabilities. Core string/hash tools working does not prove those capabilities exist.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What to build after the connection works
&lt;/h2&gt;

&lt;p&gt;Use this setup to inspect a synthetic session, check a cache entry's lifetime or debug an agent's saved context. Tool results can enter your model conversation, so choose which data the agent may read before you connect a real application.&lt;/p&gt;

&lt;p&gt;If you want the application to save memories automatically, continue with &lt;a href="https://lizard.build/blog/redis-agent-memory" rel="noopener noreferrer"&gt;AI agent memory with Redis&lt;/a&gt;. If your agent needs relational data, use the separate reader role in &lt;a href="https://lizard.build/blog/postgres-mcp" rel="noopener noreferrer"&gt;Postgres MCP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start with &lt;a href="https://lizard.build/redis" rel="noopener noreferrer"&gt;Managed Redis&lt;/a&gt;, connect the restricted reader, and verify one successful read and one denied write before expanding the tool set.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>AI Agent Memory with Redis: Remember Users Across Sessions</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Fri, 25 Sep 2026 14:57:30 +0000</pubDate>
      <link>https://dev.to/yuraoak/ai-agent-memory-with-redis-remember-users-across-sessions-4fnc</link>
      <guid>https://dev.to/yuraoak/ai-agent-memory-with-redis-remember-users-across-sessions-4fnc</guid>
      <description>&lt;p&gt;AI agent memory with Redis lets your application save context between model calls and load it when the same user returns. The model sees that context in its next request. Redis holds the data; your application decides what to keep, retrieve and forget.&lt;/p&gt;

&lt;p&gt;In this guide, a small Python assistant remembers that Alice prefers vegetarian meals. We stop one Python process, start another with a new session ID, and ask it to recall her preference. Bob gets a separate profile. A forget command removes Alice's live Redis records.&lt;/p&gt;

&lt;p&gt;You can run the example locally, then connect the same code to &lt;a href="https://lizard.build/redis" rel="noopener noreferrer"&gt;Managed Redis&lt;/a&gt; in Lizard (lizard.build). It uses standard Redis Hash and List commands, so this walkthrough needs no vector index, RedisJSON or agent framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tested on September 25, 2026:&lt;/strong&gt; Python 3.12.10, Redis 8.8.0, &lt;code&gt;redis-py==5.2.1&lt;/code&gt; and four live calls to &lt;code&gt;openai/gpt-4.1-mini&lt;/code&gt; through OpenRouter. All 16 checks passed. The test covered separate application processes and sessions on a local Redis instance. It did not cover a hosted instance, concurrent requests or recovery from a Redis crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should an AI agent remember?
&lt;/h2&gt;

&lt;p&gt;Start with two kinds of data. They need different keys and retention rules.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;What it contains&lt;/th&gt;
&lt;th&gt;Redis type&lt;/th&gt;
&lt;th&gt;Retention in this example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User profile&lt;/td&gt;
&lt;td&gt;A preference the user explicitly saves&lt;/td&gt;
&lt;td&gt;Hash&lt;/td&gt;
&lt;td&gt;30 days after the last profile write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session history&lt;/td&gt;
&lt;td&gt;Recent user messages and assistant replies&lt;/td&gt;
&lt;td&gt;List&lt;/td&gt;
&lt;td&gt;Latest 20 messages; expires 24 hours after the last completed turn&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The profile belongs to the user, so a new session can read it. Conversation history belongs to one user and one session. Starting a second session does not copy the first session's transcript.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9d8xwaz0kyjh77rw7c0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9d8xwaz0kyjh77rw7c0m.png" alt="A Redis Hash holds Alice's preference across two sessions, while separate Lists hold each session's recent messages." width="1200" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This distinction matters when the agent answers “What do you know about me?” A recent message can disappear from a bounded history list. A saved preference has its own retention period. Neither has to stay in the model's context forever.&lt;/p&gt;

&lt;p&gt;Redis also offers a separate &lt;a href="https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/" rel="noopener noreferrer"&gt;Redis Agent Memory service&lt;/a&gt;. This tutorial builds a small memory layer in application code using a Redis connection. It does not use that service or its automatic extraction features.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Set up the demo
&lt;/h2&gt;

&lt;p&gt;You need Python 3.10 or later, a disposable Redis instance and an OpenRouter API key. Use invented preferences while testing: the saved profile and selected chat messages go to the model provider on each call.&lt;/p&gt;

&lt;p&gt;Download the complete, tested program and install its one Python dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;redis-memory-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;redis-memory-demo
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;.&lt;/span&gt; .venv/bin/activate
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;5.2.1
curl &lt;span class="nt"&gt;-fsSLo&lt;/span&gt; memory_agent.py &lt;span class="se"&gt;\&lt;/span&gt;
  https://lizard.build/blog-examples/redis-agent-memory/memory_agent.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a local test, run Redis in a separate terminal. This command binds it to loopback on port 6387; keep that terminal running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-server &lt;span class="nt"&gt;--bind&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 6387 &lt;span class="nt"&gt;--save&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;--appendonly&lt;/span&gt; no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This local Redis process is deliberately disposable. Its data remains available when the &lt;strong&gt;Python application&lt;/strong&gt; exits, but stopping Redis loses that data. The test below restarts the application, not the database.&lt;/p&gt;

&lt;p&gt;Create a private &lt;code&gt;.env&lt;/code&gt; file in the demo directory using your editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REDIS_URL=redis://127.0.0.1:6387/0
OPENROUTER_API_KEY=replace-with-your-key
OPENROUTER_MODEL=openai/gpt-4.1-mini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep &lt;code&gt;.env&lt;/code&gt; out of Git. Restrict access and load it into the current shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 .env
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; ./.env
&lt;span class="nb"&gt;set&lt;/span&gt; +a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example calls the &lt;a href="https://openrouter.ai/docs/api/reference/overview" rel="noopener noreferrer"&gt;OpenRouter Chat Completions API&lt;/a&gt;. Redis handles the same memory reads and writes if you replace the model call with another provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Give each user and session their own keys
&lt;/h2&gt;

&lt;p&gt;The program creates keys like these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agentmem:v1:{alice}:profile
agentmem:v1:{alice}:session:session-1
agentmem:v1:{alice}:session:session-2
agentmem:v1:{bob}:profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The version prefix gives future data formats a separate namespace. The user ID separates profiles. The session ID separates conversations. The braces are a Redis hash tag; they keep one user's keys in the same hash slot if you later use Redis Cluster. This guide tests a single Redis instance.&lt;/p&gt;

&lt;p&gt;The demo validates IDs before building keys. In a web application, derive the user ID from the authenticated server session. Do not accept another user's ID from a request body and treat it as proof of identity. Redis key names alone do not enforce who can access a user's data.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Save an explicit preference
&lt;/h2&gt;

&lt;p&gt;Save Alice's choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python memory_agent.py remember &lt;span class="nt"&gt;--user&lt;/span&gt; alice &lt;span class="nt"&gt;--preference&lt;/span&gt; vegetarian
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command prints &lt;code&gt;Preference saved.&lt;/code&gt; Internally, it writes a field to a &lt;a href="https://redis.io/docs/latest/develop/data-types/hashes/" rel="noopener noreferrer"&gt;Redis Hash&lt;/a&gt; and sets the profile's expiry in a transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dietary_preference&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;preference&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only allowed values are &lt;code&gt;vegetarian&lt;/code&gt;, &lt;code&gt;vegan&lt;/code&gt; and &lt;code&gt;no_preference&lt;/code&gt;. That small schema makes the example easy to inspect. The model cannot write arbitrary claims into the profile. In a real product, a “Remember this” control can make the same validated write after the user confirms it.&lt;/p&gt;

&lt;p&gt;This guide does not infer preferences from every chat message. Changing or correcting a preference uses the same explicit command, which overwrites that field and renews its 30-day TTL.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Load memory before calling the model
&lt;/h2&gt;

&lt;p&gt;Ask the assistant to recall the saved choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python memory_agent.py chat &lt;span class="nt"&gt;--user&lt;/span&gt; alice &lt;span class="nt"&gt;--session&lt;/span&gt; session-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"What dietary preference have I saved?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our test, the model replied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your saved dietary preference is vegetarian.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each call, the application reads the user's profile and the current session's recent messages. It builds the model request in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instructions that define the assistant's task.&lt;/li&gt;
&lt;li&gt;A data message containing the allowed saved preference.&lt;/li&gt;
&lt;li&gt;This session's recent user and assistant messages.&lt;/li&gt;
&lt;li&gt;The new user question.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model has no direct Redis credentials or general database tool. It only receives the selected context. After a successful reply, the app stores the new user and assistant messages together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ltrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each turn contributes two messages, so the list keeps the last ten complete turns. The app also caps each message at 2,000 characters. A message count alone would not bound context size if one message could contain an entire book.&lt;/p&gt;

&lt;p&gt;In this example, reading memory does not renew its expiry. Profile writes renew the profile TTL; completed chat turns renew the session TTL. See the &lt;a href="https://redis.io/docs/latest/commands/expire/" rel="noopener noreferrer"&gt;Redis EXPIRE reference&lt;/a&gt; for how expiry interacts with updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Start a new process and a new session
&lt;/h2&gt;

&lt;p&gt;Each CLI command starts and exits its own Python process. Run a second chat command with a different session ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python memory_agent.py chat &lt;span class="nt"&gt;--user&lt;/span&gt; alice &lt;span class="nt"&gt;--session&lt;/span&gt; session-2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"What dietary preference have I saved?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model again returned &lt;code&gt;Your saved dietary preference is vegetarian.&lt;/code&gt; The new session began without the first conversation's messages. Its request contained the shared user profile, which was enough to answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33uibuotpsamzoxccygt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33uibuotpsamzoxccygt.png" alt="After the app restarts, it loads Alice's saved preference before the model call and writes the reply to the new session." width="1200" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inspect the records the application reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python memory_agent.py inspect &lt;span class="nt"&gt;--user&lt;/span&gt; alice &lt;span class="nt"&gt;--session&lt;/span&gt; session-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a profile with &lt;code&gt;dietary_preference&lt;/code&gt; and a history with that session's user question and assistant answer. The model has not learned a new weight or gained permanent internal memory. The application supplies the stored context on each request.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Check user separation and forgetting
&lt;/h2&gt;

&lt;p&gt;First, inspect another user's context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python memory_agent.py inspect &lt;span class="nt"&gt;--user&lt;/span&gt; bob &lt;span class="nt"&gt;--session&lt;/span&gt; session-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a fresh Bob profile, the result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"history"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Asking Bob's assistant the same preference question produced &lt;code&gt;Your dietary preference is unknown.&lt;/code&gt; in our test. The empty context is the deterministic isolation check; model wording can vary.&lt;/p&gt;

&lt;p&gt;Next, stop any requests for Alice and remove her live Redis memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python memory_agent.py forget-user &lt;span class="nt"&gt;--user&lt;/span&gt; alice
python memory_agent.py inspect &lt;span class="nt"&gt;--user&lt;/span&gt; alice &lt;span class="nt"&gt;--session&lt;/span&gt; session-1
python memory_agent.py inspect &lt;span class="nt"&gt;--user&lt;/span&gt; alice &lt;span class="nt"&gt;--session&lt;/span&gt; session-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both inspections should return empty profiles and histories. The command scans only Alice's validated key prefix and unlinks matching keys. It leaves Bob's keys alone.&lt;/p&gt;

&lt;p&gt;For a deployed app, block new writes for that user while deletion runs. Otherwise, a reply that finishes during the scan could recreate a session. Deleting Redis keys also does not delete provider logs, backups or records in another database; those stores need their own retention and deletion rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we tested
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://lizard.build/blog-examples/redis-agent-memory/verify.py" rel="noopener noreferrer"&gt;verification script&lt;/a&gt; starts a new Redis process on an available loopback port. It never connects to an existing database. Its &lt;a href="https://lizard.build/blog-examples/redis-agent-memory/validation.json" rel="noopener noreferrer"&gt;saved results&lt;/a&gt; record the versions, scope and four model replies.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A saved preference survives separate Python processes&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A new session loads the profile without the old transcript&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Another user starts with an empty context&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expired profile and session keys disappear&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;History stays within 20 messages and preserves complete turns&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forgetting removes both sessions and the profile, leaving another user intact&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live model calls recall the preference before deletion and report it unknown afterward&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The harness ran 16 checks in total. These results show that the example's reads and writes work as described. They do not establish a production availability or data recovery guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect the example to Managed Redis
&lt;/h2&gt;

&lt;p&gt;After the local test, create a separate Redis instance for your project. In a linked Lizard project, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lizard add redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a deployed service named &lt;code&gt;api&lt;/code&gt;, set the connection reference and redeploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lizard secrets &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;REDIS_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'${{redis.REDIS_URL}}'&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt; api
lizard redeploy &lt;span class="nt"&gt;--service&lt;/span&gt; api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands assume the service already exists and has its model API key configured. They connect that service to Redis; the CLI demo itself is not an HTTP server. Follow the &lt;a href="https://lizard.build/docs/addons/redis/" rel="noopener noreferrer"&gt;Managed Redis connection guide&lt;/a&gt; for environment variables and access from your computer.&lt;/p&gt;

&lt;p&gt;You can inspect the demo's keys in the Redis browser in the dashboard. Keep Redis credentials on the server, and use a private connection or verified TLS when your provider supplies it. A plain &lt;code&gt;redis://&lt;/code&gt; URL does not add transport encryption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decide which memories must survive data loss
&lt;/h3&gt;

&lt;p&gt;Memory surviving an application restart does not mean it survives every Redis failure. Expiry, &lt;a href="https://redis.io/docs/latest/develop/reference/eviction/" rel="noopener noreferrer"&gt;key eviction&lt;/a&gt;, persistence settings and backups all matter.&lt;/p&gt;

&lt;p&gt;Managed Redis currently documents an &lt;code&gt;allkeys-lru&lt;/code&gt; eviction policy: memory pressure can remove any key, including a profile whose TTL has not elapsed. For preferences you cannot afford to lose, keep the source record in &lt;a href="https://lizard.build/postgres" rel="noopener noreferrer"&gt;Managed Postgres&lt;/a&gt; and use Redis for recent context or a reloadable copy. Review the &lt;a href="https://lizard.build/docs/platform/storage-and-recovery/" rel="noopener noreferrer"&gt;storage and recovery guide&lt;/a&gt; before choosing a retention policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you put this behind a web API
&lt;/h2&gt;

&lt;p&gt;The CLI keeps the example small. A shared application also needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and ownership checks.&lt;/strong&gt; Derive user IDs on the server and check that each session belongs to that user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One active turn per session.&lt;/strong&gt; Queue or lock the full read → model call → write sequence. The final Redis transaction alone does not stop two model calls from reading the same old context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A memory size budget.&lt;/strong&gt; Bound profile fields, message size, history length and the number of sessions. Monitor Redis memory and evictions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Redis failure policy.&lt;/strong&gt; Fail the request clearly or offer an explicitly stateless mode. Never claim a preference was saved when the write failed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate authority from remembered text.&lt;/strong&gt; A saved message can contain hostile instructions. Keep permissions and tool decisions in application code; a system prompt is not an access-control boundary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need a vector database for AI agent memory?
&lt;/h3&gt;

&lt;p&gt;You can load a known user's explicit preferences and recent messages by key, as this example does. Vector search becomes useful when the application must find relevant facts in a much larger set of memories. That adds embeddings, index configuration and retrieval tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Redis make an agent remember forever?
&lt;/h3&gt;

&lt;p&gt;Retention depends on TTLs, eviction, persistence and your application's writes. This example deliberately expires data. Keep durable records elsewhere when losing them would break the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is this RAG or a semantic cache?
&lt;/h3&gt;

&lt;p&gt;This example retrieves one user's saved context. RAG commonly retrieves relevant source material to help answer a question. A semantic cache reuses a prior answer for a similar query. They can share Redis infrastructure, but they need different data models and tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use LangGraph later?
&lt;/h3&gt;

&lt;p&gt;Yes. LangGraph offers checkpoints for conversation state and stores for memory across threads. Its Redis integration has its own requirements; check the &lt;a href="https://pypi.org/project/langgraph-checkpoint-redis/" rel="noopener noreferrer"&gt;LangGraph Redis package documentation&lt;/a&gt; before replacing this simple client with a framework backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give your agent a memory you can inspect
&lt;/h2&gt;

&lt;p&gt;Start with one explicit fact, a separate session history and a test that crosses a process boundary. Add expiry, ownership checks and a way to forget before you add automatic extraction or vector search.&lt;/p&gt;

&lt;p&gt;Create &lt;a href="https://lizard.build/redis" rel="noopener noreferrer"&gt;Managed Redis&lt;/a&gt; for the application's shared context. If your agent also needs to query structured project data, the &lt;a href="https://lizard.build/blog/postgres-mcp" rel="noopener noreferrer"&gt;Postgres MCP guide&lt;/a&gt; covers that connection with a separate database reader role.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Postgres MCP: connect your AI agent to a database</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Fri, 25 Sep 2026 11:40:38 +0000</pubDate>
      <link>https://dev.to/yuraoak/postgres-mcp-connect-your-ai-agent-to-a-database-pd6</link>
      <guid>https://dev.to/yuraoak/postgres-mcp-connect-your-ai-agent-to-a-database-pd6</guid>
      <description>&lt;p&gt;A Postgres MCP server lets an AI agent inspect a PostgreSQL schema and run queries through the Model Context Protocol. You give the server a database connection; your agent calls its tools to read tables and answer questions about the data.&lt;/p&gt;

&lt;p&gt;This guide connects Cursor to a sample database using Postgres MCP Pro, a separate database role and restricted access mode. The end result is easy to check: the agent should find two active projects with a combined monthly budget of $68. It should fail if it tries to change those rows.&lt;/p&gt;

&lt;p&gt;You can create the database with &lt;a href="https://lizard.build/postgres" rel="noopener noreferrer"&gt;Managed Postgres&lt;/a&gt; on Lizard (lizard.build). The MCP process runs on your computer. The same SQL setup also works with a local PostgreSQL instance you own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Postgres MCP connects to your database
&lt;/h2&gt;

&lt;p&gt;The agent sends a tool call to the MCP server. The server connects to PostgreSQL, runs the query and returns the result. PostgreSQL checks the permissions of the connection's database role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fukjz314g7ggol5fb6xqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fukjz314g7ggol5fb6xqr.png" alt="Cursor connects through Postgres MCP Pro to Managed Postgres using the mcp_reader role." width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://github.com/crystaldba/postgres-mcp" rel="noopener noreferrer"&gt;Postgres MCP Pro&lt;/a&gt;, an independent open-source project. It exposes tools for listing schemas, reading table details and executing SQL. Lizard supplies the database in this setup.&lt;/p&gt;

&lt;p&gt;The local connection between Cursor and the MCP process uses &lt;code&gt;stdio&lt;/code&gt;. Your computer must be able to reach the database endpoint. Query results can enter your AI provider's context, so this walkthrough uses invented project names and budgets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A new PostgreSQL instance or a separate disposable database, with an owner account that can create a database and a role.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;psql&lt;/code&gt; on your computer.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.astral.sh/uv/getting-started/installation/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, which runs the pinned Python package.&lt;/li&gt;
&lt;li&gt;Cursor with custom MCP servers enabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We tested the SQL and MCP calls with PostgreSQL 14.20, Python 3.12.10, &lt;code&gt;postgres-mcp==0.3.0&lt;/code&gt; and &lt;code&gt;mcp==1.30.0&lt;/code&gt;. The result table below records the scope of those checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a sample Postgres database
&lt;/h2&gt;

&lt;p&gt;In a new Lizard project, add Managed Postgres from the dashboard. If you already use Lizard CLI and have linked the new project, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lizard add postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard provides the host, port, database and credentials. Follow the &lt;a href="https://lizard.build/docs/addons/postgres/" rel="noopener noreferrer"&gt;Managed Postgres connection guide&lt;/a&gt; to connect with &lt;code&gt;psql&lt;/code&gt;. Use the owner account for this setup; the agent will receive a different account.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;psql&lt;/code&gt;, create and switch to a fresh database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;mcp_demo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;connect&lt;/span&gt; &lt;span class="n"&gt;mcp_demo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;\connect&lt;/code&gt; is a &lt;code&gt;psql&lt;/code&gt; command. If you use a SQL editor, select &lt;code&gt;mcp_demo&lt;/code&gt; before running the next block. If the database name already exists, choose another name and update the later examples.&lt;/p&gt;

&lt;p&gt;Create one table with three rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paused'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;monthly_budget_usd&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Atlas'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Beacon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Cedar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paused'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These amounts belong to the sample data. They are not Lizard prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Give the agent a role that can read the sample table
&lt;/h2&gt;

&lt;p&gt;Create a login with no administrative privileges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;mcp_reader&lt;/span&gt; &lt;span class="n"&gt;LOGIN&lt;/span&gt;
  &lt;span class="n"&gt;NOSUPERUSER&lt;/span&gt; &lt;span class="k"&gt;NOCREATEDB&lt;/span&gt; &lt;span class="n"&gt;NOCREATEROLE&lt;/span&gt; &lt;span class="n"&gt;NOREPLICATION&lt;/span&gt; &lt;span class="n"&gt;NOINHERIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run this &lt;code&gt;psql&lt;/code&gt; command to set its password without putting the password in SQL history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\password mcp_reader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following grants are for the &lt;strong&gt;fresh &lt;code&gt;mcp_demo&lt;/code&gt; database&lt;/strong&gt;. The &lt;code&gt;PUBLIC&lt;/code&gt; revocations affect other roles that use that database, so do not paste this block into an existing shared application database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;mcp_demo&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;PUBLIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;PUBLIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;mcp_demo&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;mcp_reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;mcp_reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;mcp_reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;mcp_reader&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;mcp_demo&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;default_transaction_read_only&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;mcp_reader&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;mcp_demo&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;statement_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'5s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgajss3xng52f0rj827r4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgajss3xng52f0rj827r4.png" alt="mcp_reader can read demo.projects but cannot write rows or create tables." width="799" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This grants access to one table. A table you create later needs its own grant. PostgreSQL may still expose object names through system catalogs; table permissions control access to the rows. See PostgreSQL's &lt;a href="https://www.postgresql.org/docs/current/sql-grant.html" rel="noopener noreferrer"&gt;GRANT reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The read-only default helps avoid mistakes, but a client can change that setting. The table grants are what prevent this role from writing to &lt;code&gt;demo.projects&lt;/code&gt;. We checked that an update still fails after turning the default off.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Save the reader connection outside your source code
&lt;/h2&gt;

&lt;p&gt;Build a connection URL using the new role and the &lt;code&gt;mcp_demo&lt;/code&gt; database. Keep the host, port and required TLS settings from your provider's connection instructions. Percent-encode special characters in the password when putting it in a URL; PostgreSQL documents the &lt;a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS" rel="noopener noreferrer"&gt;connection URI format&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a hosted endpoint that requires TLS, the shape is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://mcp_reader:URL_ENCODED_PASSWORD@DB_HOST:DB_PORT/mcp_demo?sslmode=require
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sslmode=require&lt;/code&gt; requires encryption. If your provider supplies a CA certificate and hostname for full certificate checks, use its &lt;code&gt;verify-full&lt;/code&gt; configuration. A certificate error needs a matching host and trust configuration; do not solve it by disabling TLS on a hosted connection.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;.env.mcp&lt;/code&gt; to your project's &lt;code&gt;.gitignore&lt;/code&gt;, then create that file at the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URI=postgresql://mcp_reader:URL_ENCODED_PASSWORD@DB_HOST:DB_PORT/mcp_demo?sslmode=require
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace every placeholder with your reader connection values. The name is &lt;strong&gt;&lt;code&gt;DATABASE_URI&lt;/code&gt;&lt;/strong&gt;: that is what Postgres MCP Pro expects. Lizard's application connection variable is named &lt;code&gt;DATABASE_URL&lt;/code&gt;; passing that name alone will not configure this MCP server.&lt;/p&gt;

&lt;p&gt;Keep the owner connection out of this file. On macOS or Linux, restrict access to the reader file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 .env.mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Configure Postgres MCP in Cursor
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in the same project. If the file already contains other servers, add &lt;code&gt;postgres-demo&lt;/code&gt; inside its existing &lt;code&gt;mcpServers&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postgres-demo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stdio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--with"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp==1.30.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--from"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgres-mcp==0.3.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"postgres-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--access-mode=restricted"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"envFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/.env.mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor supports project MCP configuration and &lt;code&gt;envFile&lt;/code&gt; for local &lt;code&gt;stdio&lt;/code&gt; servers. See its &lt;a href="https://cursor.com/docs/mcp#stdio-server-configuration" rel="noopener noreferrer"&gt;MCP configuration reference&lt;/a&gt;. If Cursor cannot find &lt;code&gt;uvx&lt;/code&gt;, replace the command with its full installed path.&lt;/p&gt;

&lt;p&gt;The two version pins matter. During our check, installing &lt;code&gt;postgres-mcp==0.3.0&lt;/code&gt; without an MCP SDK constraint selected &lt;code&gt;mcp==2.2.0&lt;/code&gt;. The server then failed to import &lt;code&gt;mcp.server.fastmcp&lt;/code&gt;. With &lt;code&gt;mcp==1.30.0&lt;/code&gt;, it started and completed the tests below.&lt;/p&gt;

&lt;p&gt;To check the package launch before opening a database connection, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvx &lt;span class="nt"&gt;--python&lt;/span&gt; 3.12 &lt;span class="nt"&gt;--with&lt;/span&gt; &lt;span class="s1"&gt;'mcp==1.30.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from&lt;/span&gt; &lt;span class="s1"&gt;'postgres-mcp==0.3.0'&lt;/span&gt; postgres-mcp &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable or restart &lt;code&gt;postgres-demo&lt;/code&gt; in Cursor's MCP settings. Leave tool approval enabled while checking the setup, and inspect the SQL arguments before allowing a call.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Verify the tools and the answer
&lt;/h2&gt;

&lt;p&gt;Start with a schema question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use postgres-demo to inspect the demo schema. List its tables and the columns
of demo.projects. Show the tool results. Do not change the database.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server should expose &lt;code&gt;list_schemas&lt;/code&gt;, &lt;code&gt;list_objects&lt;/code&gt;, &lt;code&gt;get_object_details&lt;/code&gt; and &lt;code&gt;execute_sql&lt;/code&gt;. Confirm that the agent calls the tools and reports &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;monthly_budget_usd&lt;/code&gt; from the table.&lt;/p&gt;

&lt;p&gt;Then ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using demo.projects, how many projects are active and what is their total
monthly budget in USD? Show the SQL and the database result.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A query for that answer is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;active_projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monthly_budget_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_budget_usd&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected values are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;active_projects&lt;/th&gt;
&lt;th&gt;total_budget_usd&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;68.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Finally, check the restriction on this sample table. The &lt;code&gt;WHERE false&lt;/code&gt; condition ensures the query has no matching rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use execute_sql to run exactly:
UPDATE demo.projects SET name = name WHERE false;
Report the tool response. Do not retry with another tool or connection.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our test, restricted mode returned &lt;code&gt;Error: Error validating query&lt;/code&gt;. A separate direct connection using &lt;code&gt;mcp_reader&lt;/code&gt; returned &lt;code&gt;permission denied for table projects&lt;/code&gt; even after we disabled its read-only default. The MCP check and database grants each rejected the operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we tested
&lt;/h2&gt;

&lt;p&gt;On 24 September 2026, we ran the sample SQL and real MCP calls against a new local PostgreSQL 14.20 instance with synthetic data. We used Python 3.12.10, Postgres MCP Pro 0.3.0 and MCP SDK 1.30.0.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connect as &lt;code&gt;mcp_reader&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Connected to &lt;code&gt;mcp_demo&lt;/code&gt;; read-only default on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List the schema, table and columns through MCP&lt;/td&gt;
&lt;td&gt;Returned the sample schema and table fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query the active projects directly and through MCP&lt;/td&gt;
&lt;td&gt;Both returned 2 projects and $68.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Try an update through restricted MCP mode&lt;/td&gt;
&lt;td&gt;Rejected during query validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Try an update directly with the read-only default off&lt;/td&gt;
&lt;td&gt;Rejected by PostgreSQL table permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read a table in an ungranted test schema&lt;/td&gt;
&lt;td&gt;Rejected by PostgreSQL schema permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check table creation in the public schema and temporary table privileges&lt;/td&gt;
&lt;td&gt;Neither granted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These checks cover the SQL permissions and MCP protocol. We did not run the Cursor UI flow or deploy a new Lizard database for this test. Follow the checks above against your own endpoint; a connected MCP indicator alone does not prove the database tools work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix common Postgres MCP connection errors
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;No module named mcp.server.fastmcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use the tested &lt;code&gt;mcp==1.30.0&lt;/code&gt; pin with Postgres MCP Pro 0.3.0. Restart the server after changing its arguments.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;uvx&lt;/code&gt; not found&lt;/td&gt;
&lt;td&gt;Install uv, then use the full path to &lt;code&gt;uvx&lt;/code&gt; in Cursor if needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing database URL&lt;/td&gt;
&lt;td&gt;Confirm &lt;code&gt;.env.mcp&lt;/code&gt; contains &lt;code&gt;DATABASE_URI&lt;/code&gt; and that &lt;code&gt;envFile&lt;/code&gt; points to the right project.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password authentication failed&lt;/td&gt;
&lt;td&gt;Use the password for &lt;code&gt;mcp_reader&lt;/code&gt;, check URL encoding and confirm the endpoint.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection timed out or refused&lt;/td&gt;
&lt;td&gt;Check host, port, network access and whether the database is running. A private service hostname may not resolve from your laptop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificate verification failed&lt;/td&gt;
&lt;td&gt;Match the provider's hostname, CA certificate and TLS settings.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission denied for schema or table&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;USAGE&lt;/code&gt; on the intended schema and &lt;code&gt;SELECT&lt;/code&gt; on the intended table. Grant only the access the example needs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty table list&lt;/td&gt;
&lt;td&gt;Confirm the database name and schema. This guide puts the table in &lt;code&gt;demo&lt;/code&gt;, not &lt;code&gt;public&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Do you need PostgreSQL extensions?
&lt;/h2&gt;

&lt;p&gt;The schema and data queries in this guide need no extra extension. Postgres MCP Pro also offers performance tools that have different requirements.&lt;/p&gt;

&lt;p&gt;Its top-query analysis uses &lt;code&gt;pg_stat_statements&lt;/code&gt;. Hypothetical index analysis uses &lt;code&gt;hypopg&lt;/code&gt;. Availability, server configuration and role permissions all matter; creating an extension may require an owner action or a server change. Check the project's &lt;a href="https://github.com/crystaldba/postgres-mcp#postgres-extension-installation-optional" rel="noopener noreferrer"&gt;extension requirements&lt;/a&gt; before using those tools.&lt;/p&gt;

&lt;p&gt;Start with the schema and &lt;code&gt;SELECT&lt;/code&gt; checks. An extension-related failure in a tuning tool does not, by itself, mean the basic MCP connection is broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Postgres MCP the same as Lizard MCP?
&lt;/h3&gt;

&lt;p&gt;No. This example uses Postgres MCP Pro to query a PostgreSQL database. Managed Postgres provides that database. The connector is a separate project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use a different AI agent?
&lt;/h3&gt;

&lt;p&gt;Yes, if its client supports local MCP servers over &lt;code&gt;stdio&lt;/code&gt;. Use the same pinned process, reader credentials and restricted mode, then follow that client's configuration format. The JSON above is for Cursor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does restricted mode replace database permissions?
&lt;/h3&gt;

&lt;p&gt;Use both. Restricted mode checks queries in the MCP server. PostgreSQL's grants limit what the connection role can do even through another client. Keep the owner account for migrations and administration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will it create tables or run migrations for me?
&lt;/h3&gt;

&lt;p&gt;This setup grants the reader access to the sample table. It cannot create or change your application's schema. Run reviewed migrations with your normal application deployment process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect the database to your app next
&lt;/h2&gt;

&lt;p&gt;Once the agent can inspect the sample schema and return the expected answer, you have a working basis for database questions during development. Keep the agent's reader credentials separate from your application's credentials as you add tables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://lizard.build/postgres" rel="noopener noreferrer"&gt;Create Managed Postgres&lt;/a&gt; for your project, follow the &lt;a href="https://lizard.build/docs/addons/postgres/" rel="noopener noreferrer"&gt;database connection guide&lt;/a&gt;, or continue with the &lt;a href="https://lizard.build/blog/deploy-cursor-app-postgres" rel="noopener noreferrer"&gt;Cursor app deployment example&lt;/a&gt;. For deploying an MCP service shared by several clients, see the separate &lt;a href="https://lizard.build/docs/guides/deploy-mcp-server/" rel="noopener noreferrer"&gt;remote MCP server guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://lizard.build/blog/postgres-mcp" rel="noopener noreferrer"&gt;Lizard&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>Welcome to the Lizard blog</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Wed, 23 Sep 2026 12:06:30 +0000</pubDate>
      <link>https://dev.to/yuraoak/welcome-to-the-lizard-blog-1hnn</link>
      <guid>https://dev.to/yuraoak/welcome-to-the-lizard-blog-1hnn</guid>
      <description>&lt;p&gt;The Lizard (lizard.build) blog helps developers turn working code into an application they can deploy, check and operate. It covers the decisions that come after the first successful local run: where the process lives, how it reaches its database, what happens to files and how to recover when something fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the task you have
&lt;/h2&gt;

&lt;p&gt;If an agent helped write your app, use the &lt;a href="https://lizard.build/blog/deploy-from-claude-code" rel="noopener noreferrer"&gt;Claude Code deployment guide&lt;/a&gt; or &lt;a href="https://lizard.build/blog/google-antigravity-how-to-deploy-your-app-to-production" rel="noopener noreferrer"&gt;Google Antigravity guide&lt;/a&gt;. Both explain how to check the live application after deployment.&lt;/p&gt;

&lt;p&gt;For Python projects, start with &lt;a href="https://lizard.build/blog/python-app-hosting" rel="noopener noreferrer"&gt;hosting Django, Flask and FastAPI&lt;/a&gt;. If you prefer to manage a server, the &lt;a href="https://lizard.build/blog/deploy-django-app-vps" rel="noopener noreferrer"&gt;Django VPS walkthrough&lt;/a&gt; covers the process, proxy and release setup.&lt;/p&gt;

&lt;p&gt;If you are choosing infrastructure, compare &lt;a href="https://lizard.build/blog/best-paas-providers" rel="noopener noreferrer"&gt;PaaS providers&lt;/a&gt;, learn what &lt;a href="https://lizard.build/blog/container-as-a-service" rel="noopener noreferrer"&gt;container as a service&lt;/a&gt; includes, or assess &lt;a href="https://lizard.build/blog/kubernetes-alternatives" rel="noopener noreferrer"&gt;Kubernetes alternatives&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read our comparisons
&lt;/h2&gt;

&lt;p&gt;We build Lizard, so these articles include our product and explain where it fits. They should also help you identify when another option suits your requirements better.&lt;/p&gt;

&lt;p&gt;Prices need their billing units, plan terms and sources. A calculation should state its assumptions. A test result should explain what ran and how it was measured. A product feature should not become a promise about your application's performance or bill.&lt;/p&gt;

&lt;p&gt;Use the linked documentation to check the details that matter to your deployment. If you find a mistake or a changed product term, &lt;a href="https://lizard.build/support" rel="noopener noreferrer"&gt;contact us&lt;/a&gt; with the page and the claim. For the current command reference and setup instructions, go to the &lt;a href="https://lizard.build/docs/" rel="noopener noreferrer"&gt;Lizard documentation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://lizard.build/blog/welcome-to-the-lizard-blog" rel="noopener noreferrer"&gt;Lizard (lizard.build)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cloud</category>
    </item>
    <item>
      <title>Vercel alternatives: choosing a host for your backend</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Wed, 23 Sep 2026 12:05:49 +0000</pubDate>
      <link>https://dev.to/yuraoak/vercel-alternatives-choosing-a-host-for-your-backend-4a17</link>
      <guid>https://dev.to/yuraoak/vercel-alternatives-choosing-a-host-for-your-backend-4a17</guid>
      <description>&lt;p&gt;&lt;strong&gt;The right Vercel alternative depends on what your application runs.&lt;/strong&gt; Compare Lizard (lizard.build), Railway and Render for services that stay active between requests. Consider Cloud Run for an HTTP service that can scale down when demand falls. Keep Vercel on your shortlist when its frontend workflow and request-driven compute fit your app.&lt;/p&gt;

&lt;p&gt;This comparison comes from Lizard. We checked the linked product documentation and billing terms on 9 September 2026. The calculations below are examples with stated inputs, not performance tests or customer savings figures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the runtime you need
&lt;/h2&gt;

&lt;p&gt;A Dockerfile describes how to package an application. It does not tell you how a hosting service schedules, stops or bills that application.&lt;/p&gt;

&lt;p&gt;Vercel now supports &lt;a href="https://vercel.com/docs/functions/container-images" rel="noopener noreferrer"&gt;container images for Functions&lt;/a&gt;. Its &lt;a href="https://vercel.com/docs/services/pricing" rel="noopener noreferrer"&gt;Services documentation&lt;/a&gt; says Services use Function compute and inherit Function limits. Check the current duration, filesystem and connection limits for the specific feature and plan you intend to use.&lt;/p&gt;

&lt;p&gt;For a conventional queue consumer, a process that keeps a connection open, or a scheduler that runs outside HTTP requests, test that lifecycle before moving. A managed queue or workflow can solve the same business problem, but it can also require changes to your code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your requirement&lt;/th&gt;
&lt;th&gt;Options to evaluate&lt;/th&gt;
&lt;th&gt;What to test first&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend previews and a request-driven backend&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;Framework behaviour, cache rules and production limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API plus a separate queue consumer&lt;/td&gt;
&lt;td&gt;Lizard, Railway, Render&lt;/td&gt;
&lt;td&gt;Worker restarts, retries and database access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An HTTP container with uneven traffic&lt;/td&gt;
&lt;td&gt;Cloud Run&lt;/td&gt;
&lt;td&gt;Cold starts, concurrency and minimum instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct control of a server&lt;/td&gt;
&lt;td&gt;A VPS with a deployment tool&lt;/td&gt;
&lt;td&gt;Patching, backups and your response to failures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are starting points for a trial. None establishes a universal winner. Our &lt;a href="https://lizard.build/blog/best-paas-providers" rel="noopener noreferrer"&gt;PaaS comparison&lt;/a&gt; covers the broader choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Lizard fits
&lt;/h2&gt;

&lt;p&gt;Lizard runs web services and workers, offers &lt;a href="https://lizard.build/postgres" rel="noopener noreferrer"&gt;Managed Postgres&lt;/a&gt;, &lt;a href="https://lizard.build/redis" rel="noopener noreferrer"&gt;Managed Redis&lt;/a&gt; and &lt;a href="https://lizard.build/object-storage" rel="noopener noreferrer"&gt;Managed Object Storage&lt;/a&gt;, and exposes deployment and operations through the &lt;a href="https://lizard.build/cli" rel="noopener noreferrer"&gt;Lizard CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That fits a backend with a familiar process model: start the API, run the worker separately, and give each service its own configuration. A coding agent can read structured deployment output, inspect logs and run a specific command inside a service. It still needs to check the result; a successful build alone does not prove that logins, jobs and database writes work.&lt;/p&gt;

&lt;p&gt;Provisioning a database and connecting your application are separate steps. Your application must read the connection variable you configure. Keep app data in the database or an attached Persistent Volume, according to the service's needs. A writable container filesystem by itself is not a backup or a promise of durability across deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Vercel is a better fit
&lt;/h2&gt;

&lt;p&gt;Stay with Vercel when your team depends on its preview workflow, frontend tooling or managed request lifecycle and those features already meet your needs. Replacing a working deployment system has a cost even if another provider advertises a lower CPU rate.&lt;/p&gt;

&lt;p&gt;An application with little traffic can also benefit from compute that does not remain active between requests. A running Lizard service can still consume memory while waiting for work. Compare the actual workload and latency requirements, including whether you are willing to accept cold starts.&lt;/p&gt;

&lt;p&gt;You can keep the frontend on Vercel and move only the worker or API. In that design, include cross-provider data transfer, authentication and request latency in the test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare bills after fees, credits and allowances
&lt;/h2&gt;

&lt;p&gt;The monthly total depends on more than runtime CPU. Record the plan, billing period, region, seats, database, storage, requests, traffic and builds. Apply each allowance to its own meter and each credit once.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Billing term&lt;/th&gt;
&lt;th&gt;Lizard&lt;/th&gt;
&lt;th&gt;Vercel Pro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base plan&lt;/td&gt;
&lt;td&gt;No monthly subscription&lt;/td&gt;
&lt;td&gt;$20/month with one deploying seat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Included usage credit&lt;/td&gt;
&lt;td&gt;$10 trial credit, valid for 31 days; purchased credits do not expire&lt;/td&gt;
&lt;td&gt;$20 per team, not per seat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Additional deploying seats&lt;/td&gt;
&lt;td&gt;No separate seat charge&lt;/td&gt;
&lt;td&gt;$20/month each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internet transfer&lt;/td&gt;
&lt;td&gt;$0.045/GB from the first byte&lt;/td&gt;
&lt;td&gt;Current Pro terms include a Flat Rate CDN tier with 1 million CDN requests and 1 TB of data transfer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosted builds&lt;/td&gt;
&lt;td&gt;Builds are billed&lt;/td&gt;
&lt;td&gt;Paid build machines bill by CPU-minute&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sources: &lt;a href="https://lizard.build/pricing" rel="noopener noreferrer"&gt;Lizard pricing&lt;/a&gt;, &lt;a href="https://vercel.com/docs/plans/pro-plan" rel="noopener noreferrer"&gt;Vercel Pro terms&lt;/a&gt; and &lt;a href="https://vercel.com/docs/builds/managing-builds" rel="noopener noreferrer"&gt;Vercel build billing&lt;/a&gt;. CDN transfer, origin transfer and Function invocations are distinct Vercel meters. Check the terms your account uses before reusing an older estimate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosted build costs: Lizard and Vercel
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lizard charges for builds.&lt;/strong&gt; Include those charges when comparing deployment costs.&lt;/p&gt;

&lt;p&gt;For a paid Vercel Standard machine with 4 vCPUs, 200 builds at exactly 3 billable minutes each use 2,400 CPU-minutes. At $0.0035 per CPU-minute, that is &lt;strong&gt;$8.40 before plan credit and tax&lt;/strong&gt;, or $0.042 per build. &lt;a href="https://vercel.com/docs/builds/managing-builds" rel="noopener noreferrer"&gt;Vercel build rates, checked 15 September 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flizard.build%2Fblog-images%2Fpricing%2Fvercel-standard-build-cost.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flizard.build%2Fblog-images%2Fpricing%2Fvercel-standard-build-cost.svg" title="Vercel build charges only: $8.40 for 200 builds at 3 billable minutes and 4 vCPUs each, before applicable plan credit and tax. Lizard builds also incur charges." alt="Vercel Standard build calculation: 200 builds × 3 minutes × 4 vCPUs = 2,400 CPU-minutes; 2,400 × $0.0035 per CPU-minute = $8.40 before plan credit and tax." width="720" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vercel credits can cover some or all of this usage, so $8.40 is not necessarily an extra payment on top of the plan. Basic builds are included on eligible Vercel Hobby projects. This is a Vercel build-cost example, not a build-speed comparison or the full monthly bill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where no subscription lowers the bill
&lt;/h3&gt;

&lt;p&gt;Lizard charges 200 GB of outgoing traffic at &lt;code&gt;200 × $0.045 = $9&lt;/code&gt;. There is no bundled traffic allowance to subtract. Vercel Pro includes a CDN allowance, so compare that meter separately from Function and origin traffic.&lt;/p&gt;

&lt;p&gt;For an illustrative small app running for 720 hours, assume average measured use of 0.01 vCPU and 0.1 GB of memory, plus 10 GB of outgoing traffic. No database, retained storage or paid add-ons are included. At &lt;a href="https://lizard.build/pricing" rel="noopener noreferrer"&gt;Lizard rates&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flizard.build%2Fblog-images%2Fpricing%2Flizard-small-app-monthly-cost.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flizard.build%2Fblog-images%2Fpricing%2Flizard-small-app-monthly-cost.svg" title="720-hour example at average measured CPU and memory usage. CPU: $0.0250128 per vCPU-hour; memory: $0.0125064 per GB RAM-hour; egress: $0.045 per GB transferred. Resource total: $1.53055296, rounded to $1.53. Before payment fees and tax; excludes databases, retained storage and add-ons." alt="Lizard monthly resource cost: CPU 0.01 vCPU × 720 h × $0.0250128/vCPU-hour ≈ $0.18; memory 0.1 GB RAM × 720 h × $0.0125064/GB RAM-hour ≈ $0.90; egress 10 GB transferred × $0.045/GB transferred = $0.45. Total ≈ $1.53." width="720" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lizard costs less for this small-app example than the $20 Vercel Pro monthly minimum&lt;/strong&gt;, provided Lizard top-up fees allocated to this usage stay below the $18.47 difference. Three deploying seats raise that Pro minimum to $60. These are paid-plan comparisons before tax and trial credits; Vercel Hobby can cost $0 for an eligible project. We do not assume identical resource consumption across the two runtimes. &lt;a href="https://vercel.com/docs/plans/pro-plan" rel="noopener noreferrer"&gt;Vercel Pro terms, checked 15 September 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Purchased credits pay for usage and do not expire. A top-up can exceed this month’s usage because the unused balance stays available; the top-up screen shows its fee and total charge.&lt;/p&gt;

&lt;p&gt;This example shows a specific price advantage from having no monthly minimum. For a larger app, compare measured resource use, included CDN traffic and required features on both hosts before estimating savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move one service before moving the whole app
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;List Vercel-specific dependencies: request handlers, image processing, cache behaviour, scheduled work, storage and environment variables.&lt;/li&gt;
&lt;li&gt;Choose an API or worker that can run independently. Give it a production start command and a health endpoint where appropriate.&lt;/li&gt;
&lt;li&gt;Deploy a test copy and connect it to a separate test database. Configure credentials through service settings.&lt;/li&gt;
&lt;li&gt;Test authentication, retries, timeouts, file handling and restarts with representative traffic.&lt;/li&gt;
&lt;li&gt;Measure cost and latency, then plan the database and domain cutover. Keep a rollback path until you have checked writes on the new deployment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a GitHub repository, start with the &lt;a href="https://lizard.build/docs/guides/github-integration/" rel="noopener noreferrer"&gt;Git deployment guide&lt;/a&gt;. For a local project, use the &lt;a href="https://lizard.build/docs/deploy/upload/" rel="noopener noreferrer"&gt;upload deployment guide&lt;/a&gt;. The &lt;a href="https://lizard.build/docs/cli/config/" rel="noopener noreferrer"&gt;CLI configuration reference&lt;/a&gt; explains how to keep deployment settings repeatable.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can Vercel run a Dockerfile?&lt;/strong&gt; Yes. Vercel documents container-image support for Functions. Check the runtime limits as well as the packaging support before treating it as a replacement for an always-running backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Railway billed as a flat server tier?&lt;/strong&gt; No. Railway meters resource consumption and applies the paid plan's subscription fee toward usage. Its billing model differs from buying a fixed instance size. See the &lt;a href="https://docs.railway.com/pricing" rel="noopener noreferrer"&gt;Railway pricing documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I keep Vercel for the frontend?&lt;/strong&gt; Yes. A separate API or worker can run elsewhere. Test cross-origin requests, cookie settings, authentication and network costs as part of that split.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which option is cheapest?&lt;/strong&gt; For the small app priced above, Lizard’s resource cost is about $1.53 plus payment fees, below Vercel Pro’s $20 monthly minimum. Lizard also has no seat fee. Vercel Hobby may be free for an eligible project, and included CDN traffic can change the result for a larger app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should an AI coding agent verify after deployment?&lt;/strong&gt; The live URL, health checks, logs and a real user flow. For a backend, also check database writes, worker execution and recovery after a restart. The &lt;a href="https://lizard.build/blog/deploy-from-claude-code" rel="noopener noreferrer"&gt;Claude Code deployment guide&lt;/a&gt; shows that workflow.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://lizard.build/blog/vercel-alternative" rel="noopener noreferrer"&gt;Lizard (lizard.build)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cloud</category>
    </item>
    <item>
      <title>Deploy from Claude Code: a practical Lizard workflow</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Wed, 23 Sep 2026 12:04:59 +0000</pubDate>
      <link>https://dev.to/yuraoak/deploy-from-claude-code-a-practical-lizard-workflow-5a5e</link>
      <guid>https://dev.to/yuraoak/deploy-from-claude-code-a-practical-lizard-workflow-5a5e</guid>
      <description>&lt;p&gt;&lt;strong&gt;Claude Code can deploy an application by using a hosting provider's tools in your development environment.&lt;/strong&gt; With Lizard (lizard.build), the agent can inspect the project, use the Lizard CLI to deploy, configure dependencies and check the live service. The host runs the application after the coding session ends.&lt;/p&gt;

&lt;p&gt;The useful result is a working user flow at a live URL, not just a command that exits successfully. This guide checks the Lizard CLI and linked documentation as of 9 September 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the agent the right context
&lt;/h2&gt;

&lt;p&gt;Start with a repository that builds and has a production start command. Identify the workspace and project that should own it, whether it needs a database or worker, and whether the deploy creates a test copy or changes an existing service.&lt;/p&gt;

&lt;p&gt;Install the CLI and read its version-matched instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @lizard-build/cli
lizard skills get core &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard status &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If authentication is needed, use &lt;code&gt;lizard login&lt;/code&gt; and finish the browser sign-in. For the reusable agent instructions, follow the &lt;a href="https://github.com/lizard-build/skill" rel="noopener noreferrer"&gt;Lizard Skill repository&lt;/a&gt;. Claude Code's own &lt;a href="https://code.claude.com/docs/en/overview" rel="noopener noreferrer"&gt;overview&lt;/a&gt; describes how it works with a codebase and tools.&lt;/p&gt;

&lt;p&gt;The agent should inspect existing configuration before adding anything. A runtime version, Dockerfile or start command already in the project is better evidence than a command guessed from the framework name.&lt;/p&gt;

&lt;h2&gt;
  
  
  A prompt that defines the deployment task
&lt;/h2&gt;

&lt;p&gt;Give the agent a bounded instruction such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deploy this repository to the existing Lizard project &lt;code&gt;my-project&lt;/code&gt; as a test service. Read the current Lizard CLI guide and inspect the build and start commands first. Use the GitHub source if the repository has an accessible GitHub remote. Configure the services this app actually needs. Verify the live URL, logs and one real application flow. Report the deployed revision and any check that failed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Replace the project and environment with the intended target. State explicitly when a production service or domain should change. The agent should not infer a production database migration from a request to publish a test copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Git deployment or source upload
&lt;/h2&gt;

&lt;p&gt;Use the repository's GitHub integration when you want later commits to trigger deployments. The &lt;a href="https://lizard.build/docs/guides/github-integration/" rel="noopener noreferrer"&gt;GitHub deployment guide&lt;/a&gt; covers connection and service setup. Check the tracked branch and root directory for a monorepo.&lt;/p&gt;

&lt;p&gt;For a local project without a suitable GitHub remote, source upload is available. After selecting the right workspace, a new example project and service can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lizard init &lt;span class="nt"&gt;--name&lt;/span&gt; my-project
lizard add &lt;span class="nt"&gt;--service&lt;/span&gt; api
lizard up &lt;span class="nt"&gt;--service&lt;/span&gt; api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These names are examples. For an existing project, confirm the current link with &lt;code&gt;lizard status --json&lt;/code&gt; and select the existing service. Do not create another project or switch a Git-backed service to upload merely to avoid reading its settings.&lt;/p&gt;

&lt;p&gt;Lizard performs the hosted build. Depending on the project and service configuration, it can use the repository's Dockerfile or generate one through the supported build path. Read the &lt;a href="https://lizard.build/docs/deploy/" rel="noopener noreferrer"&gt;deployment docs&lt;/a&gt; and keep the settings in the &lt;a href="https://lizard.build/docs/cli/config/" rel="noopener noreferrer"&gt;configuration reference&lt;/a&gt; repeatable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect the database deliberately
&lt;/h2&gt;

&lt;p&gt;Creating Managed Postgres does not prove the application can reach it. The application must read the connection variable you set. For a project with a service named &lt;code&gt;api&lt;/code&gt; and its first PostgreSQL add-on named &lt;code&gt;postgres&lt;/code&gt;, the reference is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lizard add postgres
lizard secrets &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="s1"&gt;'DATABASE_URL=${{postgres.DATABASE_URL}}'&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt; api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the add-on's actual name if it differs. Configure each consumer service, including workers, with the variables it needs. Run migrations as a controlled release step and confirm an application read and write against the intended database. See &lt;a href="https://lizard.build/docs/variables/references/" rel="noopener noreferrer"&gt;variable references&lt;/a&gt; and &lt;a href="https://lizard.build/postgres" rel="noopener noreferrer"&gt;Managed Postgres&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For uploads, choose storage that survives the lifecycle your app needs. For a worker, check that it can reach the broker and handle restarts. Avoid treating the successful creation of resources as a completed application setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the result from the outside
&lt;/h2&gt;

&lt;p&gt;Use structured output for diagnostics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lizard status &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard logs &lt;span class="nt"&gt;--service&lt;/span&gt; api &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard logs &lt;span class="nt"&gt;--service&lt;/span&gt; api &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
lizard metrics &lt;span class="nt"&gt;--service&lt;/span&gt; api &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then fetch the live URL and exercise a real flow. For a small API, that might be authentication followed by a write and read. For a frontend, check a route reached directly by URL as well as through client-side navigation.&lt;/p&gt;

&lt;p&gt;If deployment fails, use the reported error to choose the next check:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;First checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build fails&lt;/td&gt;
&lt;td&gt;Dependency lockfile, runtime version and build command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process exits&lt;/td&gt;
&lt;td&gt;Start command, missing configuration and import errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health check fails&lt;/td&gt;
&lt;td&gt;Bound interface, port and startup time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page loads but actions fail&lt;/td&gt;
&lt;td&gt;API URL, auth, database and callback settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files disappear after a change&lt;/td&gt;
&lt;td&gt;Storage location and persistence guarantees&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An agent should report an unresolved error rather than declare success from the build log. The &lt;a href="https://lizard.build/blog/python-app-hosting" rel="noopener noreferrer"&gt;Python hosting guide&lt;/a&gt; gives examples of start-command problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the cost of the workflow
&lt;/h2&gt;

&lt;p&gt;Your coding subscription and hosting bill pay for different parts of the work. Lizard uses pay as you go with no monthly subscription. Its &lt;a href="https://lizard.build/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; lists resource rates and payment terms. A test service, database or sandbox can continue consuming resources after the coding session ends.&lt;/p&gt;

&lt;p&gt;Set resource limits and review use. Stop or remove temporary resources when they are no longer needed through your normal approval process. Do not assume that closing Claude Code stops an application that has already deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does Claude Code host the app?&lt;/strong&gt; The deployment target does. Claude Code uses tools to prepare and deploy the code; the hosting provider runs the resulting application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I deploy an existing GitHub repository?&lt;/strong&gt; Yes, if Lizard can access it. Configure the repository, branch and service through the Git deployment workflow, then verify the deployed revision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do databases connect automatically?&lt;/strong&gt; Provision the database, configure the variable the application reads, and test the connection. Those are separate checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can the same workflow work with another coding agent?&lt;/strong&gt; An agent that can use the required tools can follow the same deployment checks. Read its own permission and tool setup rather than assuming every environment behaves like Claude Code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I know the deployment is complete?&lt;/strong&gt; Confirm the live URL, expected revision, healthy process and meaningful application behaviour. A successful build alone is not enough.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://lizard.build/blog/deploy-from-claude-code" rel="noopener noreferrer"&gt;Lizard (lizard.build)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Firebase alternatives: match the backend you need</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Wed, 23 Sep 2026 12:04:17 +0000</pubDate>
      <link>https://dev.to/yuraoak/firebase-alternatives-match-the-backend-you-need-3hde</link>
      <guid>https://dev.to/yuraoak/firebase-alternatives-match-the-backend-you-need-3hde</guid>
      <description>&lt;p&gt;&lt;strong&gt;Choose a Firebase alternative by the Firebase services you need to replace.&lt;/strong&gt; Supabase and Nhost belong on a shortlist for a PostgreSQL-based backend. Appwrite offers an integrated backend product. PocketBase can suit a small self-hosted project. Hosting your own API on Lizard (lizard.build) is another approach, but it does not automatically replace Firebase Authentication, security rules or realtime client behaviour.&lt;/p&gt;

&lt;p&gt;This comparison comes from Lizard. We checked the linked documentation on 9 September 2026. Start with architecture and migration effort, then compare the bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firebase is more than a database
&lt;/h2&gt;

&lt;p&gt;An app may use Firestore, Realtime Database, Authentication, Storage, Functions, Hosting and messaging in different combinations. Each has its own API and cost model. &lt;a href="https://firebase.google.com/pricing" rel="noopener noreferrer"&gt;Firebase pricing&lt;/a&gt; lists those products separately.&lt;/p&gt;

&lt;p&gt;Exporting documents solves only part of a migration. The client may depend on realtime listeners, offline behaviour and security rules that the new system expresses differently. Authentication also includes identity providers, account recovery and sessions, not just a users table.&lt;/p&gt;

&lt;p&gt;Make a list of SDK calls in your application. Use it to define the replacement contract before picking a vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  A shortlist by backend model
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Why to evaluate it&lt;/th&gt;
&lt;th&gt;What needs review&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Supabase&lt;/td&gt;
&lt;td&gt;PostgreSQL with an integrated backend workflow&lt;/td&gt;
&lt;td&gt;SQL schema, access policies and client API changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Appwrite&lt;/td&gt;
&lt;td&gt;Auth, databases, storage, functions and realtime features&lt;/td&gt;
&lt;td&gt;API compatibility and cloud versus self-hosted operation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Convex&lt;/td&gt;
&lt;td&gt;A reactive backend approach&lt;/td&gt;
&lt;td&gt;Its data and function model compared with your existing code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PocketBase&lt;/td&gt;
&lt;td&gt;An embedded SQLite backend in a compact application&lt;/td&gt;
&lt;td&gt;Production requirements, upgrades and recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nhost&lt;/td&gt;
&lt;td&gt;Database, GraphQL, auth and storage in one backend product&lt;/td&gt;
&lt;td&gt;GraphQL model, permissions and deployment requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Amplify&lt;/td&gt;
&lt;td&gt;An application backend built around AWS services&lt;/td&gt;
&lt;td&gt;AWS identity, resources and the resulting bill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your own API and PostgreSQL&lt;/td&gt;
&lt;td&gt;Control over the server-side application&lt;/td&gt;
&lt;td&gt;Building auth, authorization and realtime behaviour you need&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the primary product documentation for &lt;a href="https://supabase.com/docs" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;, &lt;a href="https://appwrite.io/docs" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt;, &lt;a href="https://docs.convex.dev/" rel="noopener noreferrer"&gt;Convex&lt;/a&gt;, &lt;a href="https://docs.nhost.io/" rel="noopener noreferrer"&gt;Nhost&lt;/a&gt; and &lt;a href="https://docs.amplify.aws/" rel="noopener noreferrer"&gt;Amplify&lt;/a&gt; before treating a feature label as equivalent behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  When PostgreSQL is the reason to move
&lt;/h2&gt;

&lt;p&gt;A relational database can fit data that benefits from joins, transactions and explicit constraints. It also changes how you model and query documents that were designed for Firestore.&lt;/p&gt;

&lt;p&gt;Start with the queries your app must answer. Map collections, nested values and identifiers to a schema, then check reads and writes with real data. Do not assume a mechanical conversion of every document produces a useful relational model.&lt;/p&gt;

&lt;p&gt;If you choose your own API, &lt;a href="https://lizard.build/postgres" rel="noopener noreferrer"&gt;Managed Postgres&lt;/a&gt; provides the database on Lizard. Your application still implements its endpoints and access checks. Supabase or Nhost may be a closer fit when you want an integrated backend API rather than building one yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  When self-hosting is the reason to move
&lt;/h2&gt;

&lt;p&gt;Self-hosting gives you control over deployment and data placement. It also makes backups, upgrades, monitoring and recovery part of your operating plan.&lt;/p&gt;

&lt;p&gt;PocketBase combines an embedded SQLite database with auth, file handling and realtime features. Its documentation warns that backward compatibility is not guaranteed before version 1.0 and advises caution for production-critical applications. Evaluate that stated limit against your project rather than presenting a small binary as an automatic production replacement. &lt;a href="https://pocketbase.io/docs/" rel="noopener noreferrer"&gt;PocketBase documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For any self-hosted backend, test restoration from backup and the upgrade path before depending on it. A process that starts successfully has not yet demonstrated data recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the complete cost model
&lt;/h2&gt;

&lt;p&gt;Firestore billing can include document operations, storage and network use. Another product may charge by database compute, users, function usage or a base plan. Those units measure different things. &lt;a href="https://firebase.google.com/docs/firestore/pricing" rel="noopener noreferrer"&gt;Firestore billing documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Record the same workload for each option: active users, reads, writes, stored data, file downloads, function execution and required environments. Add the plan you need for backups or other essential features. Use &lt;a href="https://firebase.google.com/pricing" rel="noopener noreferrer"&gt;Firebase&lt;/a&gt;, &lt;a href="https://supabase.com/pricing" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;, &lt;a href="https://appwrite.io/pricing" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt; and &lt;a href="https://lizard.build/pricing" rel="noopener noreferrer"&gt;Lizard pricing&lt;/a&gt; as applicable.&lt;/p&gt;

&lt;p&gt;Avoid calculating savings by comparing a database's entry plan with the whole Firebase bill. Also include development work: replacing client SDK calls and security rules can cost more than a small monthly hosting difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  A migration plan that tests permissions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Inventory the Firebase products, SDK calls, security rules and identity providers in use.&lt;/li&gt;
&lt;li&gt;Design the replacement data model and the access rules for each user role.&lt;/li&gt;
&lt;li&gt;Import a test dataset. Check counts, identifiers, timestamps and representative application queries.&lt;/li&gt;
&lt;li&gt;Test access as an anonymous user, a normal user and an administrator. Include denied reads and writes.&lt;/li&gt;
&lt;li&gt;Test sign-in, sign-out, password recovery, file access and realtime updates where used.&lt;/li&gt;
&lt;li&gt;Plan the final write transition, client release and rollback. Old mobile or browser clients may continue calling the old API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On Lizard, a custom backend can use &lt;a href="https://lizard.build/redis" rel="noopener noreferrer"&gt;Managed Redis&lt;/a&gt; where appropriate and &lt;a href="https://lizard.build/object-storage" rel="noopener noreferrer"&gt;Managed Object Storage&lt;/a&gt; for files. Storage access policy must match the sensitivity of those files; do not assume a newly provisioned bucket is private.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Lizard a direct Firebase replacement?&lt;/strong&gt; Lizard can host your backend and data services. It does not automatically supply the Firebase client API, auth flows or security rules. Choose it when you want to run that server-side application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which alternative is closest to Firebase?&lt;/strong&gt; It depends on which Firebase products you use. Compare actual APIs, auth, file access and realtime behaviour rather than selecting by a feature count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I keep Firebase Authentication and move the database?&lt;/strong&gt; You can design a backend that verifies the existing identity tokens. Check token verification and authorization carefully; accepting an identity is not the same as granting access to every record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will PostgreSQL remove all usage charges?&lt;/strong&gt; No. The host still charges under its own plan for compute, storage, traffic or other services. The meter changes; the need to estimate the workload remains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the most important migration test?&lt;/strong&gt; Confirm that users can access exactly the records and files they should, and no others. Data import success alone does not prove correct permissions.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://lizard.build/blog/firebase-alternatives" rel="noopener noreferrer"&gt;Lizard (lizard.build)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cloud</category>
    </item>
    <item>
      <title>Python app hosting: deploy Django, Flask and FastAPI</title>
      <dc:creator>Yura Oak</dc:creator>
      <pubDate>Wed, 23 Sep 2026 12:03:21 +0000</pubDate>
      <link>https://dev.to/yuraoak/python-app-hosting-deploy-django-flask-and-fastapi-40j5</link>
      <guid>https://dev.to/yuraoak/python-app-hosting-deploy-django-flask-and-fastapi-40j5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Python app hosting starts with the process your application needs to run.&lt;/strong&gt; Django and Flask commonly use a WSGI server; FastAPI uses an ASGI server. A Celery worker or a scheduled script has another lifecycle. Choose a host that supports those processes, the database and the storage your app needs.&lt;/p&gt;

&lt;p&gt;Lizard (lizard.build) publishes this guide. We checked the linked framework and provider documentation on 9 September 2026. The examples use explicit module names that you must adapt to your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the runtime before the host
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;Production process&lt;/th&gt;
&lt;th&gt;Other requirements to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Django with WSGI&lt;/td&gt;
&lt;td&gt;Gunicorn or another supported WSGI server&lt;/td&gt;
&lt;td&gt;Database, migrations, static files and uploads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Django with async features&lt;/td&gt;
&lt;td&gt;A supported ASGI server&lt;/td&gt;
&lt;td&gt;Connection handling and compatibility of the app's dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flask&lt;/td&gt;
&lt;td&gt;A production WSGI server&lt;/td&gt;
&lt;td&gt;App import path or factory, secrets and database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FastAPI&lt;/td&gt;
&lt;td&gt;Uvicorn or another ASGI server&lt;/td&gt;
&lt;td&gt;Startup tasks, concurrency and database connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Celery worker&lt;/td&gt;
&lt;td&gt;A separate queue consumer&lt;/td&gt;
&lt;td&gt;Broker, result backend if used, retries and shutdown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script or batch job&lt;/td&gt;
&lt;td&gt;A process that runs and exits&lt;/td&gt;
&lt;td&gt;Schedule, timeout, exit status and durable output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The development server is for development. See the official &lt;a href="https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/" rel="noopener noreferrer"&gt;Django deployment guide&lt;/a&gt;, &lt;a href="https://flask.palletsprojects.com/en/stable/deploying/" rel="noopener noreferrer"&gt;Flask production guide&lt;/a&gt; and &lt;a href="https://fastapi.tiangolo.com/deployment/server-workers/" rel="noopener noreferrer"&gt;FastAPI worker guidance&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python hosting options by need
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Why to consider it&lt;/th&gt;
&lt;th&gt;Check before choosing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lizard&lt;/td&gt;
&lt;td&gt;Web services and workers with managed data services and CLI operations&lt;/td&gt;
&lt;td&gt;Runtime settings, database wiring and measured resource use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Railway&lt;/td&gt;
&lt;td&gt;Several services in a project&lt;/td&gt;
&lt;td&gt;All service consumption and plan credit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render&lt;/td&gt;
&lt;td&gt;Published web and worker instance plans&lt;/td&gt;
&lt;td&gt;Separate database cost and free-service restrictions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PythonAnywhere&lt;/td&gt;
&lt;td&gt;A Python-focused hosting workflow&lt;/td&gt;
&lt;td&gt;WSGI/ASGI support, outbound access and task limits on your plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Run&lt;/td&gt;
&lt;td&gt;HTTP services, jobs or worker pools&lt;/td&gt;
&lt;td&gt;Resource type, billing mode, concurrency and cold starts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fly.io&lt;/td&gt;
&lt;td&gt;Machines in selected regions&lt;/td&gt;
&lt;td&gt;Machine sizing, storage and network charges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DigitalOcean App Platform&lt;/td&gt;
&lt;td&gt;Managed source or image deployment&lt;/td&gt;
&lt;td&gt;Build support, app components and database pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heroku&lt;/td&gt;
&lt;td&gt;A familiar Python deployment workflow&lt;/td&gt;
&lt;td&gt;Current plan, add-ons and product direction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A VPS&lt;/td&gt;
&lt;td&gt;Direct server control&lt;/td&gt;
&lt;td&gt;Updates, process management, TLS, backups and recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use the &lt;a href="https://lizard.build/blog/best-paas-providers" rel="noopener noreferrer"&gt;PaaS comparison&lt;/a&gt; for the wider hosting decision. A Python badge in a feature table is not enough to confirm worker or database support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Python hosting needs a workload limit
&lt;/h2&gt;

&lt;p&gt;A free plan may restrict outbound requests, sleep an inactive web process, limit task execution or include only a trial credit. Those terms can be fine for a demonstration and unsuitable for a webhook receiver or queue worker.&lt;/p&gt;

&lt;p&gt;Check &lt;a href="https://render.com/docs/free" rel="noopener noreferrer"&gt;Render's free-service rules&lt;/a&gt; and &lt;a href="https://help.pythonanywhere.com/pages/FreeAccountsFeatures/" rel="noopener noreferrer"&gt;PythonAnywhere's free-account features&lt;/a&gt;. For Cloud Run, the &lt;a href="https://cloud.google.com/run/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; describes free usage alongside billable resources. A database, image registry or network usage may remain outside the allowance you are looking at.&lt;/p&gt;

&lt;p&gt;Test the first request after a quiet period and confirm whether scheduled or background work still runs. Describe the free option in terms of those limits, not as unlimited hosting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build and start commands
&lt;/h2&gt;

&lt;p&gt;For a project that uses &lt;code&gt;requirements.txt&lt;/code&gt;, install its pinned dependencies with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the package manager and lockfile already in your repository if it uses uv, Poetry or another tool. Include the production server in the dependencies. Do not rely on a package installed only on your laptop.&lt;/p&gt;

&lt;p&gt;For Django, where &lt;code&gt;myproject/wsgi.py&lt;/code&gt; defines the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gunicorn myproject.wsgi:application &lt;span class="nt"&gt;--bind&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;3000&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Flask, where &lt;code&gt;app.py&lt;/code&gt; exports &lt;code&gt;app&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gunicorn app:app &lt;span class="nt"&gt;--bind&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;3000&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For FastAPI, where &lt;code&gt;main.py&lt;/code&gt; exports &lt;code&gt;app&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvicorn main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;3000&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands expect a shell to expand the port variable. A JSON-array Docker &lt;code&gt;CMD&lt;/code&gt; does not expand it automatically. Either use the runtime's documented variable support, a shell wrapper, or a small program that reads the environment.&lt;/p&gt;

&lt;p&gt;Set worker counts from measurements and available memory. More processes can use more memory and database connections; adding workers is not a substitute for checking a slow query or blocking task.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal FastAPI container
&lt;/h2&gt;

&lt;p&gt;For an application with &lt;code&gt;main.py&lt;/code&gt; and a locked &lt;code&gt;requirements.txt&lt;/code&gt; containing FastAPI and Uvicorn, this Dockerfile starts one server process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;--create-home&lt;/span&gt; appuser
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["sh", "-c", "exec uvicorn main:app --host 0.0.0.0 --port ${PORT:-3000}"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose a Python version compatible with your project. Put &lt;code&gt;.env&lt;/code&gt;, local virtual environments, caches and Git data in &lt;code&gt;.dockerignore&lt;/code&gt;. This example does not install extra operating-system packages; add only those your dependencies need.&lt;/p&gt;

&lt;p&gt;On Lizard, you can bring a Dockerfile or use the source-build path. The &lt;a href="https://lizard.build/docs/deploy/" rel="noopener noreferrer"&gt;deployment guide&lt;/a&gt; explains the options. Your app should expose a small health endpoint, and you should also test an endpoint that exercises its real dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  A FastAPI deployment checked on Lizard
&lt;/h2&gt;

&lt;p&gt;Our &lt;a href="https://github.com/lizard-build/fastapi-example" rel="noopener noreferrer"&gt;public FastAPI example&lt;/a&gt; has a dated deployment record from 9 September 2026. The test used &lt;a href="https://github.com/lizard-build/fastapi-example/tree/df522c15f2771704ec2ab28aadda47293d6ea3b1" rel="noopener noreferrer"&gt;commit &lt;code&gt;df522c1&lt;/code&gt;&lt;/a&gt;, built from GitHub in &lt;code&gt;eu-west-lim-a&lt;/code&gt;, with port 8000 and a Uvicorn start command in the Procfile. It used Python 3.13 with FastAPI 0.141.1 and Uvicorn 0.52.4. No manual build or start override was set.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/lizard-build/fastapi-example/blob/main/deployment-checks/2026-09-09.json" rel="noopener noreferrer"&gt;published check record&lt;/a&gt; reports six passing checks through the public HTTPS URL:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Observed result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 200 with &lt;code&gt;{"status":"ok"}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 200; API interface uses &lt;code&gt;/openapi.json&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /openapi.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 200; schema contains health and echo routes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;POST /echo&lt;/code&gt; with a JSON object&lt;/td&gt;
&lt;td&gt;HTTP 200 with the same object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;POST /echo&lt;/code&gt; with a JSON array&lt;/td&gt;
&lt;td&gt;HTTP 422 for the invalid body type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /no-such-route&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 404&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Open the &lt;a href="https://crawl-timber-nt5k.eu-west-lim-a.onlizard.com/health" rel="noopener noreferrer"&gt;live health endpoint&lt;/a&gt;, try the &lt;a href="https://crawl-timber-nt5k.eu-west-lim-a.onlizard.com/docs" rel="noopener noreferrer"&gt;API interface&lt;/a&gt;, or follow the &lt;a href="https://lizard.build/docs/framework-guides/fastapi/" rel="noopener noreferrer"&gt;FastAPI deployment guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These results cover deployment and HTTP behaviour for a small app without a database. They do not measure uptime, load capacity or end-to-end deployment time. The same record gives a CLI reading at 11:48 UTC of about 0.034 GB of memory and about $0.00051/hour in resource cost. That is a reading at one moment, not a monthly invoice; the quoted reading used the rates in effect at the time. Current Lizard hosting uses pay as you go with no monthly subscription; storage, traffic and payment fees can add to the total. The Dockerfile above is a separate example and does not reproduce that deployment's exact configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django, Postgres and Celery need separate checks
&lt;/h2&gt;

&lt;p&gt;Connect &lt;a href="https://lizard.build/postgres" rel="noopener noreferrer"&gt;Managed Postgres&lt;/a&gt; through the variable your Django settings read. Run migrations as a controlled release step, not independently in every web worker. Configure static-file handling and give uploaded files durable storage.&lt;/p&gt;

&lt;p&gt;Run Celery as a separate service with the same relevant application code and its own start command. Configure the broker explicitly, for example with &lt;a href="https://lizard.build/redis" rel="noopener noreferrer"&gt;Managed Redis&lt;/a&gt; if your application uses Redis. Check retries, duplicate-task handling and shutdown before accepting production work.&lt;/p&gt;

&lt;p&gt;For a server you operate yourself, use the &lt;a href="https://lizard.build/blog/deploy-django-app-vps" rel="noopener noreferrer"&gt;Django VPS walkthrough&lt;/a&gt;. For a managed worker, see the &lt;a href="https://lizard.build/docs/deploy/workers/" rel="noopener noreferrer"&gt;worker deployment reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estimate the whole Python application
&lt;/h2&gt;

&lt;p&gt;Include web processes, workers, database, stored files, backups, transfer and the plan your team needs. Railway meters consumption and applies its paid plan toward usage. Render publishes instance plans. PythonAnywhere currently lists Developer at $10/month; check its included features against your app. Sources: &lt;a href="https://docs.railway.com/pricing" rel="noopener noreferrer"&gt;Railway&lt;/a&gt;, &lt;a href="https://render.com/pricing" rel="noopener noreferrer"&gt;Render&lt;/a&gt;, &lt;a href="https://www.pythonanywhere.com/pricing/" rel="noopener noreferrer"&gt;PythonAnywhere&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lizard has no monthly subscription or per-service plan fee. For a small app averaging 0.01 vCPU and 0.1 GB of memory over 720 hours, with 10 GB of egress, resource charges total about $1.53 before payment fees and tax. Add the database, worker and storage your Python app needs. See the &lt;a href="https://lizard.build/blog/vercel-alternative#where-no-subscription-lowers-the-bill" rel="noopener noreferrer"&gt;worked example&lt;/a&gt; and &lt;a href="https://lizard.build/pricing" rel="noopener noreferrer"&gt;current rates&lt;/a&gt;. Purchased credits do not expire, so a quiet month does not require buying another plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I host FastAPI on the same services as Django?&lt;/strong&gt; Often, but FastAPI needs an ASGI server. Confirm the host supports your start command and any long-lived connections or workers the app uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I use &lt;code&gt;runserver&lt;/code&gt; in production?&lt;/strong&gt; No. Use a production WSGI or ASGI server and check the framework's deployment guidance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does the host say my app is unhealthy?&lt;/strong&gt; Check the build logs, process exit status, import path, bound interface and expected port. Then check missing variables and dependency connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a Dockerfile?&lt;/strong&gt; Not on every host. A source builder can create the image, but you still need correct dependencies and a production start command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the best host for a Python worker?&lt;/strong&gt; One that supports the worker's lifecycle and dependencies. Test queue consumption, retries and restart behaviour; an HTTP-only deployment is not enough.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://lizard.build/blog/python-app-hosting" rel="noopener noreferrer"&gt;Lizard (lizard.build)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
    </item>
  </channel>
</rss>
