<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Lizard]]></title><description><![CDATA[Lizard]]></description><link>https://lizard-build.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Lizard</title><link>https://lizard-build.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 30 Sep 2026 14:10:29 GMT</lastBuildDate><atom:link href="https://lizard-build.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Codex Exec: Automate Deployment Checks from the Terminal]]></title><description><![CDATA[Originally published on Lizard (lizard.build).
codex exec 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 ]]></description><link>https://lizard-build.hashnode.dev/codex-exec-automate-deployment-checks-from-the-terminal</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/codex-exec-automate-deployment-checks-from-the-terminal</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Mon, 28 Sep 2026 11:02:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/9665787a-2931-4eb3-8142-73ccade00417.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Originally published on <a href="https://lizard.build/blog/codex-exec">Lizard (lizard.build)</a>.</p>
<p><code>codex exec</code> 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.</p>
<p>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.</p>
<h2>Run Codex without the interactive interface</h2>
<p>From a trusted Git repository, run:</p>
<pre><code class="language-bash">codex exec --sandbox read-only \
  "Summarize this repository in three sentences. Do not change files."
</code></pre>
<p>Codex writes progress to stderr and its final answer to stdout. You can redirect that answer to a file. The <a href="https://learn.chatgpt.com/docs/non-interactive-mode">official non-interactive guide</a> covers this behavior and the supported automation patterns.</p>
<p>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.</p>
<img src="https://lizard.build/blog-images/codex-exec/workflow.png" alt="Codex exec: HTTP evidence, JSONL events and a validated final report" style="display:block;margin:0 auto" />

<h2>Understand the three output files</h2>
<p>These files have different roles:</p>
<table>
<thead>
<tr>
<th>File</th>
<th>Contains</th>
<th>How to read it</th>
</tr>
</thead>
<tbody><tr>
<td><code>events.jsonl</code></td>
<td>Run events from <code>--json</code>, one JSON object per line</td>
<td>Parse each nonempty line separately</td>
</tr>
<tr>
<td><code>report.json</code></td>
<td>Final answer written by <code>-o</code>, shaped by <code>--output-schema</code></td>
<td>Parse one JSON object</td>
</tr>
<tr>
<td><code>stderr.log</code></td>
<td>Diagnostics from the CLI process</td>
<td>Keep it for troubleshooting</td>
</tr>
</tbody></table>
<p><code>--json</code> changes stdout into an event stream. It does not make the whole stream a single report object. <code>--output-schema</code> describes the final answer, and <code>-o</code> saves that answer separately. This distinction prevents a common automation bug: trying to parse the entire event log with one JSON read.</p>
<h2>Prepare the example</h2>
<p>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.</p>
<pre><code class="language-bash">codex --version
codex login status
mkdir codex-deployment-check
cd codex-deployment-check
for file in demo.py collect.py gate.py run.py report.schema.json verify.py; do
  curl --fail --silent --show-error \
    "https://lizard.build/blog-examples/agent-deployment-checks/$file" \
    --output "$file"
done
</code></pre>
<p>Read the downloaded files before executing them. Start the demo and keep it running:</p>
<pre><code class="language-bash">python3 demo.py --port 8787
</code></pre>
<p>The application has two GET routes. <code>/healthz</code> returns <code>status: ok</code>. <code>/api/quote?quantity=3</code> 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.</p>
<p>Open another terminal in the same folder for the remaining commands.</p>
<h2>Collect and inspect the HTTP evidence</h2>
<pre><code class="language-bash">python3 collect.py http://127.0.0.1:8787 &gt; evidence.json
</code></pre>
<p>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.</p>
<p>For your application, replace the demo paths and expected values in both <code>collect.py</code> and <code>gate.py</code>. 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.</p>
<p>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.</p>
<h2>Request a structured report</h2>
<p>From a trusted Git repository containing the files, run:</p>
<pre><code class="language-bash">codex exec --ignore-user-config --ephemeral \
  --sandbox read-only \
  --json \
  --output-schema report.schema.json \
  -o report.json \
  "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; evidence.json &gt; events.jsonl 2&gt; stderr.log
</code></pre>
<p>If you use only the new download folder, add <code>--skip-git-repo-check</code>. The complete wrapper does so in a temporary directory made for the report. Use that flag deliberately when no repository is needed.</p>
<p><code>--ignore-user-config</code> skips the user's main Codex configuration file while retaining normal authentication. <code>--ephemeral</code> prevents saving the session rollout. The shell still saves the three explicit output files above. <code>--sandbox read-only</code> limits model-generated commands; the shell's redirects write the artifacts.</p>
<p>The schema requires <code>verdict</code>, <code>failed_checks</code>, <code>summary</code> and <code>next_step</code>, 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.</p>
<h2>Parse the stream and the final answer separately</h2>
<p>In our live test, Codex emitted these event types in order:</p>
<pre><code class="language-text">thread.started
turn.started
item.completed
turn.completed
</code></pre>
<p>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.</p>
<p>The wrapper reads each event and rejects <code>turn.failed</code> or <code>error</code>. It also requires <code>turn.completed</code>, 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.</p>
<p>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.</p>
<img src="https://lizard.build/blog-images/codex-exec/outputs.png" alt="Codex events, final report and diagnostics saved to three separate files" style="display:block;margin:0 auto" />

<h2>Run the complete workflow</h2>
<pre><code class="language-bash">python3 run.py codex http://127.0.0.1:8787 runs/healthy
</code></pre>
<p>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.</p>
<p>To reproduce the failure, start a second demo in another terminal:</p>
<pre><code class="language-bash">python3 demo.py --port 8788 --broken
</code></pre>
<p>Then run the same workflow against that instance:</p>
<pre><code class="language-bash">python3 run.py codex http://127.0.0.1:8788 runs/broken
</code></pre>
<p>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.</p>
<h2>Results from the working example</h2>
<p>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.</p>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Health route</th>
<th>Quote route</th>
<th>Codex report</th>
<th>Wrapper exit</th>
</tr>
</thead>
<tbody><tr>
<td>Working demo</td>
<td>200, expected body</td>
<td>200, $36.00 (3,600 cents)</td>
<td><code>pass</code>, no failed checks</td>
<td><code>0</code></td>
</tr>
<tr>
<td>Broken quote</td>
<td>200, expected body</td>
<td>503</td>
<td><code>fail</code>, <code>quote</code> failed</td>
<td><code>1</code></td>
</tr>
</tbody></table>
<p>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.</p>
<p>The local validation tests also reject a false success report and incomplete evidence. Run them without a model call:</p>
<pre><code class="language-bash">python3 verify.py
</code></pre>
<p>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.</p>
<h2>Add Lizard service state and logs</h2>
<p>For an application on Lizard, confirm the target before interpreting failures:</p>
<pre><code class="language-bash">lizard skills get core --json
lizard status --json
lizard ps --project YOUR_PROJECT --json
lizard logs --project YOUR_PROJECT --service YOUR_SERVICE --tail 100 --json
</code></pre>
<p>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.</p>
<p>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.</p>
<p>If you need to deploy first, start with the <a href="https://lizard.build/docs/guides/deploy-from-coding-agent/">coding-agent deployment guide</a>. For apps with data dependencies, the <a href="https://lizard.build/blog/postgres-mcp">Postgres MCP tutorial</a> and <a href="https://lizard.build/blog/redis-mcp">Redis MCP tutorial</a> explain scoped access for inspecting test data.</p>
<h2>Use the result in automation</h2>
<p>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.</p>
<p>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.</p>
<p>On your own trusted machine, <code>codex exec</code> can reuse the saved CLI login. For GitHub Actions, follow the <a href="https://learn.chatgpt.com/docs/github-action">official Codex action guide</a> 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.</p>
<p>Use fresh evidence for each independent check. <code>codex exec resume</code> can continue a conversation, but it is not needed for this one-shot report. The example's ephemeral mode makes each run independent.</p>
<h2>Troubleshooting</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>What to inspect</th>
</tr>
</thead>
<tbody><tr>
<td>Not inside a Git repository</td>
<td>Run from the intended repository, or deliberately use <code>--skip-git-repo-check</code> for the isolated report folder</td>
</tr>
<tr>
<td>JSON parser reports extra data</td>
<td>Parse <code>events.jsonl</code> one line at a time; parse <code>report.json</code> as one object</td>
</tr>
<tr>
<td>No final report</td>
<td>Check the process exit, stderr and failure events</td>
</tr>
<tr>
<td>Wrapper exits 1 although Codex exited 0</td>
<td>The application check failed; inspect <code>failed_checks</code> and evidence</td>
</tr>
<tr>
<td>Wrapper exits 2</td>
<td>Check authentication, timeout, schema, contradictory output or an existing output directory</td>
</tr>
<tr>
<td>A hosted endpoint redirects</td>
<td>Verify the route and required authentication; this collector rejects redirects</td>
</tr>
</tbody></table>
<h2>Where to go next</h2>
<p>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.</p>
<p>If your team uses Claude Code, the <a href="https://lizard.build/blog/claude-code-headless">Claude Code headless guide</a> shows the same HTTP checks with its result envelope. To run the application itself, use <a href="https://lizard.build/docs/cli/">Lizard CLI</a> and add the report after your deployment step.</p>
]]></content:encoded></item><item><title><![CDATA[Claude Code Headless: Check Your Deployment from the Terminal]]></title><description><![CDATA[Originally published on Lizard (lizard.build).
Claude Code headless mode runs a prompt from your terminal with claude -p and exits when the task finishes. You can pipe in data, save the answer and cal]]></description><link>https://lizard-build.hashnode.dev/claude-code-headless-check-your-deployment-from-the-terminal</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/claude-code-headless-check-your-deployment-from-the-terminal</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Mon, 28 Sep 2026 10:48:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/00a8fa5d-5133-4735-9706-d158f9d2465b.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Originally published on <a href="https://lizard.build/blog/claude-code-headless">Lizard (lizard.build)</a>.</p>
<p>Claude Code headless mode runs a prompt from your terminal with <code>claude -p</code> 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.</p>
<p>The example catches a common failure: <code>/healthz</code> 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.</p>
<h2>Start with one non-interactive command</h2>
<p>With Claude Code installed and signed in, run:</p>
<pre><code class="language-bash">claude -p "Explain what an HTTP 503 response tells me in two sentences." \
  --tools ""
</code></pre>
<p><code>-p</code> means print the result and exit. <code>--tools ""</code> disables the built-in tools for this question. Claude can answer from the prompt without reading your repository or running a shell command. The <a href="https://code.claude.com/docs/en/cli-reference">Claude Code CLI reference</a> lists the available flags.</p>
<p>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.</p>
<p><img src="https://lizard.build/blog-images/claude-code-headless/workflow.png" alt="Claude Code headless: HTTP checks, structured report and result validation" /></p>
<h2>What you need</h2>
<p>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.</p>
<p>Check the installed CLI and its authentication state:</p>
<pre><code class="language-bash">claude --version
claude auth status
</code></pre>
<p>If a request fails because the saved session has expired, run <code>claude auth login</code> and retry. A saved login can exist even when the next API request cannot refresh it.</p>
<p>For the optional hosted checks, install <a href="https://lizard.build/docs/cli/">Lizard CLI</a> and sign in to your own project. If you still need to deploy your application, follow <a href="https://lizard.build/blog/deploy-from-claude-code">Deploy from Claude Code</a> first.</p>
<h2>Download the example and start the demo</h2>
<p>Create a new folder and download the six files. Read them before running them. The demo accepts GET requests and keeps no customer data.</p>
<pre><code class="language-bash">mkdir claude-deployment-check
cd claude-deployment-check
for file in demo.py collect.py gate.py run.py report.schema.json verify.py; do
  curl --fail --silent --show-error \
    "https://lizard.build/blog-examples/agent-deployment-checks/$file" \
    --output "$file"
done
python3 demo.py --port 8787
</code></pre>
<p>Leave that terminal running. In another terminal, change to the same folder. The demo exposes <code>/healthz</code> and <code>/api/quote?quantity=3</code>. Each item costs 1,200 cents; a quote for three items must return <code>currency: USD</code> and <code>total_cents: 3600</code>.</p>
<p>These are demo routes. For your own application, edit the paths and expected fields in <code>collect.py</code> and <code>gate.py</code>. 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.</p>
<h2>Collect evidence before asking Claude</h2>
<pre><code class="language-bash">python3 collect.py http://127.0.0.1:8787 &gt; evidence.json
</code></pre>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2>Ask for a structured report</h2>
<p>Run this from the folder containing the downloaded files:</p>
<pre><code class="language-bash">claude --safe-mode -p \
  "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." \
  --tools "" \
  --no-session-persistence \
  --output-format json \
  --json-schema "$(cat report.schema.json)" \
  &lt; evidence.json &gt; claude-result.json
</code></pre>
<p>The example uses <code>--safe-mode</code> 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.</p>
<p>Claude's response has an outer result object. When you supply the schema, the report sits inside <code>structured_output</code>. The outer object also carries run metadata and an <code>is_error</code> field. A plain JSON response and a schema-constrained response serve different purposes; parse the field your command requested.</p>
<p>The schema asks for four fields:</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Purpose</th>
</tr>
</thead>
<tbody><tr>
<td><code>verdict</code></td>
<td><code>pass</code> or <code>fail</code>, copied from the checks</td>
</tr>
<tr>
<td><code>failed_checks</code></td>
<td>Names of the checks that failed</td>
</tr>
<tr>
<td><code>summary</code></td>
<td>A short explanation of the observed result</td>
</tr>
<tr>
<td><code>next_step</code></td>
<td>One concrete follow-up</td>
</tr>
</tbody></table>
<p>The <a href="https://code.claude.com/docs/en/headless">programmatic execution documentation</a> 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.</p>
<h2>Run the complete check</h2>
<p><code>run.py</code> collects fresh evidence, calls Claude with a 180-second process timeout, extracts the report and checks it. Give each run a new output directory:</p>
<pre><code class="language-bash">python3 run.py claude http://127.0.0.1:8787 runs/healthy
</code></pre>
<p>The directory contains <code>evidence.json</code>, <code>claude-result.json</code>, <code>report.json</code> and <code>stderr.log</code>. Keep the raw result when debugging a failure; an authentication error can appear in the result on stdout.</p>
<p>The wrapper uses three exit codes:</p>
<table>
<thead>
<tr>
<th>Exit code</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>0</code></td>
<td>Both application checks passed and the report agreed</td>
</tr>
<tr>
<td><code>1</code></td>
<td>At least one application check failed and the report agreed</td>
</tr>
<tr>
<td><code>2</code></td>
<td>The reporting job failed, timed out or returned invalid or contradictory output</td>
</tr>
</tbody></table>
<p>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.</p>
<h2>Reproduce a failure that a health check misses</h2>
<p>Start a second demo in another terminal:</p>
<pre><code class="language-bash">python3 demo.py --port 8788 --broken
</code></pre>
<p>Then run:</p>
<pre><code class="language-bash">python3 run.py claude http://127.0.0.1:8788 runs/broken
</code></pre>
<p>The second demo still answers <code>/healthz</code> with HTTP 200. Its quote route returns HTTP 503. The evidence therefore records <code>fail</code>, with <code>quote</code> as the failed check. With a valid Claude response, the wrapper exits 1.</p>
<p><img src="https://lizard.build/blog-images/claude-code-headless/failure.png" alt="A health endpoint returns 200 while the quote route returns 503, so the deployment check fails" /></p>
<p>You can also test the HTTP checks and validation logic without calling a model:</p>
<pre><code class="language-bash">python3 verify.py
</code></pre>
<p>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.</p>
<h2>Apply the check to an application on Lizard</h2>
<p>Choose the application URL from your project. Inspect the current project context and service state with Lizard CLI:</p>
<pre><code class="language-bash">lizard skills get core --json
lizard status --json
lizard ps --project YOUR_PROJECT --json
lizard logs --project YOUR_PROJECT --service YOUR_SERVICE --tail 100 --json
</code></pre>
<p><code>lizard status</code> shows the local folder's link. <code>lizard ps</code> reads the service state for the selected project. JSON logs return a bounded snapshot and exit; they do not keep following the service.</p>
<p>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 <code>0.0.0.0</code> and configure the service's port to match. The local examples bind to <code>127.0.0.1</code>.</p>
<p>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.</p>
<p>For applications that depend on stored data, add a test with a known record. The <a href="https://lizard.build/blog/postgres-mcp">Postgres MCP guide</a> explains scoped database access, and the <a href="https://lizard.build/blog/redis-mcp">Redis MCP guide</a> covers restricted Redis reads. Use test data and credentials suited to the check.</p>
<h2>Headless mode, authentication and unattended jobs</h2>
<p>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.</p>
<p>Claude also offers <code>--bare</code>, which skips much of the normal startup context. Its Anthropic authentication path uses <code>ANTHROPIC_API_KEY</code> or a configured API key helper; it does not read subscription OAuth credentials. Our account-login example therefore uses <code>--safe-mode</code>. Review the current documentation before moving the job to CI, where the authentication setup may differ.</p>
<p>For live progress, Claude supports <code>--output-format stream-json --verbose</code>. Each line is an event. Use plain <code>json</code> 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.</p>
<h2>Troubleshooting</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Check next</th>
</tr>
</thead>
<tbody><tr>
<td>OAuth session expired</td>
<td>Run <code>claude auth login</code>, then retry the actual request</td>
</tr>
<tr>
<td>The report file contains an error</td>
<td>Inspect the Claude exit code and outer <code>is_error</code> field</td>
</tr>
<tr>
<td><code>structured_output</code> is missing</td>
<td>Confirm both schema and JSON output flags reached the CLI</td>
</tr>
<tr>
<td>A tool waits for approval</td>
<td>Decide which operation the job needs; this report task disables built-in tools</td>
</tr>
<tr>
<td><code>/healthz</code> passes but the wrapper exits 1</td>
<td>Inspect the quote check and logs at the recorded time</td>
</tr>
<tr>
<td>The wrapper exits 2</td>
<td>Inspect stderr, raw result and schema; use a new output directory on retry</td>
</tr>
<tr>
<td>A hosted route redirects to a login page</td>
<td>Use the correct test endpoint and its intended authentication; the demo checker rejects redirects</td>
</tr>
</tbody></table>
<h2>Test scope and next step</h2>
<p>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.</p>
<p>For the same pattern with JSONL events and a separate final report file, see <a href="https://lizard.build/blog/codex-exec">Codex Exec deployment checks</a>. To put your own app online, follow the <a href="https://lizard.build/blog/deploy-from-claude-code">Claude Code deployment guide</a>, then add a check for the operation your users need most.</p>
]]></content:encoded></item><item><title><![CDATA[Redis MCP: Connect Your AI Agent to Your Database]]></title><description><![CDATA[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 redis-mcp-server, give it a Redis address and a restricted crede]]></description><link>https://lizard-build.hashnode.dev/redis-mcp-connect-your-ai-agent-to-your-database</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/redis-mcp-connect-your-ai-agent-to-your-database</guid><category><![CDATA[Redis]]></category><category><![CDATA[Python]]></category><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Sat, 26 Sep 2026 20:34:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/d0b69ec7-7e91-4865-a83f-2c53a165a5f3.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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 <code>redis-mcp-server</code>, give it a Redis address and a restricted credential, then register that process in Cursor or Claude Desktop.</p>
<p>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 <a href="https://lizard.build/redis">Managed Redis</a> instance in Lizard (lizard.build).</p>
<p><strong>Tested September 27, 2026, Dubai time:</strong> Python 3.12.10, Redis 8.8.0, <code>redis-mcp-server</code> 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.</p>
<h2>Choose the Redis MCP server that accesses data</h2>
<p>The <a href="https://redis.io/docs/latest/integrate/redis-mcp/">official Redis MCP server</a> 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.</p>
<p>There are several Redis tools with MCP in their names:</p>
<table>
<thead>
<tr>
<th>Tool</th>
<th>What it connects to</th>
<th>Use it for</th>
</tr>
</thead>
<tbody><tr>
<td><code>redis/mcp-redis</code>, packaged as <code>redis-mcp-server</code></td>
<td>Your Redis database</td>
<td>Reading or changing application data</td>
</tr>
<tr>
<td>Redis documentation MCP at <code>redis.io/mcp</code></td>
<td>Redis documentation</td>
<td>Looking up commands and examples</td>
</tr>
<tr>
<td>Redis Cloud MCP</td>
<td>The Redis Cloud management API</td>
<td>Managing Redis Cloud resources</td>
</tr>
</tbody></table>
<p>We use the first one. A documentation connection will not give your agent access to your keys. Redis describes the distinction in its <a href="https://redis.io/docs/latest/develop/setup/build-with-an-agent/">agent setup guide</a>.</p>
<img src="https://lizard.build/blog-images/redis-mcp/redis-mcp-connection.png" alt="Cursor or Claude Desktop connects to Redis through a local MCP server and a reader credential." style="display:block;margin:0 auto" />

<p>The MCP process runs on the same computer as your client and communicates through standard input and output, or <strong>stdio</strong>. 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.</p>
<h2>1. Prepare a small Redis database</h2>
<p>Use a dedicated learning instance with synthetic data. You need Python 3.10 or later, <a href="https://docs.astral.sh/uv/getting-started/installation/">uv</a>, and access to a Redis server. The local route also needs <code>redis-server</code> on your PATH.</p>
<p>Download these files into a new demo directory:</p>
<ul>
<li><p><a href="https://lizard.build/blog-examples/redis-mcp/requirements.txt">requirements.txt</a>: the pinned Python packages.</p>
</li>
<li><p><a href="https://lizard.build/blog-examples/redis-mcp/setup.py">setup.py</a>: creates two keys and a restricted reader, then writes the MCP configuration.</p>
</li>
<li><p><a href="https://lizard.build/blog-examples/redis-mcp/verify.py">verify.py</a>: starts its own local Redis process and tests the MCP connection.</p>
</li>
<li><p><a href="https://lizard.build/blog-examples/redis-mcp/validation.json">validation.json</a>: the results from this guide's test run.</p>
</li>
</ul>
<p>On macOS or Linux, create the Python environment:</p>
<pre><code class="language-bash">uv venv .venv --python 3.12
uv pip install --python .venv/bin/python -r requirements.txt
</code></pre>
<p>In a separate terminal, start a temporary Redis instance:</p>
<pre><code class="language-bash">redis-server --bind 127.0.0.1 --port 6391 --save "" --appendonly no
</code></pre>
<p>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 <code>ADMIN_REDIS_URL</code> to match before running setup.</p>
<p>In your demo directory, run:</p>
<pre><code class="language-bash">.venv/bin/python setup.py
</code></pre>
<p>The setup script connects to <code>redis://127.0.0.1:6391/0</code> by default. It creates:</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Type</th>
<th>Value</th>
<th>Initial TTL</th>
</tr>
</thead>
<tbody><tr>
<td><code>mcpdemo:status</code></td>
<td>String</td>
<td><code>ready</code></td>
<td>3,600 seconds</td>
</tr>
<tr>
<td><code>mcpdemo:session:42</code></td>
<td>Hash</td>
<td><code>user=demo-user</code>, <code>language=english</code></td>
<td>3,600 seconds</td>
</tr>
</tbody></table>
<p>It also creates the user <code>mcp_reader</code> 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.</p>
<p>The script saves <code>mcp.local.json</code> 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 <code>.gitignore</code> before committing anything:</p>
<pre><code class="language-gitignore">.venv/
.env
mcp.local.json
.cursor/mcp.json
test-runs/
</code></pre>
<p>On Windows, use <code>.venv\Scripts\python.exe</code> for the Python commands. The script chooses the matching executable path for the generated config. This guide's automated run used macOS.</p>
<h2>2. Understand the reader's permissions</h2>
<p>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:</p>
<pre><code class="language-text">ACL SETUSER mcp_reader reset on &gt;REPLACE_WITH_RANDOM_PASSWORD ~mcpdemo:* -@all +ping +get +hget +hgetall +type +ttl
</code></pre>
<p>The rules permit reads of known strings and hashes under <code>mcpdemo:*</code>, plus type and TTL checks. They deny writes, administration commands, pub/sub and key enumeration. The <a href="https://redis.io/docs/latest/commands/acl-setuser/">Redis ACL reference</a> explains each rule.</p>
<p><strong>A read-only prompt does not enforce read-only access.</strong> 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.</p>
<img src="https://lizard.build/blog-images/redis-mcp/redis-mcp-permissions.png" alt="Redis allows reads and TTL checks for demo keys; writes, other prefixes and SCAN are denied." style="display:block;margin:0 auto" />

<h3>Why the policy does not allow SCAN</h3>
<p>An ACL key pattern restricts access to key values. It does not make <code>SCAN</code> return only the key names under that pattern. In our test, granting <code>+scan</code> let <code>mcp_reader</code> discover the name <code>private:sentinel</code>, even though it could not read that key's value. Revoking <code>SCAN</code> blocked enumeration again.</p>
<p>Start with known demo keys. If you later allow browsing on a separate instance that holds no unrelated data, use <code>scan_keys</code> in small iterations and follow the returned cursor until it becomes zero. <code>COUNT</code> is a work hint, not a hard result limit. See the <a href="https://redis.io/docs/latest/commands/scan/">SCAN reference</a>. The tutorial's default policy deliberately rejects both <code>scan_keys</code> and <code>scan_all_keys</code>.</p>
<h2>3. Add Redis MCP to Cursor or Claude Desktop</h2>
<p>Open the generated <code>mcp.local.json</code> locally. It has this shape:</p>
<pre><code class="language-json">{
  "mcpServers": {
    "redis-demo": {
      "command": "/ABSOLUTE/PATH/redis-mcp-demo/.venv/bin/redis-mcp-server",
      "args": ["--host", "127.0.0.1", "--port", "6391", "--db", "0"],
      "env": {
        "REDIS_USERNAME": "mcp_reader",
        "REDIS_PWD": "YOUR_GENERATED_READER_PASSWORD"
      }
    }
  }
}
</code></pre>
<p>Use the actual generated file, not the placeholders above. Merge the <code>redis-demo</code> entry into your client's existing <code>mcpServers</code> object; keep any other servers already there.</p>
<p><strong>Cursor:</strong> use <code>.cursor/mcp.json</code> in the demo project, or <code>~/.cursor/mcp.json</code> 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 <a href="https://cursor.com/help/customization/mcp">MCP integration guide</a>.</p>
<p><strong>Claude Desktop:</strong> merge the entry into <code>claude_desktop_config.json</code>. On macOS that file lives under <code>~/Library/Application Support/Claude/</code>. Restart the app after saving. Follow the <a href="https://redis.io/docs/latest/integrate/redis-mcp/client-conf/">Redis client configuration guide</a> for the current client steps.</p>
<p>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 <code>REDIS_HOST</code> alone can therefore leave the process trying <code>127.0.0.1</code>. The username and password in the configuration above use the supported <code>REDIS_USERNAME</code> and <code>REDIS_PWD</code> variables.</p>
<h2>4. Prove that the connection works</h2>
<p>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.</p>
<pre><code class="language-text">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.
</code></pre>
<p>The expected results are:</p>
<ul>
<li><p><code>get</code> returns <code>ready</code>.</p>
</li>
<li><p><code>hgetall</code> returns the two synthetic fields.</p>
</li>
<li><p><code>type</code> returns <code>hash</code> and a positive TTL below 3,600 seconds.</p>
</li>
</ul>
<p>The official <code>type</code> tool includes TTL in its response. In our run it returned:</p>
<pre><code class="language-json">{
  "key": "mcpdemo:session:42",
  "type": "hash",
  "ttl": 3591
}
</code></pre>
<p>Your number will differ. A TTL of <code>-2</code> means the key does not exist; <code>-1</code> 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.</p>
<p>Next, use the disposable demo to check the restriction:</p>
<pre><code class="language-text">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.
</code></pre>
<p>Our MCP call returned <code>User mcp_reader has no permissions to run the 'set' command</code>, and a separate read still returned <code>ready</code>. 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.</p>
<p>You can reproduce the underlying protocol checks without a model API key:</p>
<pre><code class="language-bash">.venv/bin/python verify.py
</code></pre>
<p>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 <code>SCAN</code> behaviour above. It stops only the process it created and saves <code>validation.json</code> beside the script.</p>
<h2>5. Connect a dedicated Managed Redis instance</h2>
<p>For a shared application database, create <a href="https://lizard.build/redis">Managed Redis</a> in the project dashboard and follow the <a href="https://lizard.build/docs/addons/redis/">connection guide</a>. Use a separate learning instance for this exercise. Copy its connection URL into a private local <code>.env</code> file under the name <code>ADMIN_REDIS_URL</code>; never paste that administrator credential into an AI chat.</p>
<p>Load your own trusted file and run setup from the demo directory:</p>
<pre><code class="language-bash">set -a
. ./.env
set +a
.venv/bin/python setup.py
unset ADMIN_REDIS_URL
</code></pre>
<p>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 <code>rediss://</code> endpoints. Query parameters and custom certificate paths need a separate configuration; the script stops rather than guessing them.</p>
<p>The endpoint must be reachable from the computer running MCP. A plain <code>redis://</code> 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 <code>redis://</code> connections, so do not assume it supplies a public TLS endpoint.</p>
<p>Creating an ACL user also requires the provider to permit <code>ACL SETUSER</code>. 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.</p>
<p>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 <a href="https://lizard.build/docs/platform/storage-and-recovery/">storage and recovery guide</a> for the service's other limits.</p>
<h2>Fix common Redis MCP connection errors</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Check</th>
</tr>
</thead>
<tbody><tr>
<td>MCP process fails to start</td>
<td>Use the absolute executable path from the generated config. Confirm that the Python environment still exists.</td>
</tr>
<tr>
<td>Connection refused or timeout</td>
<td>Check the explicit <code>--host</code> and <code>--port</code>, Redis availability and network access from the MCP computer.</td>
</tr>
<tr>
<td><code>WRONGPASS</code> or authentication failed</td>
<td>Check <code>REDIS_USERNAME</code> and <code>REDIS_PWD</code>. A password for <code>default</code> does not authenticate <code>mcp_reader</code>. Check whether a restart removed the temporary ACL user.</td>
</tr>
<tr>
<td><code>NOPERM</code> or a permission error</td>
<td>Compare the requested command and key with the ACL. A denied write, <code>SCAN</code> or unrelated prefix is expected in this guide.</td>
</tr>
<tr>
<td><code>WRONGTYPE</code></td>
<td>Use <code>type</code> first. Read strings with <code>get</code>; read hashes with <code>hgetall</code>.</td>
</tr>
<tr>
<td>Missing key or TTL <code>-2</code></td>
<td>Check the database number, exact key name and expiry.</td>
</tr>
<tr>
<td>TLS certificate failure</td>
<td>Confirm the server actually supports TLS and supply its trusted CA through the server's documented SSL options. Keep certificate checks enabled.</td>
</tr>
<tr>
<td><code>JSON.GET</code> or <code>FT.SEARCH</code> is unknown</td>
<td>Those tools need the matching Redis JSON or search capabilities. Core string/hash tools working does not prove those capabilities exist.</td>
</tr>
</tbody></table>
<h2>What to build after the connection works</h2>
<p>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.</p>
<p>If you want the application to save memories automatically, continue with <a href="https://lizard.build/blog/redis-agent-memory">AI agent memory with Redis</a>. If your agent needs relational data, use the separate reader role in <a href="https://lizard.build/blog/postgres-mcp">Postgres MCP</a>.</p>
<p>Start with <a href="https://lizard.build/redis">Managed Redis</a>, connect the restricted reader, and verify one successful read and one denied write before expanding the tool set.</p>
]]></content:encoded></item><item><title><![CDATA[AI Agent Memory with Redis: Remember Users Across Sessions]]></title><description><![CDATA[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 ap]]></description><link>https://lizard-build.hashnode.dev/ai-agent-memory-with-redis-remember-users-across-sessions</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/ai-agent-memory-with-redis-remember-users-across-sessions</guid><category><![CDATA[Redis]]></category><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Fri, 25 Sep 2026 14:55:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/e13c84ef-79c0-48c6-b6f4-4d2d4106de2e.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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.</p>
<p>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.</p>
<p>You can run the example locally, then connect the same code to <a href="https://lizard.build/redis">Managed Redis</a> in Lizard (lizard.build). It uses standard Redis Hash and List commands, so this walkthrough needs no vector index, RedisJSON or agent framework.</p>
<p><strong>Tested on September 25, 2026:</strong> Python 3.12.10, Redis 8.8.0, <code>redis-py==5.2.1</code> and four live calls to <code>openai/gpt-4.1-mini</code> 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.</p>
<h2>What should an AI agent remember?</h2>
<p>Start with two kinds of data. They need different keys and retention rules.</p>
<table>
<thead>
<tr>
<th>Memory</th>
<th>What it contains</th>
<th>Redis type</th>
<th>Retention in this example</th>
</tr>
</thead>
<tbody><tr>
<td>User profile</td>
<td>A preference the user explicitly saves</td>
<td>Hash</td>
<td>30 days after the last profile write</td>
</tr>
<tr>
<td>Session history</td>
<td>Recent user messages and assistant replies</td>
<td>List</td>
<td>Latest 20 messages; expires 24 hours after the last completed turn</td>
</tr>
</tbody></table>
<p>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.</p>
<img src="https://lizard.build/blog-images/redis-agent-memory/redis-agent-memory-layers.png" alt="A Redis Hash holds Alice's preference across two sessions, while separate Lists hold each session's recent messages." style="display:block;margin:0 auto" />

<p>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.</p>
<p>Redis also offers a separate <a href="https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/">Redis Agent Memory service</a>. 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.</p>
<h2>1. Set up the demo</h2>
<p>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.</p>
<p>Download the complete, tested program and install its one Python dependency:</p>
<pre><code class="language-bash">mkdir redis-memory-demo
cd redis-memory-demo
python3 -m venv .venv
. .venv/bin/activate
python -m pip install redis==5.2.1
curl -fsSLo memory_agent.py \
  https://lizard.build/blog-examples/redis-agent-memory/memory_agent.py
</code></pre>
<p>For a local test, run Redis in a separate terminal. This command binds it to loopback on port 6387; keep that terminal running:</p>
<pre><code class="language-bash">redis-server --bind 127.0.0.1 --port 6387 --save "" --appendonly no
</code></pre>
<p>This local Redis process is deliberately disposable. Its data remains available when the <strong>Python application</strong> exits, but stopping Redis loses that data. The test below restarts the application, not the database.</p>
<p>Create a private <code>.env</code> file in the demo directory using your editor:</p>
<pre><code class="language-dotenv">REDIS_URL=redis://127.0.0.1:6387/0
OPENROUTER_API_KEY=replace-with-your-key
OPENROUTER_MODEL=openai/gpt-4.1-mini
</code></pre>
<p>Keep <code>.env</code> out of Git. Restrict access and load it into the current shell:</p>
<pre><code class="language-bash">chmod 600 .env
set -a
. ./.env
set +a
</code></pre>
<p>The example calls the <a href="https://openrouter.ai/docs/api/reference/overview">OpenRouter Chat Completions API</a>. Redis handles the same memory reads and writes if you replace the model call with another provider.</p>
<h2>2. Give each user and session their own keys</h2>
<p>The program creates keys like these:</p>
<pre><code class="language-text">agentmem:v1:{alice}:profile
agentmem:v1:{alice}:session:session-1
agentmem:v1:{alice}:session:session-2
agentmem:v1:{bob}:profile
</code></pre>
<p>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.</p>
<p>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.</p>
<h2>3. Save an explicit preference</h2>
<p>Save Alice's choice:</p>
<pre><code class="language-bash">python memory_agent.py remember --user alice --preference vegetarian
</code></pre>
<p>The command prints <code>Preference saved.</code> Internally, it writes a field to a <a href="https://redis.io/docs/latest/develop/data-types/hashes/">Redis Hash</a> and sets the profile's expiry in a transaction:</p>
<pre><code class="language-python">with client.pipeline(transaction=True) as tx:
    tx.hset(profile, mapping={"dietary_preference": preference})
    tx.expire(profile, 30 * 24 * 60 * 60)
    tx.execute()
</code></pre>
<p>The only allowed values are <code>vegetarian</code>, <code>vegan</code> and <code>no_preference</code>. 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.</p>
<p>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.</p>
<h2>4. Load memory before calling the model</h2>
<p>Ask the assistant to recall the saved choice:</p>
<pre><code class="language-bash">python memory_agent.py chat --user alice --session session-1 \
  --message "What dietary preference have I saved?"
</code></pre>
<p>In our test, the model replied:</p>
<pre><code class="language-text">Your saved dietary preference is vegetarian.
</code></pre>
<p>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:</p>
<ol>
<li><p>Instructions that define the assistant's task.</p>
</li>
<li><p>A data message containing the allowed saved preference.</p>
</li>
<li><p>This session's recent user and assistant messages.</p>
</li>
<li><p>The new user question.</p>
</li>
</ol>
<p>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:</p>
<pre><code class="language-python">with client.pipeline(transaction=True) as tx:
    tx.rpush(history, *rows)
    tx.ltrim(history, -20, -1)
    tx.expire(history, 24 * 60 * 60)
    tx.execute()
</code></pre>
<p>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.</p>
<p>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 <a href="https://redis.io/docs/latest/commands/expire/">Redis EXPIRE reference</a> for how expiry interacts with updates.</p>
<h2>5. Start a new process and a new session</h2>
<p>Each CLI command starts and exits its own Python process. Run a second chat command with a different session ID:</p>
<pre><code class="language-bash">python memory_agent.py chat --user alice --session session-2 \
  --message "What dietary preference have I saved?"
</code></pre>
<p>The model again returned <code>Your saved dietary preference is vegetarian.</code> The new session began without the first conversation's messages. Its request contained the shared user profile, which was enough to answer.</p>
<img src="https://lizard.build/blog-images/redis-agent-memory/redis-agent-memory-flow.png" alt="After the app restarts, it loads Alice's saved preference before the model call and writes the reply to the new session." style="display:block;margin:0 auto" />

<p>Inspect the records the application reads:</p>
<pre><code class="language-bash">python memory_agent.py inspect --user alice --session session-2
</code></pre>
<p>You should see a profile with <code>dietary_preference</code> 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.</p>
<h2>6. Check user separation and forgetting</h2>
<p>First, inspect another user's context:</p>
<pre><code class="language-bash">python memory_agent.py inspect --user bob --session session-1
</code></pre>
<p>For a fresh Bob profile, the result is:</p>
<pre><code class="language-json">{
  "profile": {},
  "history": []
}
</code></pre>
<p>Asking Bob's assistant the same preference question produced <code>Your dietary preference is unknown.</code> in our test. The empty context is the deterministic isolation check; model wording can vary.</p>
<p>Next, stop any requests for Alice and remove her live Redis memory:</p>
<pre><code class="language-bash">python memory_agent.py forget-user --user alice
python memory_agent.py inspect --user alice --session session-1
python memory_agent.py inspect --user alice --session session-2
</code></pre>
<p>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.</p>
<p>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.</p>
<h2>What we tested</h2>
<p>The <a href="https://lizard.build/blog-examples/redis-agent-memory/verify.py">verification script</a> starts a new Redis process on an available loopback port. It never connects to an existing database. Its <a href="https://lizard.build/blog-examples/redis-agent-memory/validation.json">saved results</a> record the versions, scope and four model replies.</p>
<table>
<thead>
<tr>
<th>Check</th>
<th>Result</th>
</tr>
</thead>
<tbody><tr>
<td>A saved preference survives separate Python processes</td>
<td>Passed</td>
</tr>
<tr>
<td>A new session loads the profile without the old transcript</td>
<td>Passed</td>
</tr>
<tr>
<td>Another user starts with an empty context</td>
<td>Passed</td>
</tr>
<tr>
<td>Expired profile and session keys disappear</td>
<td>Passed</td>
</tr>
<tr>
<td>History stays within 20 messages and preserves complete turns</td>
<td>Passed</td>
</tr>
<tr>
<td>Forgetting removes both sessions and the profile, leaving another user intact</td>
<td>Passed</td>
</tr>
<tr>
<td>Live model calls recall the preference before deletion and report it unknown afterward</td>
<td>Passed</td>
</tr>
</tbody></table>
<p>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.</p>
<h2>Connect the example to Managed Redis</h2>
<p>After the local test, create a separate Redis instance for your project. In a linked Lizard project, run:</p>
<pre><code class="language-bash">lizard add redis
</code></pre>
<p>For a deployed service named <code>api</code>, set the connection reference and redeploy:</p>
<pre><code class="language-bash">lizard secrets set REDIS_URL='${{redis.REDIS_URL}}' --service api
lizard redeploy --service api
</code></pre>
<p>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 <a href="https://lizard.build/docs/addons/redis/">Managed Redis connection guide</a> for environment variables and access from your computer.</p>
<p>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 <code>redis://</code> URL does not add transport encryption.</p>
<h3>Decide which memories must survive data loss</h3>
<p>Memory surviving an application restart does not mean it survives every Redis failure. Expiry, <a href="https://redis.io/docs/latest/develop/reference/eviction/">key eviction</a>, persistence settings and backups all matter.</p>
<p>Managed Redis currently documents an <code>allkeys-lru</code> 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 <a href="https://lizard.build/postgres">Managed Postgres</a> and use Redis for recent context or a reloadable copy. Review the <a href="https://lizard.build/docs/platform/storage-and-recovery/">storage and recovery guide</a> before choosing a retention policy.</p>
<h2>Before you put this behind a web API</h2>
<p>The CLI keeps the example small. A shared application also needs:</p>
<ul>
<li><p><strong>Authentication and ownership checks.</strong> Derive user IDs on the server and check that each session belongs to that user.</p>
</li>
<li><p><strong>One active turn per session.</strong> 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.</p>
</li>
<li><p><strong>A memory size budget.</strong> Bound profile fields, message size, history length and the number of sessions. Monitor Redis memory and evictions.</p>
</li>
<li><p><strong>A Redis failure policy.</strong> Fail the request clearly or offer an explicitly stateless mode. Never claim a preference was saved when the write failed.</p>
</li>
<li><p><strong>Separate authority from remembered text.</strong> A saved message can contain hostile instructions. Keep permissions and tool decisions in application code; a system prompt is not an access-control boundary.</p>
</li>
</ul>
<h2>Common questions</h2>
<h3>Do I need a vector database for AI agent memory?</h3>
<p>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.</p>
<h3>Does Redis make an agent remember forever?</h3>
<p>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.</p>
<h3>Is this RAG or a semantic cache?</h3>
<p>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.</p>
<h3>Can I use LangGraph later?</h3>
<p>Yes. LangGraph offers checkpoints for conversation state and stores for memory across threads. Its Redis integration has its own requirements; check the <a href="https://pypi.org/project/langgraph-checkpoint-redis/">LangGraph Redis package documentation</a> before replacing this simple client with a framework backend.</p>
<h2>Give your agent a memory you can inspect</h2>
<p>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.</p>
<p>Create <a href="https://lizard.build/redis">Managed Redis</a> for the application's shared context. If your agent also needs to query structured project data, the <a href="https://lizard.build/blog/postgres-mcp">Postgres MCP guide</a> covers that connection with a separate database reader role.</p>
]]></content:encoded></item><item><title><![CDATA[Postgres MCP: connect your AI agent to a database]]></title><description><![CDATA[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 tab]]></description><link>https://lizard-build.hashnode.dev/postgres-mcp-connect-your-ai-agent-to-a-database</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/postgres-mcp-connect-your-ai-agent-to-a-database</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[mcp]]></category><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Fri, 25 Sep 2026 11:42:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/3a6d0fe0-e0d4-47a0-9351-cb2526f85265.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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.</p>
<p>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.</p>
<p>You can create the database with <a href="https://lizard.build/postgres">Managed Postgres</a> on Lizard (lizard.build). The MCP process runs on your computer. The same SQL setup also works with a local PostgreSQL instance you own.</p>
<h2>How Postgres MCP connects to your database</h2>
<p>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.</p>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/mkxbdku6sa9kgl1oai7n.png" alt="Cursor connects through Postgres MCP Pro to Managed Postgres using the mcp_reader role." style="display:block;margin:0 auto" />

<p>We use <a href="https://github.com/crystaldba/postgres-mcp">Postgres MCP Pro</a>, an independent open-source project. It exposes tools for listing schemas, reading table details and executing SQL. Lizard supplies the database in this setup.</p>
<p>The local connection between Cursor and the MCP process uses <code>stdio</code>. 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.</p>
<h2>What you need</h2>
<ul>
<li><p>A new PostgreSQL instance or a separate disposable database, with an owner account that can create a database and a role.</p>
</li>
<li><p><code>psql</code> on your computer.</p>
</li>
<li><p><a href="https://docs.astral.sh/uv/getting-started/installation/">uv</a>, which runs the pinned Python package.</p>
</li>
<li><p>Cursor with custom MCP servers enabled.</p>
</li>
</ul>
<p>We tested the SQL and MCP calls with PostgreSQL 14.20, Python 3.12.10, <code>postgres-mcp==0.3.0</code> and <code>mcp==1.30.0</code>. The result table below records the scope of those checks.</p>
<h2>1. Create a sample Postgres database</h2>
<p>In a new Lizard project, add Managed Postgres from the dashboard. If you already use Lizard CLI and have linked the new project, run:</p>
<pre><code class="language-bash">lizard add postgres
</code></pre>
<p>The dashboard provides the host, port, database and credentials. Follow the <a href="https://lizard.build/docs/addons/postgres/">Managed Postgres connection guide</a> to connect with <code>psql</code>. Use the owner account for this setup; the agent will receive a different account.</p>
<p>In <code>psql</code>, create and switch to a fresh database:</p>
<pre><code class="language-sql">CREATE DATABASE mcp_demo;
\connect mcp_demo
</code></pre>
<p><code>\connect</code> is a <code>psql</code> command. If you use a SQL editor, select <code>mcp_demo</code> before running the next block. If the database name already exists, choose another name and update the later examples.</p>
<p>Create one table with three rows:</p>
<pre><code class="language-sql">CREATE SCHEMA demo;

CREATE TABLE demo.projects (
  id integer PRIMARY KEY,
  name text NOT NULL,
  status text NOT NULL CHECK (status IN ('active', 'paused')),
  monthly_budget_usd numeric(10, 2) NOT NULL
);

INSERT INTO demo.projects VALUES
  (1, 'Atlas', 'active', 49.00),
  (2, 'Beacon', 'active', 19.00),
  (3, 'Cedar', 'paused', 0.00);
</code></pre>
<p>These amounts belong to the sample data. They are not Lizard prices.</p>
<h2>2. Give the agent a role that can read the sample table</h2>
<p>Create a login with no administrative privileges:</p>
<pre><code class="language-sql">CREATE ROLE mcp_reader LOGIN
  NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOINHERIT;
</code></pre>
<p>Then run this <code>psql</code> command to set its password without putting the password in SQL history:</p>
<pre><code class="language-text">\password mcp_reader
</code></pre>
<p>The following grants are for the <strong>fresh</strong> <code>mcp_demo</code> <strong>database</strong>. The <code>PUBLIC</code> revocations affect other roles that use that database, so do not paste this block into an existing shared application database.</p>
<pre><code class="language-sql">REVOKE ALL ON DATABASE mcp_demo FROM PUBLIC;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

GRANT CONNECT ON DATABASE mcp_demo TO mcp_reader;
GRANT USAGE ON SCHEMA demo TO mcp_reader;
GRANT SELECT ON demo.projects TO mcp_reader;

ALTER ROLE mcp_reader IN DATABASE mcp_demo
  SET default_transaction_read_only = on;
ALTER ROLE mcp_reader IN DATABASE mcp_demo
  SET statement_timeout = '5s';
</code></pre>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/zxor50hzuge8e67clhmy.png" alt="mcp_reader can read demo.projects but cannot write rows or create tables." style="display:block;margin:0 auto" />

<p>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 <a href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT reference</a>.</p>
<p>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 <code>demo.projects</code>. We checked that an update still fails after turning the default off.</p>
<h2>3. Save the reader connection outside your source code</h2>
<p>Build a connection URL using the new role and the <code>mcp_demo</code> 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 <a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS">connection URI format</a>.</p>
<p>For a hosted endpoint that requires TLS, the shape is:</p>
<pre><code class="language-text">postgresql://mcp_reader:URL_ENCODED_PASSWORD@DB_HOST:DB_PORT/mcp_demo?sslmode=require
</code></pre>
<p><code>sslmode=require</code> requires encryption. If your provider supplies a CA certificate and hostname for full certificate checks, use its <code>verify-full</code> configuration. A certificate error needs a matching host and trust configuration; do not solve it by disabling TLS on a hosted connection.</p>
<p>Add <code>.env.mcp</code> to your project's <code>.gitignore</code>, then create that file at the project root:</p>
<pre><code class="language-dotenv">DATABASE_URI=postgresql://mcp_reader:URL_ENCODED_PASSWORD@DB_HOST:DB_PORT/mcp_demo?sslmode=require
</code></pre>
<p>Replace every placeholder with your reader connection values. The name is <code>DATABASE_URI</code>: that is what Postgres MCP Pro expects. Lizard's application connection variable is named <code>DATABASE_URL</code>; passing that name alone will not configure this MCP server.</p>
<p>Keep the owner connection out of this file. On macOS or Linux, restrict access to the reader file:</p>
<pre><code class="language-bash">chmod 600 .env.mcp
</code></pre>
<h2>4. Configure Postgres MCP in Cursor</h2>
<p>Create <code>.cursor/mcp.json</code> in the same project. If the file already contains other servers, add <code>postgres-demo</code> inside its existing <code>mcpServers</code> object.</p>
<pre><code class="language-json">{
  "mcpServers": {
    "postgres-demo": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "--python", "3.12",
        "--with", "mcp==1.30.0",
        "--from", "postgres-mcp==0.3.0",
        "postgres-mcp", "--access-mode=restricted"
      ],
      "envFile": "${workspaceFolder}/.env.mcp"
    }
  }
}
</code></pre>
<p>Cursor supports project MCP configuration and <code>envFile</code> for local <code>stdio</code> servers. See its <a href="https://cursor.com/docs/mcp#stdio-server-configuration">MCP configuration reference</a>. If Cursor cannot find <code>uvx</code>, replace the command with its full installed path.</p>
<p>The two version pins matter. During our check, installing <code>postgres-mcp==0.3.0</code> without an MCP SDK constraint selected <code>mcp==2.2.0</code>. The server then failed to import <code>mcp.server.fastmcp</code>. With <code>mcp==1.30.0</code>, it started and completed the tests below.</p>
<p>To check the package launch before opening a database connection, run:</p>
<pre><code class="language-bash">uvx --python 3.12 --with 'mcp==1.30.0' \
  --from 'postgres-mcp==0.3.0' postgres-mcp --help
</code></pre>
<p>Enable or restart <code>postgres-demo</code> in Cursor's MCP settings. Leave tool approval enabled while checking the setup, and inspect the SQL arguments before allowing a call.</p>
<h2>5. Verify the tools and the answer</h2>
<p>Start with a schema question:</p>
<pre><code class="language-text">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.
</code></pre>
<p>The server should expose <code>list_schemas</code>, <code>list_objects</code>, <code>get_object_details</code> and <code>execute_sql</code>. Confirm that the agent calls the tools and reports <code>id</code>, <code>name</code>, <code>status</code> and <code>monthly_budget_usd</code> from the table.</p>
<p>Then ask:</p>
<pre><code class="language-text">Using demo.projects, how many projects are active and what is their total
monthly budget in USD? Show the SQL and the database result.
</code></pre>
<p>A query for that answer is:</p>
<pre><code class="language-sql">SELECT
  count(*) AS active_projects,
  sum(monthly_budget_usd) AS total_budget_usd
FROM demo.projects
WHERE status = 'active';
</code></pre>
<p>The expected values are:</p>
<table>
<thead>
<tr>
<th>active_projects</th>
<th>total_budget_usd</th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>68.00</td>
</tr>
</tbody></table>
<p>Finally, check the restriction on this sample table. The <code>WHERE false</code> condition ensures the query has no matching rows:</p>
<pre><code class="language-text">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.
</code></pre>
<p>In our test, restricted mode returned <code>Error: Error validating query</code>. A separate direct connection using <code>mcp_reader</code> returned <code>permission denied for table projects</code> even after we disabled its read-only default. The MCP check and database grants each rejected the operation.</p>
<h2>What we tested</h2>
<p>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.</p>
<table>
<thead>
<tr>
<th>Check</th>
<th>Result</th>
</tr>
</thead>
<tbody><tr>
<td>Connect as <code>mcp_reader</code></td>
<td>Connected to <code>mcp_demo</code>; read-only default on</td>
</tr>
<tr>
<td>List the schema, table and columns through MCP</td>
<td>Returned the sample schema and table fields</td>
</tr>
<tr>
<td>Query the active projects directly and through MCP</td>
<td>Both returned 2 projects and $68.00</td>
</tr>
<tr>
<td>Try an update through restricted MCP mode</td>
<td>Rejected during query validation</td>
</tr>
<tr>
<td>Try an update directly with the read-only default off</td>
<td>Rejected by PostgreSQL table permissions</td>
</tr>
<tr>
<td>Read a table in an ungranted test schema</td>
<td>Rejected by PostgreSQL schema permissions</td>
</tr>
<tr>
<td>Check table creation in the public schema and temporary table privileges</td>
<td>Neither granted</td>
</tr>
</tbody></table>
<p>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.</p>
<h2>Fix common Postgres MCP connection errors</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>What to check</th>
</tr>
</thead>
<tbody><tr>
<td><code>No module named mcp.server.fastmcp</code></td>
<td>Use the tested <code>mcp==1.30.0</code> pin with Postgres MCP Pro 0.3.0. Restart the server after changing its arguments.</td>
</tr>
<tr>
<td><code>uvx</code> not found</td>
<td>Install uv, then use the full path to <code>uvx</code> in Cursor if needed.</td>
</tr>
<tr>
<td>Missing database URL</td>
<td>Confirm <code>.env.mcp</code> contains <code>DATABASE_URI</code> and that <code>envFile</code> points to the right project.</td>
</tr>
<tr>
<td>Password authentication failed</td>
<td>Use the password for <code>mcp_reader</code>, check URL encoding and confirm the endpoint.</td>
</tr>
<tr>
<td>Connection timed out or refused</td>
<td>Check host, port, network access and whether the database is running. A private service hostname may not resolve from your laptop.</td>
</tr>
<tr>
<td>Certificate verification failed</td>
<td>Match the provider's hostname, CA certificate and TLS settings.</td>
</tr>
<tr>
<td>Permission denied for schema or table</td>
<td>Check <code>USAGE</code> on the intended schema and <code>SELECT</code> on the intended table. Grant only the access the example needs.</td>
</tr>
<tr>
<td>Empty table list</td>
<td>Confirm the database name and schema. This guide puts the table in <code>demo</code>, not <code>public</code>.</td>
</tr>
</tbody></table>
<h2>Do you need PostgreSQL extensions?</h2>
<p>The schema and data queries in this guide need no extra extension. Postgres MCP Pro also offers performance tools that have different requirements.</p>
<p>Its top-query analysis uses <code>pg_stat_statements</code>. Hypothetical index analysis uses <code>hypopg</code>. Availability, server configuration and role permissions all matter; creating an extension may require an owner action or a server change. Check the project's <a href="https://github.com/crystaldba/postgres-mcp#postgres-extension-installation-optional">extension requirements</a> before using those tools.</p>
<p>Start with the schema and <code>SELECT</code> checks. An extension-related failure in a tuning tool does not, by itself, mean the basic MCP connection is broken.</p>
<h2>FAQ</h2>
<h3>Is Postgres MCP the same as Lizard MCP?</h3>
<p>No. This example uses Postgres MCP Pro to query a PostgreSQL database. Managed Postgres provides that database. The connector is a separate project.</p>
<h3>Can I use a different AI agent?</h3>
<p>Yes, if its client supports local MCP servers over <code>stdio</code>. Use the same pinned process, reader credentials and restricted mode, then follow that client's configuration format. The JSON above is for Cursor.</p>
<h3>Does restricted mode replace database permissions?</h3>
<p>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.</p>
<h3>Will it create tables or run migrations for me?</h3>
<p>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.</p>
<h2>Connect the database to your app next</h2>
<p>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.</p>
<p><a href="https://lizard.build/postgres">Create Managed Postgres</a> for your project, follow the <a href="https://lizard.build/docs/addons/postgres/">database connection guide</a>, or continue with the <a href="https://lizard.build/blog/deploy-cursor-app-postgres">Cursor app deployment example</a>. For deploying an MCP service shared by several clients, see the separate <a href="https://lizard.build/docs/guides/deploy-mcp-server/">remote MCP server guide</a>.</p>
<p>Originally published on <a href="https://lizard.build/blog/postgres-mcp">Lizard</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Vercel alternatives: choosing a host for your backend]]></title><description><![CDATA[The right Vercel alternative depends on what your application runs. Compare Lizard, Railway and Render for services that stay active between requests. Consider Cloud Run for an HTTP service that can s]]></description><link>https://lizard-build.hashnode.dev/vercel-alternatives-choosing-a-host-for-your-backend</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/vercel-alternatives-choosing-a-host-for-your-backend</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:51:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/21fce415-e361-4dc9-aae3-6238d666c506.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>The right Vercel alternative depends on what your application runs.</strong> Compare Lizard, 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.</p>
<p>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.</p>
<h2>Start with the runtime you need</h2>
<p>A Dockerfile describes how to package an application. It does not tell you how a hosting service schedules, stops or bills that application.</p>
<p>Vercel now supports <a href="https://vercel.com/docs/functions/container-images">container images for Functions</a>. Its <a href="https://vercel.com/docs/services/pricing">Services documentation</a> 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.</p>
<p>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.</p>
<table>
<thead>
<tr>
<th>Your requirement</th>
<th>Options to evaluate</th>
<th>What to test first</th>
</tr>
</thead>
<tbody><tr>
<td>Frontend previews and a request-driven backend</td>
<td>Vercel</td>
<td>Framework behaviour, cache rules and production limits</td>
</tr>
<tr>
<td>API plus a separate queue consumer</td>
<td>Lizard, Railway, Render</td>
<td>Worker restarts, retries and database access</td>
</tr>
<tr>
<td>An HTTP container with uneven traffic</td>
<td>Cloud Run</td>
<td>Cold starts, concurrency and minimum instances</td>
</tr>
<tr>
<td>Direct control of a server</td>
<td>A VPS with a deployment tool</td>
<td>Patching, backups and your response to failures</td>
</tr>
</tbody></table>
<p>These are starting points for a trial. None establishes a universal winner. Our <a href="https://lizard.build/blog/best-paas-providers">PaaS comparison</a> covers the broader choice.</p>
<h2>Where Lizard fits</h2>
<p>Lizard runs web services and workers, offers <a href="https://lizard.build/postgres">Managed Postgres</a>, <a href="https://lizard.build/redis">Managed Redis</a> and <a href="https://lizard.build/object-storage">Managed Object Storage</a>, and exposes deployment and operations through the <a href="https://lizard.build/cli">Lizard CLI</a>.</p>
<p>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.</p>
<p>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.</p>
<h2>When Vercel is a better fit</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2>Compare bills after fees, credits and allowances</h2>
<p>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.</p>
<table>
<thead>
<tr>
<th>Billing term</th>
<th>Lizard</th>
<th>Vercel Pro</th>
</tr>
</thead>
<tbody><tr>
<td>Base plan</td>
<td>No monthly subscription</td>
<td>$20/month with one deploying seat</td>
</tr>
<tr>
<td>Included usage credit</td>
<td>$10 trial credit, valid for 31 days; purchased credits do not expire</td>
<td>$20 per team, not per seat</td>
</tr>
<tr>
<td>Additional deploying seats</td>
<td>No separate seat charge</td>
<td>$20/month each</td>
</tr>
<tr>
<td>Internet transfer</td>
<td>$0.045/GB from the first byte</td>
<td>Current Pro terms include a Flat Rate CDN tier with 1 million CDN requests and 1 TB of data transfer</td>
</tr>
<tr>
<td>Hosted builds</td>
<td>Builds are billed</td>
<td>Paid build machines bill by CPU-minute</td>
</tr>
</tbody></table>
<p>Sources: <a href="https://lizard.build/pricing">Lizard pricing</a>, <a href="https://vercel.com/docs/plans/pro-plan">Vercel Pro terms</a> and <a href="https://vercel.com/docs/builds/managing-builds">Vercel build billing</a>. CDN transfer, origin transfer and Function invocations are distinct Vercel meters. Check the terms your account uses before reusing an older estimate.</p>
<h3>Hosted build costs: Lizard and Vercel</h3>
<p><strong>Lizard charges for builds.</strong> Include those charges when comparing deployment costs.</p>
<p>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 <strong>$8.40 before plan credit and tax</strong>, or $0.042 per build. <a href="https://vercel.com/docs/builds/managing-builds">Vercel build rates, checked 15 September 2026</a>.</p>
<p><img src="https://lizard.build/blog-images/pricing/vercel-standard-build-cost.svg" 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." /></p>
<p>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.</p>
<h3>Where no subscription lowers the bill</h3>
<p>Lizard charges 200 GB of outgoing traffic at <code>200 × $0.045 = $9</code>. There is no bundled traffic allowance to subtract. Vercel Pro includes a CDN allowance, so compare that meter separately from Function and origin traffic.</p>
<p>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 <a href="https://lizard.build/pricing">Lizard rates</a>:</p>
<p><img src="https://lizard.build/blog-images/pricing/lizard-small-app-monthly-cost.svg" 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." /></p>
<p><strong>Lizard costs less for this small-app example than the $20 Vercel Pro monthly minimum</strong>, 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. <a href="https://vercel.com/docs/plans/pro-plan">Vercel Pro terms, checked 15 September 2026</a>.</p>
<p>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.</p>
<p>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.</p>
<h2>Move one service before moving the whole app</h2>
<ol>
<li>List Vercel-specific dependencies: request handlers, image processing, cache behaviour, scheduled work, storage and environment variables.</li>
<li>Choose an API or worker that can run independently. Give it a production start command and a health endpoint where appropriate.</li>
<li>Deploy a test copy and connect it to a separate test database. Configure credentials through service settings.</li>
<li>Test authentication, retries, timeouts, file handling and restarts with representative traffic.</li>
<li>Measure cost and latency, then plan the database and domain cutover. Keep a rollback path until you have checked writes on the new deployment.</li>
</ol>
<p>For a GitHub repository, start with the <a href="https://lizard.build/docs/guides/github-integration/">Git deployment guide</a>. For a local project, use the <a href="https://lizard.build/docs/deploy/upload/">upload deployment guide</a>. The <a href="https://lizard.build/docs/cli/config/">CLI configuration reference</a> explains how to keep deployment settings repeatable.</p>
<h2>FAQ</h2>
<p><strong>Can Vercel run a Dockerfile?</strong> 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.</p>
<p><strong>Is Railway billed as a flat server tier?</strong> 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 <a href="https://docs.railway.com/pricing">Railway pricing documentation</a>.</p>
<p><strong>Can I keep Vercel for the frontend?</strong> Yes. A separate API or worker can run elsewhere. Test cross-origin requests, cookie settings, authentication and network costs as part of that split.</p>
<p><strong>Which option is cheapest?</strong> 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.</p>
<p><strong>What should an AI coding agent verify after deployment?</strong> 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 <a href="https://lizard.build/blog/deploy-from-claude-code">Claude Code deployment guide</a> shows that workflow.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/vercel-alternative">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Deploy from Claude Code: a practical Lizard workflow]]></title><description><![CDATA[Claude Code can deploy an application by using a hosting provider's tools in your development environment. With Lizard, the agent can inspect the project, use the Lizard CLI to deploy, configure depen]]></description><link>https://lizard-build.hashnode.dev/deploy-from-claude-code-a-practical-lizard-workflow</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/deploy-from-claude-code-a-practical-lizard-workflow</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:50:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/141db4bd-6705-4b82-ad11-46ff4c065da3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Claude Code can deploy an application by using a hosting provider's tools in your development environment.</strong> With Lizard, 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.</p>
<p>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.</p>
<h2>Give the agent the right context</h2>
<p>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.</p>
<p>Install the CLI and read its version-matched instructions:</p>
<pre><code class="language-bash">npm install -g @lizard-build/cli
lizard skills get core --json
lizard status --json
</code></pre>
<p>If authentication is needed, use <code>lizard login</code> and finish the browser sign-in. For the reusable agent instructions, follow the <a href="https://github.com/lizard-build/skill">Lizard Skill repository</a>. Claude Code's own <a href="https://code.claude.com/docs/en/overview">overview</a> describes how it works with a codebase and tools.</p>
<p>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.</p>
<h2>A prompt that defines the deployment task</h2>
<p>Give the agent a bounded instruction such as:</p>
<blockquote>
<p>Deploy this repository to the existing Lizard project <code>my-project</code> 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.</p>
</blockquote>
<p>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.</p>
<h2>Choose Git deployment or source upload</h2>
<p>Use the repository's GitHub integration when you want later commits to trigger deployments. The <a href="https://lizard.build/docs/guides/github-integration/">GitHub deployment guide</a> covers connection and service setup. Check the tracked branch and root directory for a monorepo.</p>
<p>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:</p>
<pre><code class="language-bash">lizard init --name my-project
lizard add --service api
lizard up --service api
</code></pre>
<p>These names are examples. For an existing project, confirm the current link with <code>lizard status --json</code> and select the existing service. Do not create another project or switch a Git-backed service to upload merely to avoid reading its settings.</p>
<p>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 <a href="https://lizard.build/docs/deploy/">deployment docs</a> and keep the settings in the <a href="https://lizard.build/docs/cli/config/">configuration reference</a> repeatable.</p>
<h2>Connect the database deliberately</h2>
<p>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 <code>api</code> and its first PostgreSQL add-on named <code>postgres</code>, the reference is:</p>
<pre><code class="language-bash">lizard add postgres
lizard secrets set 'DATABASE_URL=${{postgres.DATABASE_URL}}' --service api
</code></pre>
<p>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 <a href="https://lizard.build/docs/variables/references/">variable references</a> and <a href="https://lizard.build/postgres">Managed Postgres</a>.</p>
<p>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.</p>
<h2>Check the result from the outside</h2>
<p>Use structured output for diagnostics:</p>
<pre><code class="language-bash">lizard status --json
lizard logs --service api --json
lizard logs --service api --build --json
lizard metrics --service api --json
</code></pre>
<p>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.</p>
<p>If deployment fails, use the reported error to choose the next check:</p>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>First checks</th>
</tr>
</thead>
<tbody><tr>
<td>Build fails</td>
<td>Dependency lockfile, runtime version and build command</td>
</tr>
<tr>
<td>Process exits</td>
<td>Start command, missing configuration and import errors</td>
</tr>
<tr>
<td>Health check fails</td>
<td>Bound interface, port and startup time</td>
</tr>
<tr>
<td>Page loads but actions fail</td>
<td>API URL, auth, database and callback settings</td>
</tr>
<tr>
<td>Files disappear after a change</td>
<td>Storage location and persistence guarantees</td>
</tr>
</tbody></table>
<p>An agent should report an unresolved error rather than declare success from the build log. The <a href="https://lizard.build/blog/python-app-hosting">Python hosting guide</a> gives examples of start-command problems.</p>
<h2>Understand the cost of the workflow</h2>
<p>Your coding subscription and hosting bill pay for different parts of the work. Lizard uses pay as you go with no monthly subscription. Its <a href="https://lizard.build/pricing">pricing page</a> lists resource rates and payment terms. A test service, database or sandbox can continue consuming resources after the coding session ends.</p>
<p>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.</p>
<h2>FAQ</h2>
<p><strong>Does Claude Code host the app?</strong> The deployment target does. Claude Code uses tools to prepare and deploy the code; the hosting provider runs the resulting application.</p>
<p><strong>Can I deploy an existing GitHub repository?</strong> Yes, if Lizard can access it. Configure the repository, branch and service through the Git deployment workflow, then verify the deployed revision.</p>
<p><strong>Do databases connect automatically?</strong> Provision the database, configure the variable the application reads, and test the connection. Those are separate checks.</p>
<p><strong>Can the same workflow work with another coding agent?</strong> 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.</p>
<p><strong>How do I know the deployment is complete?</strong> Confirm the live URL, expected revision, healthy process and meaningful application behaviour. A successful build alone is not enough.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/deploy-from-claude-code">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Firebase alternatives: match the backend you need]]></title><description><![CDATA[Choose a Firebase alternative by the Firebase services you need to replace. Supabase and Nhost belong on a shortlist for a PostgreSQL-based backend. Appwrite offers an integrated backend product. Pock]]></description><link>https://lizard-build.hashnode.dev/firebase-alternatives-match-the-backend-you-need</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/firebase-alternatives-match-the-backend-you-need</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:50:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/043365cc-d676-44c8-a937-c89f46552a9e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Choose a Firebase alternative by the Firebase services you need to replace.</strong> 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 is another approach, but it does not automatically replace Firebase Authentication, security rules or realtime client behaviour.</p>
<p>This comparison comes from Lizard. We checked the linked documentation on 9 September 2026. Start with architecture and migration effort, then compare the bill.</p>
<h2>Firebase is more than a database</h2>
<p>An app may use Firestore, Realtime Database, Authentication, Storage, Functions, Hosting and messaging in different combinations. Each has its own API and cost model. <a href="https://firebase.google.com/pricing">Firebase pricing</a> lists those products separately.</p>
<p>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.</p>
<p>Make a list of SDK calls in your application. Use it to define the replacement contract before picking a vendor.</p>
<h2>A shortlist by backend model</h2>
<table>
<thead>
<tr>
<th>Option</th>
<th>Why to evaluate it</th>
<th>What needs review</th>
</tr>
</thead>
<tbody><tr>
<td>Supabase</td>
<td>PostgreSQL with an integrated backend workflow</td>
<td>SQL schema, access policies and client API changes</td>
</tr>
<tr>
<td>Appwrite</td>
<td>Auth, databases, storage, functions and realtime features</td>
<td>API compatibility and cloud versus self-hosted operation</td>
</tr>
<tr>
<td>Convex</td>
<td>A reactive backend approach</td>
<td>Its data and function model compared with your existing code</td>
</tr>
<tr>
<td>PocketBase</td>
<td>An embedded SQLite backend in a compact application</td>
<td>Production requirements, upgrades and recovery</td>
</tr>
<tr>
<td>Nhost</td>
<td>Database, GraphQL, auth and storage in one backend product</td>
<td>GraphQL model, permissions and deployment requirements</td>
</tr>
<tr>
<td>AWS Amplify</td>
<td>An application backend built around AWS services</td>
<td>AWS identity, resources and the resulting bill</td>
</tr>
<tr>
<td>Your own API and PostgreSQL</td>
<td>Control over the server-side application</td>
<td>Building auth, authorization and realtime behaviour you need</td>
</tr>
</tbody></table>
<p>Read the primary product documentation for <a href="https://supabase.com/docs">Supabase</a>, <a href="https://appwrite.io/docs">Appwrite</a>, <a href="https://docs.convex.dev/">Convex</a>, <a href="https://docs.nhost.io/">Nhost</a> and <a href="https://docs.amplify.aws/">Amplify</a> before treating a feature label as equivalent behaviour.</p>
<h2>When PostgreSQL is the reason to move</h2>
<p>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.</p>
<p>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.</p>
<p>If you choose your own API, <a href="https://lizard.build/postgres">Managed Postgres</a> 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.</p>
<h2>When self-hosting is the reason to move</h2>
<p>Self-hosting gives you control over deployment and data placement. It also makes backups, upgrades, monitoring and recovery part of your operating plan.</p>
<p>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. <a href="https://pocketbase.io/docs/">PocketBase documentation</a>.</p>
<p>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.</p>
<h2>Compare the complete cost model</h2>
<p>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. <a href="https://firebase.google.com/docs/firestore/pricing">Firestore billing documentation</a>.</p>
<p>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 <a href="https://firebase.google.com/pricing">Firebase</a>, <a href="https://supabase.com/pricing">Supabase</a>, <a href="https://appwrite.io/pricing">Appwrite</a> and <a href="https://lizard.build/pricing">Lizard pricing</a> as applicable.</p>
<p>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.</p>
<h2>A migration plan that tests permissions</h2>
<ol>
<li>Inventory the Firebase products, SDK calls, security rules and identity providers in use.</li>
<li>Design the replacement data model and the access rules for each user role.</li>
<li>Import a test dataset. Check counts, identifiers, timestamps and representative application queries.</li>
<li>Test access as an anonymous user, a normal user and an administrator. Include denied reads and writes.</li>
<li>Test sign-in, sign-out, password recovery, file access and realtime updates where used.</li>
<li>Plan the final write transition, client release and rollback. Old mobile or browser clients may continue calling the old API.</li>
</ol>
<p>On Lizard, a custom backend can use <a href="https://lizard.build/redis">Managed Redis</a> where appropriate and <a href="https://lizard.build/object-storage">Managed Object Storage</a> for files. Storage access policy must match the sensitivity of those files; do not assume a newly provisioned bucket is private.</p>
<h2>FAQ</h2>
<p><strong>Is Lizard a direct Firebase replacement?</strong> 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.</p>
<p><strong>Which alternative is closest to Firebase?</strong> It depends on which Firebase products you use. Compare actual APIs, auth, file access and realtime behaviour rather than selecting by a feature count.</p>
<p><strong>Can I keep Firebase Authentication and move the database?</strong> 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.</p>
<p><strong>Will PostgreSQL remove all usage charges?</strong> 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.</p>
<p><strong>What is the most important migration test?</strong> Confirm that users can access exactly the records and files they should, and no others. Data import success alone does not prove correct permissions.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/firebase-alternatives">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Python app hosting: deploy Django, Flask and FastAPI]]></title><description><![CDATA[Python app hosting starts with the process your application needs to run. Django and Flask commonly use a WSGI server; FastAPI uses an ASGI server. A Celery worker or a scheduled script has another li]]></description><link>https://lizard-build.hashnode.dev/python-app-hosting-deploy-django-flask-and-fastapi</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/python-app-hosting-deploy-django-flask-and-fastapi</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:49:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/b40af43c-24a1-4acd-86dd-07f4d54dc3d9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Python app hosting starts with the process your application needs to run.</strong> 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.</p>
<p>Lizard 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.</p>
<h2>Choose the runtime before the host</h2>
<table>
<thead>
<tr>
<th>Application</th>
<th>Production process</th>
<th>Other requirements to check</th>
</tr>
</thead>
<tbody><tr>
<td>Django with WSGI</td>
<td>Gunicorn or another supported WSGI server</td>
<td>Database, migrations, static files and uploads</td>
</tr>
<tr>
<td>Django with async features</td>
<td>A supported ASGI server</td>
<td>Connection handling and compatibility of the app's dependencies</td>
</tr>
<tr>
<td>Flask</td>
<td>A production WSGI server</td>
<td>App import path or factory, secrets and database</td>
</tr>
<tr>
<td>FastAPI</td>
<td>Uvicorn or another ASGI server</td>
<td>Startup tasks, concurrency and database connections</td>
</tr>
<tr>
<td>Celery worker</td>
<td>A separate queue consumer</td>
<td>Broker, result backend if used, retries and shutdown</td>
</tr>
<tr>
<td>Script or batch job</td>
<td>A process that runs and exits</td>
<td>Schedule, timeout, exit status and durable output</td>
</tr>
</tbody></table>
<p>The development server is for development. See the official <a href="https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/">Django deployment guide</a>, <a href="https://flask.palletsprojects.com/en/stable/deploying/">Flask production guide</a> and <a href="https://fastapi.tiangolo.com/deployment/server-workers/">FastAPI worker guidance</a>.</p>
<h2>Python hosting options by need</h2>
<table>
<thead>
<tr>
<th>Host</th>
<th>Why to consider it</th>
<th>Check before choosing</th>
</tr>
</thead>
<tbody><tr>
<td>Lizard</td>
<td>Web services and workers with managed data services and CLI operations</td>
<td>Runtime settings, database wiring and measured resource use</td>
</tr>
<tr>
<td>Railway</td>
<td>Several services in a project</td>
<td>All service consumption and plan credit</td>
</tr>
<tr>
<td>Render</td>
<td>Published web and worker instance plans</td>
<td>Separate database cost and free-service restrictions</td>
</tr>
<tr>
<td>PythonAnywhere</td>
<td>A Python-focused hosting workflow</td>
<td>WSGI/ASGI support, outbound access and task limits on your plan</td>
</tr>
<tr>
<td>Cloud Run</td>
<td>HTTP services, jobs or worker pools</td>
<td>Resource type, billing mode, concurrency and cold starts</td>
</tr>
<tr>
<td>Fly.io</td>
<td>Machines in selected regions</td>
<td>Machine sizing, storage and network charges</td>
</tr>
<tr>
<td>DigitalOcean App Platform</td>
<td>Managed source or image deployment</td>
<td>Build support, app components and database pricing</td>
</tr>
<tr>
<td>Heroku</td>
<td>A familiar Python deployment workflow</td>
<td>Current plan, add-ons and product direction</td>
</tr>
<tr>
<td>A VPS</td>
<td>Direct server control</td>
<td>Updates, process management, TLS, backups and recovery</td>
</tr>
</tbody></table>
<p>Use the <a href="https://lizard.build/blog/best-paas-providers">PaaS comparison</a> for the wider hosting decision. A Python badge in a feature table is not enough to confirm worker or database support.</p>
<h2>Free Python hosting needs a workload limit</h2>
<p>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.</p>
<p>Check <a href="https://render.com/docs/free">Render's free-service rules</a> and <a href="https://help.pythonanywhere.com/pages/FreeAccountsFeatures/">PythonAnywhere's free-account features</a>. For Cloud Run, the <a href="https://cloud.google.com/run/pricing">pricing page</a> describes free usage alongside billable resources. A database, image registry or network usage may remain outside the allowance you are looking at.</p>
<p>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.</p>
<h2>Build and start commands</h2>
<p>For a project that uses <code>requirements.txt</code>, install its pinned dependencies with:</p>
<pre><code class="language-bash">python -m pip install -r requirements.txt
</code></pre>
<p>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.</p>
<p>For Django, where <code>myproject/wsgi.py</code> defines the application:</p>
<pre><code class="language-bash">gunicorn myproject.wsgi:application --bind "0.0.0.0:${PORT:-3000}"
</code></pre>
<p>For Flask, where <code>app.py</code> exports <code>app</code>:</p>
<pre><code class="language-bash">gunicorn app:app --bind "0.0.0.0:${PORT:-3000}"
</code></pre>
<p>For FastAPI, where <code>main.py</code> exports <code>app</code>:</p>
<pre><code class="language-bash">uvicorn main:app --host 0.0.0.0 --port "${PORT:-3000}"
</code></pre>
<p>These commands expect a shell to expand the port variable. A JSON-array Docker <code>CMD</code> does not expand it automatically. Either use the runtime's documented variable support, a shell wrapper, or a small program that reads the environment.</p>
<p>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.</p>
<h2>A minimal FastAPI container</h2>
<p>For an application with <code>main.py</code> and a locked <code>requirements.txt</code> containing FastAPI and Uvicorn, this Dockerfile starts one server process:</p>
<pre><code class="language-dockerfile">FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd --create-home appuser
USER appuser
EXPOSE 3000
CMD ["sh", "-c", "exec uvicorn main:app --host 0.0.0.0 --port ${PORT:-3000}"]
</code></pre>
<p>Choose a Python version compatible with your project. Put <code>.env</code>, local virtual environments, caches and Git data in <code>.dockerignore</code>. This example does not install extra operating-system packages; add only those your dependencies need.</p>
<p>On Lizard, you can bring a Dockerfile or use the source-build path. The <a href="https://lizard.build/docs/deploy/">deployment guide</a> explains the options. Your app should expose a small health endpoint, and you should also test an endpoint that exercises its real dependencies.</p>
<h2>A FastAPI deployment checked on Lizard</h2>
<p>Our <a href="https://github.com/lizard-build/fastapi-example">public FastAPI example</a> has a dated deployment record from 9 September 2026. The test used <a href="https://github.com/lizard-build/fastapi-example/tree/df522c15f2771704ec2ab28aadda47293d6ea3b1">commit <code>df522c1</code></a>, built from GitHub in <code>eu-west-lim-a</code>, 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.</p>
<p>The <a href="https://github.com/lizard-build/fastapi-example/blob/main/deployment-checks/2026-09-09.json">published check record</a> reports six passing checks through the public HTTPS URL:</p>
<table>
<thead>
<tr>
<th>Request</th>
<th>Observed result</th>
</tr>
</thead>
<tbody><tr>
<td><code>GET /health</code></td>
<td>HTTP 200 with <code>{"status":"ok"}</code></td>
</tr>
<tr>
<td><code>GET /docs</code></td>
<td>HTTP 200; API interface uses <code>/openapi.json</code></td>
</tr>
<tr>
<td><code>GET /openapi.json</code></td>
<td>HTTP 200; schema contains health and echo routes</td>
</tr>
<tr>
<td><code>POST /echo</code> with a JSON object</td>
<td>HTTP 200 with the same object</td>
</tr>
<tr>
<td><code>POST /echo</code> with a JSON array</td>
<td>HTTP 422 for the invalid body type</td>
</tr>
<tr>
<td><code>GET /no-such-route</code></td>
<td>HTTP 404</td>
</tr>
</tbody></table>
<p>Open the <a href="https://crawl-timber-nt5k.eu-west-lim-a.onlizard.com/health">live health endpoint</a>, try the <a href="https://crawl-timber-nt5k.eu-west-lim-a.onlizard.com/docs">API interface</a>, or follow the <a href="https://lizard.build/docs/framework-guides/fastapi/">FastAPI deployment guide</a>.</p>
<p>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.</p>
<h2>Django, Postgres and Celery need separate checks</h2>
<p>Connect <a href="https://lizard.build/postgres">Managed Postgres</a> 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.</p>
<p>Run Celery as a separate service with the same relevant application code and its own start command. Configure the broker explicitly, for example with <a href="https://lizard.build/redis">Managed Redis</a> if your application uses Redis. Check retries, duplicate-task handling and shutdown before accepting production work.</p>
<p>For a server you operate yourself, use the <a href="https://lizard.build/blog/deploy-django-app-vps">Django VPS walkthrough</a>. For a managed worker, see the <a href="https://lizard.build/docs/deploy/workers/">worker deployment reference</a>.</p>
<h2>Estimate the whole Python application</h2>
<p>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: <a href="https://docs.railway.com/pricing">Railway</a>, <a href="https://render.com/pricing">Render</a>, <a href="https://www.pythonanywhere.com/pricing/">PythonAnywhere</a>.</p>
<p>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 <a href="https://lizard.build/blog/vercel-alternative#where-no-subscription-lowers-the-bill">worked example</a> and <a href="https://lizard.build/pricing">current rates</a>. Purchased credits do not expire, so a quiet month does not require buying another plan.</p>
<h2>FAQ</h2>
<p><strong>Can I host FastAPI on the same services as Django?</strong> Often, but FastAPI needs an ASGI server. Confirm the host supports your start command and any long-lived connections or workers the app uses.</p>
<p><strong>Should I use <code>runserver</code> in production?</strong> No. Use a production WSGI or ASGI server and check the framework's deployment guidance.</p>
<p><strong>Why does the host say my app is unhealthy?</strong> Check the build logs, process exit status, import path, bound interface and expected port. Then check missing variables and dependency connections.</p>
<p><strong>Do I need a Dockerfile?</strong> Not on every host. A source builder can create the image, but you still need correct dependencies and a production start command.</p>
<p><strong>What is the best host for a Python worker?</strong> One that supports the worker's lifecycle and dependencies. Test queue consumption, retries and restart behaviour; an HTTP-only deployment is not enough.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/python-app-hosting">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[How to deploy a Django app on a VPS with Gunicorn and nginx]]></title><description><![CDATA[To deploy a Django app on a VPS, run it with a production server, put a reverse proxy in front, connect a database and configure HTTPS. This guide uses Ubuntu 24.04 LTS, Django 5.2, PostgreSQL, Gunico]]></description><link>https://lizard-build.hashnode.dev/how-to-deploy-a-django-app-on-a-vps-with-gunicorn-and-nginx</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/how-to-deploy-a-django-app-on-a-vps-with-gunicorn-and-nginx</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:49:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/213480c0-305a-4e72-bdb5-1fdaffd05f34.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>To deploy a Django app on a VPS, run it with a production server, put a reverse proxy in front, connect a database and configure HTTPS.</strong> This guide uses Ubuntu 24.04 LTS, Django 5.2, PostgreSQL, Gunicorn, systemd and nginx. Adapt the paths and module name to your project.</p>
<p>You need a VPS with SSH and sudo access, a domain pointed at it, a working Django repository and a dependency lock or pinned <code>requirements.txt</code>. The example uses <code>example.com</code>, <code>/srv/myapp</code> and <code>myproject.wsgi:application</code> as placeholders. It covers a WSGI application; use an ASGI setup if your app requires it. References checked on 9 September 2026.</p>
<h2>1. Prepare the server and application user</h2>
<p>Install the base packages on the new Ubuntu server:</p>
<pre><code class="language-bash">sudo apt update
sudo apt install -y python3-venv python3-dev build-essential libpq-dev postgresql nginx git
sudo adduser --system --group --home /srv/myapp django
sudo install -d -o django -g django /srv/myapp
</code></pre>
<p>The <code>django</code> user runs the application without root privileges. Keep administrative SSH access with your normal account. Allow your SSH connection in the firewall before enabling firewall rules, and permit inbound HTTP and HTTPS. Keep PostgreSQL reachable only where the app needs it.</p>
<h2>2. Create PostgreSQL credentials</h2>
<p>Create a database role with a password you choose at the prompt, then create its database:</p>
<pre><code class="language-bash">sudo -u postgres createuser --pwprompt myapp
sudo -u postgres createdb --owner=myapp myapp
</code></pre>
<p>The application will connect to <code>127.0.0.1</code>, which uses password authentication under the usual Ubuntu PostgreSQL setup. Confirm your <code>pg_hba.conf</code> if the connection fails. Do not expose the database port publicly to solve a local authentication problem.</p>
<h2>3. Install the application and dependencies</h2>
<p>Replace the example repository URL. For a private repository, arrange read access without putting a token into the URL or committed files.</p>
<pre><code class="language-bash">sudo -u django git clone https://github.com/your-org/your-app.git /srv/myapp/app
sudo -u django python3 -m venv /srv/myapp/venv
sudo -u django /srv/myapp/venv/bin/pip install -r /srv/myapp/app/requirements.txt
</code></pre>
<p>Your requirements must include the Django version, Gunicorn and the PostgreSQL driver your project uses. Use the project's existing package manager if it has a different lockfile. Confirm that the settings module and WSGI import path match your repository.</p>
<h2>4. Load production settings explicitly</h2>
<p>Create <code>/etc/myapp.env</code> using your editor. Store real values there, not in Git. This example assumes your settings read these names:</p>
<pre><code class="language-text">DJANGO_SECRET_KEY='replace-with-a-long-random-secret'
DJANGO_ALLOWED_HOSTS='example.com'
DJANGO_CSRF_TRUSTED_ORIGINS='https://example.com'
DB_NAME='myapp'
DB_USER='myapp'
DB_PASSWORD='replace-with-the-password-you-set'
DB_HOST='127.0.0.1'
DB_PORT='5432'
</code></pre>
<p>Use simple quoted values compatible with both a shell and systemd's environment-file syntax. Protect the file:</p>
<pre><code class="language-bash">sudo chown root:django /etc/myapp.env
sudo chmod 640 /etc/myapp.env
</code></pre>
<p>In the production settings, configure the matching reads. This fragment assumes <code>BASE_DIR</code> already exists:</p>
<pre><code class="language-python">import os

DEBUG = False
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
ALLOWED_HOSTS = os.environ['DJANGO_ALLOWED_HOSTS'].split(',')
CSRF_TRUSTED_ORIGINS = os.environ['DJANGO_CSRF_TRUSTED_ORIGINS'].split(',')
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['DB_NAME'],
        'USER': os.environ['DB_USER'],
        'PASSWORD': os.environ['DB_PASSWORD'],
        'HOST': os.environ['DB_HOST'],
        'PORT': os.environ['DB_PORT'],
    }
}
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
</code></pre>
<p>Keep uploaded media separate from collected static assets. Choose durable storage, a backup policy and access controls appropriate for the uploaded content. See Django's <a href="https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/">deployment checklist</a>.</p>
<h2>5. Check settings and prepare the database</h2>
<p>Run management commands as the app user with the same environment as the service:</p>
<pre><code class="language-bash">sudo -u django bash -c 'set -a; . /etc/myapp.env; set +a; cd /srv/myapp/app; /srv/myapp/venv/bin/python manage.py check --deploy'
sudo -u django bash -c 'set -a; . /etc/myapp.env; set +a; cd /srv/myapp/app; /srv/myapp/venv/bin/python manage.py migrate --noinput'
sudo -u django bash -c 'set -a; . /etc/myapp.env; set +a; cd /srv/myapp/app; /srv/myapp/venv/bin/python manage.py collectstatic --noinput'
</code></pre>
<p>Review the deployment warnings. HTTPS-related settings come after the proxy has a working certificate. For an existing production database, take and test a backup before applying schema changes. Run migrations once as part of the release, rather than in each Gunicorn worker.</p>
<h2>6. Run Gunicorn under systemd</h2>
<p>Create <code>/etc/systemd/system/myapp.service</code>:</p>
<pre><code class="language-ini">[Unit]
Description=Django application
After=network.target postgresql.service

[Service]
User=django
Group=django
WorkingDirectory=/srv/myapp/app
EnvironmentFile=/etc/myapp.env
ExecStart=/srv/myapp/venv/bin/gunicorn myproject.wsgi:application --bind 127.0.0.1:8001 --workers 2 --access-logfile - --error-logfile -
Restart=on-failure
RestartSec=5
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
</code></pre>
<p>The two-worker value is an initial example, not a sizing formula. Measure memory, database connections and response time before increasing it. Gunicorn binds to loopback so visitors reach the app through nginx.</p>
<pre><code class="language-bash">sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp --no-pager
curl -I -H 'Host: example.com' http://127.0.0.1:8001/
</code></pre>
<p>Use <code>journalctl -u myapp</code> for process errors. An import failure usually points to the module path, working directory or dependency installation. See <a href="https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/">Django's WSGI guidance</a> and <a href="https://gunicorn.org/">Gunicorn</a>.</p>
<h2>7. Put nginx in front</h2>
<p>Create <code>/etc/nginx/sites-available/myapp</code>:</p>
<pre><code class="language-nginx">server {
    listen 80;
    server_name example.com;

    location /static/ {
        alias /srv/myapp/app/staticfiles/;
    }

    location / {
        proxy_pass http://127.0.0.1:8001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
</code></pre>
<p>Enable the new site once, test the configuration and reload:</p>
<pre><code class="language-bash">sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
sudo nginx -t
sudo systemctl reload nginx
</code></pre>
<p>Confirm nginx can read the collected static files and traverse their parent directories. Keep any default site configuration consistent with your other hosted domains; do not remove unrelated sites. The <a href="https://nginx.org/en/docs/beginners_guide.html">nginx guide</a> explains reverse-proxy and static-file configuration.</p>
<h2>8. Add HTTPS and recheck Django</h2>
<p>Install a certificate using your chosen ACME client and its current instructions. With Certbot installed for nginx, the certificate request for this example is:</p>
<pre><code class="language-bash">sudo certbot --nginx -d example.com
sudo certbot renew --dry-run
</code></pre>
<p>Use the <a href="https://certbot.eff.org/instructions">Certbot instructions</a> for installation on your OS. Verify domain DNS, HTTP reachability and renewal before considering TLS complete.</p>
<p>Once HTTPS works, set these Django options:</p>
<pre><code class="language-python">SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
</code></pre>
<p>Trust the forwarded-protocol header only because nginx controls it and Gunicorn is not directly exposed. Restart the service, rerun <code>check --deploy</code>, and test login and a form submission over HTTPS. Review HSTS separately after verifying HTTPS on every affected host.</p>
<h2>9. Make future releases repeatable</h2>
<p>For each release, choose a reviewed revision, install its locked dependencies, apply any compatible migration, collect static files and restart the service. Test the live application and watch errors after the change.</p>
<p>Keep the previous revision available, but remember that reverting code may not undo a database migration. Document the recovery path for schema changes and test restoring data into a separate database.</p>
<p>If the app uses Celery, give it a separate service definition, command and broker configuration. Do not run the worker as a background shell command inside the Gunicorn service.</p>
<h2>When to choose managed hosting instead</h2>
<p>A VPS is reasonable when you want server control and can maintain it. If you want to hand off more host operations, compare <a href="https://lizard.build/blog/python-app-hosting">Python app hosting</a> options. On Lizard, the application can use <a href="https://lizard.build/postgres">Managed Postgres</a> and a separate worker, while you keep control of its code and configuration.</p>
<p>The <a href="https://lizard.build/docs/deploy/">deployment documentation</a> covers that route. You still need correct Django settings, a release process and a verified data-recovery plan.</p>
<h2>FAQ</h2>
<p><strong>Why do I get a 502 error?</strong> Check whether Gunicorn is running and listening on <code>127.0.0.1:8001</code>, then inspect its logs and nginx's error log. A reverse proxy cannot fix a process that fails to start.</p>
<p><strong>Why are static files missing?</strong> Check <code>STATIC_ROOT</code>, the output of <code>collectstatic</code>, nginx's alias path and file permissions. Uploaded media requires a separate setup.</p>
<p><strong>Can I use SQLite instead of PostgreSQL?</strong> Some applications can, but assess concurrency, persistence and backup requirements. Do not change the database engine without testing your application's workload.</p>
<p><strong>Is a successful deployment check enough?</strong> No. Test a real user flow, database writes, file access and recovery. Configuration checks catch only part of production behaviour.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/deploy-django-app-vps">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes alternatives: choose the control you need]]></title><description><![CDATA[A Kubernetes alternative can be a managed app host, another scheduler, or a simpler way to deploy containers to servers. Choose by what you want to stop managing. If you only need an API, a worker and]]></description><link>https://lizard-build.hashnode.dev/kubernetes-alternatives-choose-the-control-you-need</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/kubernetes-alternatives-choose-the-control-you-need</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:48:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/aac27477-f2c3-4b8b-8108-f63337316eee.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>A Kubernetes alternative can be a managed app host, another scheduler, or a simpler way to deploy containers to servers.</strong> Choose by what you want to stop managing. If you only need an API, a worker and a database, an application platform may cover the task. If you need custom scheduling and infrastructure policy, evaluate a scheduler or keep Kubernetes.</p>
<p>This guide comes from Lizard. We checked the linked product documentation on 9 September 2026. The options below solve different problems; they are not interchangeable implementations of Kubernetes.</p>
<h2>Identify the work you want to remove</h2>
<p>Write down the tasks your team handles today: cluster upgrades, ingress, certificates, storage, networking, permissions, deployment policies and monitoring. Then identify which tasks exist because your application needs them and which exist because you chose a cluster.</p>
<p>That distinction prevents an expensive migration to another system with the same operating burden. A managed control plane can help with cluster administration while leaving workload and networking decisions to you. A managed app platform can take over more of those decisions while offering fewer low-level controls.</p>
<h2>Seven approaches to compare</h2>
<table>
<thead>
<tr>
<th>Approach</th>
<th>Fits</th>
<th>Responsibility you keep</th>
</tr>
</thead>
<tbody><tr>
<td>Managed app platform: Lizard, Railway or Render</td>
<td>Web services and workers with standard deployment needs</td>
<td>App settings, data requirements and release checks</td>
</tr>
<tr>
<td>ECS with Fargate</td>
<td>Container workloads in an AWS account</td>
<td>Task configuration, IAM, networking and surrounding AWS resources</td>
</tr>
<tr>
<td>Cloud Run</td>
<td>HTTP services, jobs and supported worker workloads</td>
<td>Resource type, scaling settings and cloud integrations</td>
</tr>
<tr>
<td>Nomad</td>
<td>A team that wants a scheduler with its own operating model</td>
<td>Scheduler operation and dependent infrastructure</td>
</tr>
<tr>
<td>Docker Swarm</td>
<td>Docker-oriented multi-host services</td>
<td>Hosts, managers, networking and storage</td>
</tr>
<tr>
<td>Kamal</td>
<td>Containerized web apps on servers you control</td>
<td>Servers, capacity, backup and recovery</td>
</tr>
<tr>
<td>Coolify or Dokploy</td>
<td>A deployment interface over your own infrastructure</td>
<td>The underlying machines and data durability</td>
</tr>
</tbody></table>
<h2>Managed application platforms</h2>
<p>This is the first model to test if your requirements are a public API, a few workers and a database. Lizard exposes that workflow through the <a href="https://lizard.build/cli">Lizard CLI</a>, with <a href="https://lizard.build/postgres">Managed Postgres</a> and <a href="https://lizard.build/redis">Managed Redis</a> available as data services.</p>
<p>The trade-off is deliberate: you use the platform's supported controls. If you depend on a particular operator, admission policy or custom network setup, verify that requirement before moving. See the <a href="https://lizard.build/blog/best-paas-providers">PaaS comparison</a> for other providers and billing models.</p>
<h2>ECS and Cloud Run</h2>
<p><a href="https://aws.amazon.com/fargate/pricing/">Fargate</a> can run supported ECS and EKS workloads without making you operate the worker hosts. In ECS, you still define tasks and services and account for the AWS resources around them. Include networking, load balancing and logs in the budget.</p>
<p><a href="https://docs.cloud.google.com/run/docs/overview/what-is-cloud-run">Cloud Run</a> provides distinct resource types for services, jobs and worker pools. Match each process to the right type. Do not assume that an HTTP service's scaling and billing settings also describe a continuous queue consumer.</p>
<p>These options can fit a team already comfortable with the relevant cloud's identity and networking model. They are less attractive if avoiding that model is the reason for leaving Kubernetes.</p>
<h2>Nomad and Docker Swarm</h2>
<p><a href="https://developer.hashicorp.com/nomad/docs">Nomad</a> is another workload scheduler. Evaluate its job model, supported drivers and operational requirements against your workloads. Choosing a different scheduler does not remove the need for service discovery, persistent data and recovery planning.</p>
<p><a href="https://docs.docker.com/engine/swarm/">Docker Swarm</a> is built into Docker Engine and manages services across a swarm of hosts. It may feel familiar to a team already using Docker, but you still need to maintain the hosts and protect manager availability. Test how persistent data and placement behave when a host fails.</p>
<p>Neither should be selected merely because a demonstration needs fewer configuration lines. A useful evaluation includes upgrades, failed hosts and a real deployment rollback.</p>
<h2>Kamal, Coolify and Dokploy</h2>
<p><a href="https://kamal-deploy.org/">Kamal</a> deploys containerized web applications to servers you provide. It can suit a team that wants a repeatable deploy process without adopting a general cluster scheduler.</p>
<p><a href="https://coolify.io/">Coolify</a> and <a href="https://dokploy.com/">Dokploy</a> provide deployment workflows on infrastructure you control. Compare their current support for your app, database, domains and deployment source.</p>
<p>With these tools, someone still owns the machine. Plan OS updates, access control, monitoring, disk space and tested backups. The <a href="https://lizard.build/blog/deploy-django-app-vps">Django VPS guide</a> gives a concrete example of those tasks.</p>
<h2>When keeping Kubernetes is reasonable</h2>
<p>Keep it on the shortlist when your applications need controls your team already uses well: custom resources, sophisticated workload policy, a shared internal deployment platform or infrastructure features with no simple replacement.</p>
<p>Also consider how much working automation and knowledge you would discard. A migration is useful when it reduces a real cost or limitation. Reducing the number of YAML files is not enough if the replacement adds manual work elsewhere.</p>
<h2>Test a move without copying every abstraction</h2>
<p>Start from the application contract: image, start command, configuration, port, health check, data and shutdown behaviour. Map those needs to the new host. You do not need an equivalent object for every Kubernetes object if the provider handles the same responsibility for you.</p>
<p>Move one stateless service first. Verify the user flow, error behaviour and resource consumption. Move workers and data only after testing their lifecycle. Preserve the old route until you have a rollback plan that includes new database writes.</p>
<p>Compare operating time as well as invoices. A provider can reduce cluster work while charging more for compute; a VPS can reduce the invoice while leaving more work with your team.</p>
<h2>FAQ</h2>
<p><strong>Do small applications need Kubernetes?</strong> Not by default. Choose it when its controls solve your requirements and your team can operate it. A standard API and worker can often use a simpler deployment model.</p>
<p><strong>Is managed Kubernetes the same as a PaaS?</strong> No. A managed cluster usually still leaves workload configuration with you. A PaaS provides a more application-focused contract, though the exact responsibility split varies.</p>
<p><strong>Can I move without changing application code?</strong> Sometimes. Configuration, storage and cloud-specific dependencies still need review. Test the app contract rather than assuming image portability means operational equivalence.</p>
<p><strong>Which alternative costs least?</strong> Compare your workload and the work required to operate it. Include databases, network, storage, monitoring and recovery; do not rank products by the control-plane price alone.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/kubernetes-alternatives">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[E2B sandboxes: pricing, lifecycle and alternatives]]></title><description><![CDATA[An E2B sandbox gives an application a remote environment in which to execute code and work with files. To choose a sandbox provider, compare the execution API, session lifetime, concurrency, network c]]></description><link>https://lizard-build.hashnode.dev/e2b-sandboxes-pricing-lifecycle-and-alternatives</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/e2b-sandboxes-pricing-lifecycle-and-alternatives</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:48:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/b73df1c4-53ec-42f0-8f24-111611c2eea8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>An E2B sandbox gives an application a remote environment in which to execute code and work with files.</strong> To choose a sandbox provider, compare the execution API, session lifetime, concurrency, network controls, retained state and complete cost. A low CPU rate alone does not tell you whether an agent can finish its task reliably.</p>
<p>This guide comes from Lizard. We checked the linked pricing and SDK documentation on 9 September 2026. It compares documented behaviour and an illustrative calculation, not measured provider performance.</p>
<h2>What a sandbox adds to an agent</h2>
<p>An agent can generate a program without having anywhere appropriate to run it. A sandbox supplies an execution environment separate from the application that coordinates the agent.</p>
<p>The coordinator sends code or commands, collects output and files, and decides what to do next. It must also handle a failed command, a timed-out session and cleanup. Isolation is useful, but access to credentials, external systems and private data still requires a deliberate policy.</p>
<p>E2B's <a href="https://docs.e2b.dev/quickstart">quickstart</a> demonstrates code execution and file access through its SDK. Start with a small task and inspect both output and errors before connecting it to a larger agent loop.</p>
<h2>E2B pricing: separate the plan from runtime</h2>
<p>E2B lists a free Hobby plan with a one-time $100 credit, sessions up to one hour and 20 concurrent sandboxes. Pro lists a $150 monthly base plus usage, sessions up to 24 hours and a higher concurrency allowance. The plan fee is not the runtime bill. <a href="https://e2b.dev/pricing">E2B pricing</a>.</p>
<p>For an illustrative workload of 1,000 total running hours at 2 vCPUs and 4 GiB, use the published per-second rates:</p>
<pre><code class="language-text">CPU:    1,000 × 3,600 × 2 × $0.000014 = $100.80
Memory: 1,000 × 3,600 × 4 × $0.0000045 = $64.80
Runtime subtotal: $165.60
With the $150 Pro base: $315.60 before tax and other charges
</code></pre>
<p>This E2B example excludes trial credits, tax, paid concurrency add-ons and other usage. Total runtime spans all sandboxes; check session and concurrency limits separately.</p>
<h3>The same compute example on Lizard</h3>
<p>Assume 1,000 total running hours with Lizard’s meter reading 2 vCPUs and 4 GB of memory throughout. Lizard’s memory meter uses 1,024 MiB per GB, matching the GiB quantity in the E2B example.</p>
<pre><code class="language-text">CPU:    1,000 × 3,600 × 2 × $0.000006948 = $50.0256
Memory: 1,000 × 3,600 × 4 × $0.000003474 = $50.0256
Compute subtotal: $100.0512, or about $100.05
Monthly subscription: $0
</code></pre>
<p><strong>Lizard has the lower compute cost in this example: $100.05 versus E2B’s $165.60.</strong> With E2B Pro’s monthly base, the comparison is $100.05 plus Lizard payment fees versus $315.60, before tax. Neither figure includes extra storage, traffic or other services. E2B Hobby has no base fee; do not add Pro’s fee when Hobby meets the requirements.</p>
<p>This is a rate calculation, not a measured performance result. Check limits and retained state before choosing a provider. Sources checked 15 September 2026: <a href="https://e2b.dev/pricing">E2B pricing</a> and <a href="https://lizard.build/pricing">Lizard pricing</a>.</p>
<h2>Compare the operating contract</h2>
<table>
<thead>
<tr>
<th>Provider</th>
<th>What to examine first</th>
<th>Questions for your trial</th>
</tr>
</thead>
<tbody><tr>
<td>E2B</td>
<td>Code execution, templates and session plans</td>
<td>Can your task finish within the session and concurrency limits?</td>
</tr>
<tr>
<td>Daytona</td>
<td>Sandbox lifecycle and resource billing</td>
<td>What happens to storage and charges when a session stops?</td>
</tr>
<tr>
<td>Modal</td>
<td>Sandbox resources and integration with the rest of Modal</td>
<td>How do startup, execution and stored outputs fit your workflow?</td>
</tr>
<tr>
<td>Lizard</td>
<td>Lizard SDK, project-scoped Sandboxes and pause behaviour</td>
<td>What survives pause, expiry and a host failure?</td>
</tr>
</tbody></table>
<p><a href="https://www.daytona.io/pricing">Daytona</a> lists compute, memory and storage separately. <a href="https://modal.com/docs/guide/sandboxes">Modal's sandbox documentation</a> describes execution, networking and lifecycle controls. Use those specific docs when comparing behaviour; a common word such as “pause” need not mean the same storage guarantee.</p>
<h2>Pause is not a backup</h2>
<p>For Lizard Sandboxes, pause freezes vCPUs and retains memory and running processes in the host's RAM. It does not write a durable disk snapshot of the whole machine. A host failure can therefore lose that paused state.</p>
<p>The sandbox lifetime also continues while paused. The Lizard SDK documents a default five-minute timeout; opting out of expiration is a separate setting. Save important outputs outside the sandbox and set a lifetime that fits the task. See the <a href="https://github.com/lizard-build/lizard-sdk">Lizard SDK</a> and <a href="https://lizard.build/docs/sandboxes/">Sandboxes documentation</a>.</p>
<p>Do not build recovery around a claim such as “resume instantly with everything saved” unless you have checked the exact failure case. Test process failure, expired sessions and unavailable hosts as separate events.</p>
<h2>A small Lizard SDK example</h2>
<p>Install <code>@lizard-build/sdk</code> in a Node project and provide <code>LIZARD_API_KEY</code> through the environment. Replace <code>my-project</code> with a project you can use. This example creates a short-lived environment, runs one command and releases it in a <code>finally</code> block:</p>
<pre><code class="language-javascript">import { Lizard } from '@lizard-build/sdk';

const client = new Lizard({ project: 'my-project' });
const sandbox = await client.create('base', { timeoutMs: 300000 });
try {
  const result = await sandbox.process.exec('node -e "console.log(6 * 7)"');
  if (result.exitCode !== 0) throw new Error(result.stderr);
  console.log(result.stdout);
} finally {
  await sandbox.kill();
}
</code></pre>
<p>The command should print <code>42</code>. The example illustrates the API and cleanup pattern; it is not a throughput or startup benchmark. Production code should also handle a failed cleanup request and preserve outputs it needs before termination.</p>
<h2>Test the workflow, not just the first command</h2>
<p>Use the same task and dependency set on each candidate. Record time to a usable environment, command execution time, failures, resource consumption and retained outputs. Separate image preparation from session startup so the comparison measures the same stages.</p>
<p>Then test the cases an agent will encounter: a command that fails, a process that hangs, concurrent sessions, a large output and a session that expires. Confirm the coordinator records enough information to retry safely.</p>
<p>If the output is a production application, deploy it as a service with a normal release process. A sandbox URL and a successful test run are not a substitute for deployment configuration, monitoring and durable data. Our <a href="https://lizard.build/blog/deploy-from-claude-code">Claude Code deployment guide</a> covers that next step.</p>
<h2>FAQ</h2>
<p><strong>Is E2B free?</strong> Its Hobby plan has no base fee and includes a one-time credit, while runtime has published usage rates. Check the applicable limits and what happens after credit is used.</p>
<p><strong>Is Lizard cheaper for the example above?</strong> Yes. Its compute subtotal is $100.05, compared with E2B’s $165.60 in usage or $315.60 with Pro’s base. Add Lizard payment fees and any storage or traffic on either host. Lizard does not require a monthly subscription.</p>
<p><strong>Does E2B Pro include unlimited runtime?</strong> No. Its base fee and usage charges are separate.</p>
<p><strong>Does pausing Lizard Sandboxes make their state durable?</strong> No. Paused memory stays in host RAM, and the lifetime continues. Export important data and plan for expiration or host failure.</p>
<p><strong>Which provider is fastest?</strong> This guide does not establish a speed ranking. Measure the same image, dependencies, region and readiness condition, and report repeated results rather than a single successful start.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/e2b-sandbox">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Heroku alternatives: how to choose and migrate safely]]></title><description><![CDATA[For a Heroku alternative, start with the processes and add-ons your app actually uses. Lizard, Railway and Render are candidates for a web process, a separate worker and a database. Cloud Run fits a d]]></description><link>https://lizard-build.hashnode.dev/heroku-alternatives-how-to-choose-and-migrate-safely</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/heroku-alternatives-how-to-choose-and-migrate-safely</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:47:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/8dedd1c4-4f8c-4f1c-8cf7-08d75251a60b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>For a Heroku alternative, start with the processes and add-ons your app actually uses.</strong> Lizard, Railway and Render are candidates for a web process, a separate worker and a database. Cloud Run fits a different resource model. A VPS gives more control and leaves server operations with you.</p>
<p>Heroku remains an option too. Its move to sustaining engineering does not mean existing applications have to leave immediately. This Lizard comparison checks the linked sources as of 9 September 2026.</p>
<h2>What changed at Heroku</h2>
<p>On 6 February 2026, Heroku announced a focus on stability, security, reliability and support rather than new features. The notice says credit-card customers, including new ones, can keep using the product. It does not announce a general shutdown. <a href="https://www.heroku.com/blog/an-update-on-heroku/">Read Heroku's statement</a>.</p>
<p>The decision to move should follow a concrete requirement: cost, a needed feature, deployment constraints, or your view of the product's direction. An unsupported claim that the service is closing creates urgency without helping you plan.</p>
<h2>Map your Heroku app before comparing hosts</h2>
<table>
<thead>
<tr>
<th>What you have</th>
<th>What the replacement must provide</th>
<th>Migration check</th>
</tr>
</thead>
<tbody><tr>
<td><code>web</code> process</td>
<td>An HTTP service and routing</td>
<td>Port, proxy headers, health checks and timeouts</td>
</tr>
<tr>
<td><code>worker</code> process</td>
<td>A separate worker runtime</td>
<td>Queue access, retries and safe shutdown</td>
</tr>
<tr>
<td>Release command</td>
<td>A controlled migration step</td>
<td>Exactly when it runs and what happens on failure</td>
</tr>
<tr>
<td>Config vars</td>
<td>Service configuration and secrets</td>
<td>Variable names, scope and credential rotation</td>
</tr>
<tr>
<td>Heroku Postgres</td>
<td>A compatible PostgreSQL target</td>
<td>Extensions, versions, roles and restore time</td>
</tr>
<tr>
<td>Redis or another add-on</td>
<td>A compatible managed or self-run service</td>
<td>Data format, connection settings and ownership</td>
</tr>
<tr>
<td>Review apps and pipelines</td>
<td>A deployment and promotion workflow</td>
<td>How test environments differ from production</td>
</tr>
</tbody></table>
<p>Do this inventory before choosing by the smallest web-instance price. The worker, database and add-ons often decide which option is practical.</p>
<h2>Compare the closest operating models</h2>
<p><strong>Lizard:</strong> choose it for web services and workers with no monthly subscription or per-service plan fee. Purchased credits do not expire. This suits small apps and workloads that vary through the month. Add <a href="https://lizard.build/postgres">Managed Postgres</a> and <a href="https://lizard.build/redis">Managed Redis</a> at resource rates, and operate them through the <a href="https://lizard.build/cli">Lizard CLI</a>. The <a href="https://lizard.build/blog/vercel-alternative#where-no-subscription-lowers-the-bill">small-app calculation</a> shows how usage can stay below a paid instance minimum. Configure connection variables explicitly.</p>
<p><strong>Railway:</strong> evaluate its project and service workflow if you want to manage several parts of an app together. Railway meters consumption; a paid plan includes credit toward usage. It is not a fixed-price replica of a dyno. <a href="https://docs.railway.com/pricing">Railway billing</a>.</p>
<p><strong>Render:</strong> evaluate its web and worker instance plans when selected capacity is easier for your team to budget. Price the database and any workspace requirements as well. <a href="https://render.com/pricing">Render pricing</a>.</p>
<p><strong>Cloud Run:</strong> identify whether each process belongs in a Service, Job or worker pool. A direct copy of a Procfile does not define the Cloud Run architecture. <a href="https://docs.cloud.google.com/run/docs/overview/what-is-cloud-run">Cloud Run resource types</a>.</p>
<p><strong>A VPS with Coolify or another deployment tool:</strong> consider this when you want control of the host and can maintain it. A deployment interface does not take over patching, storage durability or disaster recovery. The <a href="https://lizard.build/blog/deploy-django-app-vps">Django VPS guide</a> shows the work a server-based setup includes.</p>
<p>For more options, use the <a href="https://lizard.build/blog/best-paas-providers">PaaS comparison</a>.</p>
<h2>Compare the full monthly bill</h2>
<p>Heroku's published Cedar prices include Basic at \(7/month and Standard-1X at \)25/month. These are dyno prices, not a quote for a web app, worker and database together. Check the foundation and plan your app uses. <a href="https://www.heroku.com/pricing/">Heroku pricing</a>.</p>
<p>On any replacement, include every process, database, retained file, network charge and required plan feature. For a metered plan, measure consumption and account for its credit. For fixed instances, count all billed instances and their operating time.</p>
<p>Keep the same workload in the comparison. Do not assume that a replacement uses a fraction of the CPU or memory without running it. Also include a period when old and new systems operate together during the migration.</p>
<h2>Move a copy before changing traffic</h2>
<ol>
<li>Export a configuration inventory without placing secret values in a public document. Pin runtime and dependency versions.</li>
<li>Deploy the web process and worker as separate test services. Verify their start commands and shutdown behaviour.</li>
<li>Restore a database backup into a test database. Check PostgreSQL extensions, row counts and application-level reads and writes.</li>
<li>Test login, email, uploads, payments in test mode and background jobs. Check callback domains and webhook URLs.</li>
<li>Decide how to handle writes during cutover. A maintenance window is often easier to reason about than two writable copies.</li>
<li>Take the final backup or finish replication, switch application connections, then change traffic. Watch errors, jobs and database activity.</li>
</ol>
<p>Keep a rollback plan that includes data written after the switch. Pointing DNS back does not move those writes into the old database.</p>
<p>On Lizard, start from the <a href="https://lizard.build/docs/guides/github-integration/">GitHub deployment guide</a> and <a href="https://lizard.build/docs/variables/references/">variable references</a>. Read the worker settings separately from the web service settings.</p>
<h2>When staying is reasonable</h2>
<p>Staying on Heroku can be the lower-risk choice when the app meets its requirements, your team knows the operating model, and moving would consume time better spent elsewhere. A mature app can depend on add-ons, release steps and internal practices that no feature table captures.</p>
<p>Write down the change you want from a new host. If a trial does not demonstrate it, a migration has not yet earned its cost.</p>
<h2>FAQ</h2>
<p><strong>Is Heroku shutting down?</strong> The cited announcement does not say that. It describes sustaining engineering and continued service for credit-card customers. Assess the current statement and your contract rather than treating it as a shutdown deadline.</p>
<p><strong>Can I keep my Procfile?</strong> It is a useful inventory of process commands. Check which entries your chosen host detects automatically and configure the others as separate services or jobs.</p>
<p><strong>Can I move Heroku Postgres to another PostgreSQL service?</strong> Often, but test the version, extensions, roles and restore process. The application must work against the restored data before you switch production traffic.</p>
<p><strong>Will migration reduce costs?</strong> Only a complete estimate and a representative trial can establish that. Include the database, workers, storage, traffic and temporary overlap between hosts.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/heroku-alternatives">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Container as a service: how it works and what it costs]]></title><description><![CDATA[Container as a service, or CaaS, means running containerized applications on infrastructure a provider operates. You supply an image or source code that becomes an image. The service handles some comb]]></description><link>https://lizard-build.hashnode.dev/container-as-a-service-how-it-works-and-what-it-costs</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/container-as-a-service-how-it-works-and-what-it-costs</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:47:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/1456eb5b-b873-4981-a12a-46ecd966cd43.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Container as a service, or CaaS, means running containerized applications on infrastructure a provider operates.</strong> You supply an image or source code that becomes an image. The service handles some combination of scheduling, networking, scaling and host maintenance. You still own your application, its configuration and its data requirements.</p>
<p>The term covers several operating models. A managed Kubernetes cluster, a serverless HTTP container and an always-running application service can all appear in CaaS comparisons, but they leave very different work to your team.</p>
<p>This guide comes from Lizard. Product references were checked on 9 September 2026.</p>
<h2>What happens when you deploy a container</h2>
<p>A typical deployment has four parts: build the image, store it in a registry, start an instance, and route traffic to it. The provider then observes the instance and handles restarts or scaling according to its rules.</p>
<p>Your image must start the right process, listen on the expected interface and port, and provide configuration in the form the application understands. The platform cannot infer whether a successful HTTP response means the application can write to its database.</p>
<p>For a worker, routing may not be needed at all. The important checks become queue access, process lifetime, retries and graceful shutdown. Treat web and worker runtimes as separate requirements.</p>
<h2>CaaS, PaaS, FaaS and IaaS compared</h2>
<table>
<thead>
<tr>
<th>Model</th>
<th>What you usually provide</th>
<th>What you still need to decide</th>
</tr>
</thead>
<tbody><tr>
<td>IaaS: infrastructure as a service</td>
<td>A virtual machine and its software configuration</td>
<td>OS maintenance, runtime, routing, deployment and recovery</td>
</tr>
<tr>
<td>CaaS: container as a service</td>
<td>A container image and runtime settings</td>
<td>Application lifecycle, data, secrets and resource sizing</td>
</tr>
<tr>
<td>PaaS: platform as a service</td>
<td>Source code or an image</td>
<td>App configuration, dependencies, data and release process</td>
</tr>
<tr>
<td>FaaS: function as a service</td>
<td>A handler or supported application entry point</td>
<td>Event semantics, duration limits, state and retries</td>
</tr>
</tbody></table>
<p>These labels overlap. A PaaS can build a container for you. A function service can accept a container image. Image support alone does not tell you whether a queue consumer can run continuously. Our <a href="https://lizard.build/blog/best-paas-providers">PaaS guide</a> compares the complete application workflow.</p>
<h2>Three ways providers run your image</h2>
<h3>An application service</h3>
<p>Use this model when you want to run a conventional web process or worker. Lizard is one option; Railway and Render are others. Compare their lifecycle, deployment controls and data services rather than assuming every implementation is identical.</p>
<p>On Lizard, you can deploy from source or supply a Dockerfile. Use the <a href="https://lizard.build/docs/deploy/">deployment documentation</a> to choose the path. Configure non-HTTP services using the <a href="https://lizard.build/docs/deploy/workers/">worker guide</a>.</p>
<h3>A request-driven container</h3>
<p>This model starts or scales instances around incoming work. It can suit APIs with uneven traffic. Check concurrency, request timeouts, idle settings and how the provider bills memory as well as CPU.</p>
<p><a href="https://docs.cloud.google.com/run/docs/overview/what-is-cloud-run">Cloud Run</a> separates Services, Jobs and worker pools. Match the resource type to the process. A batch program that exits and a web server that receives requests should not use the same deployment assumptions.</p>
<h3>A managed scheduler or cluster</h3>
<p>ECS and managed Kubernetes give you more control over how several workloads run. You may still need to design networking, permissions, deployment policies and observability.</p>
<p>For AWS, <a href="https://aws.amazon.com/fargate/pricing/">Fargate</a> removes the need to manage the worker machines for supported ECS or EKS workloads. It does not remove the need to understand the surrounding AWS resources. If cluster operation is the problem you want to avoid, read the <a href="https://lizard.build/blog/kubernetes-alternatives">Kubernetes alternatives guide</a>.</p>
<h2>What container hosting leaves with you</h2>
<p><strong>Data:</strong> decide which data belongs in a database, object storage or an attached volume. A writable filesystem is not proof that files survive replacement of an instance. Test the exact persistence behaviour you rely on.</p>
<p><strong>Secrets:</strong> give each service only the values it needs. Confirm that the application reads those values and fails clearly when one is missing.</p>
<p><strong>Release safety:</strong> a new image may require a schema migration. Plan the order and compatibility of application and database changes, including rollback.</p>
<p><strong>Recovery:</strong> a restart can recover a process. It cannot reconstruct a lost database or repair a bad migration. Test backups and restoration separately.</p>
<p><strong>Capacity:</strong> resource limits prevent one service from consuming everything available to it, but they do not establish throughput. Measure latency, errors and queue depth under representative load.</p>
<h2>How to estimate CaaS cost</h2>
<p>List every billed component: runtime CPU and memory, image storage, builds, persistent storage, internet transfer, load balancing, logs and databases. Include the plan or control-plane fee where one applies.</p>
<p>Then identify the meter. Some products bill allocated capacity for the time an instance runs. Others measure consumption or active request time. A published hourly CPU rate is not comparable until you know what creates a billable hour.</p>
<p>For example, ten containers running concurrently for one hour produce ten container-hours. Ten one-hour runs executed in sequence produce the same runtime total, but they need different concurrency limits. The application may also experience different queue delays. A cost estimate should state both runtime and peak concurrency.</p>
<p>Use <a href="https://aws.amazon.com/fargate/pricing/">AWS Fargate pricing</a>, <a href="https://cloud.google.com/run/pricing">Cloud Run pricing</a>, <a href="https://docs.railway.com/pricing">Railway billing</a> and <a href="https://lizard.build/pricing">Lizard pricing</a> for the relevant meters. Do not reuse one provider's memory or transfer allowance in another provider's calculation.</p>
<h2>A short evaluation plan</h2>
<p>Deploy a representative service with its real start command. Check its health endpoint, database connection and one meaningful user flow. Restart it and confirm how temporary and persistent files behave. Run enough traffic to observe resource use and latency.</p>
<p>Repeat those checks for a worker if the app has one. Then calculate the bill from the observed meters and the plan your team can use. Choose the model that satisfies the lifecycle and recovery requirements with an operating process you understand.</p>
<h2>FAQ</h2>
<p><strong>Does CaaS require Kubernetes?</strong> No. Kubernetes is one way to schedule containers. Application platforms and services such as ECS or Cloud Run can run containers through other interfaces.</p>
<p><strong>Is CaaS the same as Docker hosting?</strong> The terms often overlap. Read the runtime contract: image support, process lifetime, networking, persistent storage and operational responsibility matter more than the label.</p>
<p><strong>Can a container keep uploaded files?</strong> Only if the storage setup provides the durability you need. Use a database, object storage or a suitable attached volume and verify behaviour across redeploys.</p>
<p><strong>Can I deploy without writing a Dockerfile?</strong> Some providers build an image from source. Lizard supports that path through lizardpack. Check the supported language and project layout in the current documentation.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/container-as-a-service">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Best PaaS providers: choose by workload and total cost]]></title><description><![CDATA[The best PaaS provider is the one that runs your complete workload at an acceptable cost and leaves your team with work it can manage. For an API, worker and database, compare Lizard, Railway and Rend]]></description><link>https://lizard-build.hashnode.dev/best-paas-providers-choose-by-workload-and-total-cost</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/best-paas-providers-choose-by-workload-and-total-cost</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:46:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/83f0b4c4-ca21-429a-935e-bd5a95b380d5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>The best PaaS provider is the one that runs your complete workload at an acceptable cost and leaves your team with work it can manage.</strong> For an API, worker and database, compare Lizard, Railway and Render. For request-driven containers, evaluate Cloud Run. For frontend previews, include Vercel. For control over your own machines, consider a VPS with Coolify.</p>
<p>Looking for a provider’s bill? Jump to <a href="#render-pricing-web-service-database-and-workspace">Render pricing</a> or <a href="#flyio-pricing-machines-storage-and-traffic">Fly.io pricing</a>.</p>
<p>We publish this guide at Lizard and include competing options where they fit. Render and Fly.io pricing below was checked on 14 September 2026. Other provider references were checked on 9 September 2026. This is a comparison of documented capabilities, not a benchmark ranking.</p>
<h2>Render pricing: web service, database and workspace</h2>
<p><strong>A small paid Render app with Postgres costs $14.50/month in this example:</strong> \(7 web service + \)6 database compute + $1.50 for 5 GB of database storage, on a $0 Hobby workspace. Rates checked 14 September 2026; USD, before tax and extra usage.</p>
<table>
<thead>
<tr>
<th>Item</th>
<th>Monthly price</th>
</tr>
</thead>
<tbody><tr>
<td>Web service: 0.5 CPU, 512 MB (<code>0.5c-512mb</code>)</td>
<td>$7</td>
</tr>
<tr>
<td>Postgres compute: 0.1 CPU, 256 MB (<code>0.1c-256mb</code>)</td>
<td>$6</td>
</tr>
<tr>
<td>Postgres storage: 5 GB × $0.30</td>
<td>$1.50</td>
</tr>
<tr>
<td>Hobby workspace</td>
<td>$0</td>
</tr>
<tr>
<td>Example total</td>
<td><strong>$14.50</strong></td>
</tr>
</tbody></table>
<p>This assumes one app instance and one database for the full month. A second $7 worker makes it $21.50. Pro adds a $25 workspace fee; storage, bandwidth and builds can add charges. This small database example does not include high availability. <a href="https://render.com/pricing">Render pricing</a>.</p>
<p>Render renamed compute plans in August 2026: <code>0.5c-512mb</code> was “Starter.” The rename did not change its price or specs. Use current plan IDs when checking a quote. <a href="https://render.com/docs/compute-plans">Compute plans</a>.</p>
<p><strong>Can Render be free?</strong> It offers free web services with idle spin-down and usage limits. Free Postgres expires after 30 days. That is useful for a trial; budget a paid database for an app you intend to keep online. <a href="https://render.com/docs/free">Free service limits</a>.</p>
<h2>Fly.io pricing: Machines, storage and traffic</h2>
<p><strong>A Fly.io app costs more than its Machine alone.</strong> For a small SQLite app in Ashburn (<code>iad</code>), a 1 GB <code>shared-cpu-1x</code> Machine running for 30 days is about $5.70. Add a 10 GB volume ($1.50) and 100 GB of public egress from North America ($2): <strong>about $9.20</strong>, before tax and other services.</p>
<table>
<thead>
<tr>
<th>Example item</th>
<th>Cost for 30 days</th>
</tr>
</thead>
<tbody><tr>
<td>One <code>shared-cpu-1x</code> Machine, 1 GB RAM, <code>iad</code></td>
<td>~$5.70</td>
</tr>
<tr>
<td>Volume: 10 GB × $0.15</td>
<td>$1.50</td>
</tr>
<tr>
<td>Public egress: 100 GB × $0.02</td>
<td>$2</td>
</tr>
<tr>
<td>Example total</td>
<td><strong>~$9.20</strong></td>
</tr>
</tbody></table>
<p>Rates checked 14 September 2026. This assumes shared IPv4, no replicas and no separate database service. Compute rates vary by region. Volumes remain billable when Machines stop; stopped Machines also incur root-filesystem storage charges. Dedicated IPv4 costs $2/month. Add snapshot storage and any support or third-party services you use. <a href="https://fly.io/docs/about/pricing/">Fly.io resource pricing</a>.</p>
<p>This SQLite example does not price a managed Postgres app. Fly.io charges separately for its managed database service; select its plan and storage in the <a href="https://fly.io/calculator/">Fly.io calculator</a>. Use the same database and availability requirements before comparing it with the Render example.</p>
<p><strong>Does Fly.io have a free tier?</strong> New accounts get a trial: two total VM hours or seven days, whichever runs out first. Trial Machines stop automatically after five minutes. Adding a card ends the trial and starts billing. Do not use legacy free allowances to estimate a new account. <a href="https://fly.io/docs/about/free-trial/">Fly.io free trial</a>.</p>
<p>For worked budgets and current trial rules, see our <a href="https://lizard.build/blog/fly-io-pricing">Fly.io pricing guide</a>.</p>
<h2>What a PaaS should do for your team</h2>
<p>A platform as a service builds or accepts your application, runs it, and provides tools for deployment and operation. The useful question is how much of your stack it covers.</p>
<p>A web service is only one part of many applications. You may also need a worker, scheduled jobs, a database, uploaded files, logs and a way to restore data. Compare that whole system. A low entry price for one web process does not price a complete SaaS application.</p>
<p>The line between PaaS and <a href="https://lizard.build/blog/container-as-a-service">container as a service</a> is not strict. Many products accept both source repositories and container images.</p>
<h2>A shortlist by use case</h2>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Start here when you need</th>
<th>Cost or operating question to answer</th>
</tr>
</thead>
<tbody><tr>
<td>Lizard</td>
<td>Web services, workers and managed data services through one CLI</td>
<td>What CPU, memory, storage and traffic will all services consume?</td>
</tr>
<tr>
<td>Railway</td>
<td>A project workflow for several services and databases</td>
<td>How much resource use falls above the included plan credit?</td>
</tr>
<tr>
<td>Render</td>
<td>Web services and workers with published instance sizes</td>
<td>What does each instance, database and workspace feature add?</td>
</tr>
<tr>
<td>Google Cloud Run</td>
<td>HTTP containers, batch jobs or worker pools</td>
<td>Which resource type and billing mode fit each process?</td>
</tr>
<tr>
<td>Vercel</td>
<td>Frontend delivery, previews and request-driven compute</td>
<td>What are the runtime limits and account-specific usage terms?</td>
</tr>
<tr>
<td>Fly.io</td>
<td>Machines placed in the regions you choose</td>
<td>What do the selected machine, storage and network cost there?</td>
</tr>
<tr>
<td>DigitalOcean App Platform</td>
<td>Managed application deployment in a DigitalOcean account</td>
<td>Which app components and managed databases bill separately?</td>
</tr>
<tr>
<td>Northflank</td>
<td>Services, jobs and a configurable deployment workflow</td>
<td>What capacity will remain allocated?</td>
</tr>
<tr>
<td>Heroku</td>
<td>An existing Heroku workflow or integrations worth retaining</td>
<td>Does its current product direction meet your future needs?</td>
</tr>
<tr>
<td>Coolify on a VPS</td>
<td>A deployment interface on servers you control</td>
<td>Who handles the host, upgrades, backups and recovery?</td>
</tr>
</tbody></table>
<p>See the providers' own <a href="https://railway.com/pricing">Railway</a>, <a href="https://render.com/pricing">Render</a>, <a href="https://fly.io/docs/about/pricing/">Fly.io</a>, <a href="https://www.digitalocean.com/pricing/app-platform">DigitalOcean</a>, <a href="https://northflank.com/pricing">Northflank</a> and <a href="https://coolify.io/">Coolify</a> pages for plan details.</p>
<h2>Compare billing models before prices</h2>
<p><strong>Measured resource use:</strong> Lizard uses pay as you go with no monthly subscription. Purchased credits do not expire. Running services can still consume memory while waiting. Railway also meters use, with a monthly minimum on paid plans.</p>
<p><strong>Selected instance size:</strong> a fixed instance price makes the compute line easier to forecast. You pay for the chosen capacity during its billed lifetime. Other charges can still change the total.</p>
<p><strong>Request-driven compute:</strong> charges depend on the provider's treatment of CPU, memory, requests and idle instances. Autoscaling can reduce idle compute, but a database, minimum instance or retained storage can continue to cost money.</p>
<p><strong>Your own server:</strong> the VPS invoice buys capacity. Your time and any services for backup, monitoring or email remain part of the cost of running the application.</p>
<p>Our <a href="https://lizard.build/blog/vercel-alternative">Vercel alternatives guide</a> compares functions with long-running processes and includes a small-app estimate.</p>
<h3>Lizard and Railway: the same service usage</h3>
<p>Using the <a href="https://railway.com/pricing">published Railway service rates</a> and <a href="https://lizard.build/pricing">Lizard rates</a>, take 10 vCPU-hours, 100 GB-hours of memory, 1 GB of Persistent Volumes for 30 days and 10 GB of service egress. No object storage, agent tokens or paid add-ons are included. Rates checked 15 September 2026.</p>
<table>
<thead>
<tr>
<th>Monthly charge</th>
<th>Lizard</th>
<th>Railway</th>
</tr>
</thead>
<tbody><tr>
<td>CPU</td>
<td>$0.250128</td>
<td>$0.277920</td>
</tr>
<tr>
<td>Memory</td>
<td>$1.250640</td>
<td>$1.389600</td>
</tr>
<tr>
<td>Persistent storage</td>
<td>$0.139968</td>
<td>$0.155520</td>
</tr>
<tr>
<td>Service egress</td>
<td>$0.450000</td>
<td>$0.500000</td>
</tr>
<tr>
<td>Resource subtotal</td>
<td><strong>$2.09</strong></td>
<td><strong>$2.32</strong></td>
</tr>
<tr>
<td>Total on the standard Lizard account / Railway Hobby</td>
<td><strong>$2.09 + payment fees</strong></td>
<td><strong>$5 minimum</strong></td>
</tr>
<tr>
<td>Total when using Railway Pro</td>
<td><strong>$2.09 + payment fees</strong></td>
<td><strong>$20 minimum</strong></td>
</tr>
</tbody></table>
<p><strong>Lizard has the lower resource subtotal and no monthly minimum.</strong> It remains cheaper than Hobby in this example if its payment fees allocated to that usage are below $2.91. Railway’s plan payment already covers usage up to its minimum; do not add it twice. Trial credits and tax are excluded. Railway Free can suit smaller eligible projects. Account limits and features differ. <a href="https://docs.railway.com/pricing/plans">Railway plan terms</a>.</p>
<h2>Build a budget for the whole application</h2>
<p>Use one billing period and one workload for every candidate. Record these inputs before opening a calculator:</p>
<ul>
<li>Web and worker process counts, including replicas and staging services.</li>
<li>CPU use, memory use or allocated capacity, according to the meter.</li>
<li>Database size, connections, backups and restore requirements.</li>
<li>Persistent files and object storage.</li>
<li>Internet egress and traffic between regions or providers.</li>
<li>Build duration, deploying seats, support and required add-ons.</li>
</ul>
<p>For a plan whose fee becomes usage credit, the basic formula is <code>plan fee + max(0, eligible usage − available credit) + charges outside that credit</code>. Check eligibility in that provider's terms. Do not subtract an allowance from every service when the provider shares it across the account.</p>
<p>Run the same app on your shortlist and measure it. A hypothetical assumption such as “quarter of a CPU” is useful for a sensitivity check, but it does not prove that a provider will be cheaper for your app.</p>
<h2>Where Lizard belongs on the shortlist</h2>
<p>Choose Lizard when you want a low resource bill without a recurring plan commitment, along with web services, workers, <a href="https://lizard.build/postgres">Managed Postgres</a> and <a href="https://lizard.build/redis">Managed Redis</a>. The Railway example above shows the price advantage for a small paid-plan workload. Unused purchased credits stay available for the next project.</p>
<p>The <a href="https://lizard.build/cli">Lizard CLI</a> supports structured output and ships with a version-matched guide. This helps an agent use the installed commands correctly. It does not remove the need to verify configuration, database access and the live application.</p>
<p>Choose another option when a specific region, contract, compliance requirement or feature is essential and you have not confirmed Lizard supports it. A platform comparison should make those gaps visible before you migrate.</p>
<h2>Heroku and App Runner have different status changes</h2>
<p>Heroku's February 2026 announcement describes a shift to sustaining engineering. It also says new and existing customers paying by credit card can continue using the service. Calling Heroku closed to all new customers is wrong. <a href="https://www.heroku.com/blog/an-update-on-heroku/">Heroku's announcement</a>.</p>
<p>AWS App Runner stopped accepting new customers on 30 April 2026, while existing services remain operational. AWS points users toward ECS Express Mode. That is a different policy. <a href="https://aws.amazon.com/apprunner/">AWS App Runner notice</a>.</p>
<p>If either affects your plans, use the <a href="https://lizard.build/blog/heroku-alternatives">Heroku migration comparison</a> or <a href="https://lizard.build/blog/aws-app-runner">App Runner guide</a> to assess the work involved.</p>
<h2>A useful trial ends with a restore test</h2>
<p>Deploy one representative service, connect its dependencies and run a real user flow. Check a failing build, a process restart and a database connection failure. Confirm where logs appear and how you would restore a backup.</p>
<p>Then compare the bill and the time spent operating the app. Choose the provider that meets those requirements with a process your team can repeat. Start with the <a href="https://lizard.build/docs/deploy/">deployment documentation</a> if you want to include Lizard in that trial.</p>
<h2>FAQ</h2>
<p><strong>Is a PaaS cheaper than a VPS?</strong> Not in every case. A VPS can have a lower infrastructure bill, while a PaaS can reduce server administration. Compare both the cash cost and the work your team must perform.</p>
<p><strong>Does a PaaS include a database?</strong> Some providers offer databases in the same product, some integrate another vendor, and some require a separate cloud service. Check the database bill, backup policy and network path.</p>
<p><strong>Can I run Celery or another queue worker?</strong> Check worker support explicitly. An HTTP function is not the same lifecycle as a process that polls a queue. Our <a href="https://lizard.build/blog/python-app-hosting">Python hosting guide</a> covers this distinction.</p>
<p><strong>Should I choose by the lowest CPU rate?</strong> No. Compare the billing unit, measured or allocated capacity, plan terms and the whole application. Different meters can make similar rates produce different bills.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/best-paas-providers">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Replit alternatives: replace the editor, agent or hosting]]></title><description><![CDATA[A Replit alternative should replace the part of Replit you want to change: the coding tool, development environment or production host. You can use a local coding agent with a separate host, use a bro]]></description><link>https://lizard-build.hashnode.dev/replit-alternatives-replace-the-editor-agent-or-hosting</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/replit-alternatives-replace-the-editor-agent-or-hosting</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:46:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/7798c171-a8a1-42bc-83f8-1a2f281fef9a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>A Replit alternative should replace the part of Replit you want to change: the coding tool, development environment or production host.</strong> You can use a local coding agent with a separate host, use a browser development environment, or keep writing in Replit and move only the deployed application.</p>
<p>Lizard belongs in the hosting part of that decision. This guide comes from Lizard and checks the linked documentation as of 9 September 2026.</p>
<h2>Separate development from production</h2>
<p>The workspace where you edit code and the deployment serving users have different jobs. A development workspace favours quick iteration and inspection. Production needs a repeatable build, stable configuration, data durability and a recovery plan.</p>
<p>Replit documents its <a href="https://docs.replit.com/cloud-services/deployments/about-deployments">publishing workflow and deployment options</a> separately from development. Read the current option your application uses before assuming that leaving the editor requires moving its deployment, or vice versa.</p>
<table>
<thead>
<tr>
<th>What you want to change</th>
<th>Replacement approach</th>
<th>What stays your responsibility</th>
</tr>
</thead>
<tbody><tr>
<td>The AI coding workflow</td>
<td>Use another coding agent with your repository</td>
<td>Reviewing code, tests and change scope</td>
</tr>
<tr>
<td>The browser IDE</td>
<td>Use a cloud development environment such as Codespaces</td>
<td>Environment setup, access and usage control</td>
</tr>
<tr>
<td>Production hosting</td>
<td>Deploy the existing app to a host that fits its runtime</td>
<td>Configuration, data, domains and release checks</td>
</tr>
<tr>
<td>The whole setup</td>
<td>Separate repository, coding tool and host</td>
<td>Connecting the workflow and testing each boundary</td>
</tr>
</tbody></table>
<p>Do not compare a development subscription with a hosting-only price as if they buy the same thing.</p>
<h2>If you want a different coding workflow</h2>
<p>A local editor or terminal agent lets you work in a normal repository and choose where the app runs. The useful comparison is how well the tool handles your language, tests and project size, and what control you have over its edits and actions.</p>
<p>For example, <a href="https://code.claude.com/docs/en/overview">Claude Code</a> works with a codebase and tools in its environment. Deployment still needs a host and credentials for the chosen workflow. Our <a href="https://lizard.build/blog/deploy-from-claude-code">Claude Code deployment guide</a> explains how to connect those parts.</p>
<p>Try the same bounded coding task in each candidate: fix a bug with a failing test, add a small feature, then inspect the diff. That is more useful than comparing how polished the first generated screen looks.</p>
<h2>If you want a browser development environment</h2>
<p><a href="https://docs.github.com/en/codespaces/overview">GitHub Codespaces</a> provides a development environment tied to a repository. It can help a team share configuration and work from a browser or supported editor.</p>
<p>Treat it as a development choice. Decide separately where the public app runs, how deployments start and where production data lives. A forwarded development port is useful for testing, but it is not a complete production operating plan.</p>
<h2>If you want another production host</h2>
<p>Choose by the process your application starts and the services it needs. A static frontend, an HTTP API, a continuous worker and a stateful backend have different requirements.</p>
<p>Lizard is an option for an application with web services, workers and managed data services. Railway and Render are other candidates for a multi-service app. Cloud Run is worth evaluating for supported service, job and worker models. Use the <a href="https://lizard.build/blog/best-paas-providers">PaaS comparison</a> to narrow the choice.</p>
<p>A host will not repair assumptions hidden in generated code. Before moving, look for development-only start commands, local file paths, hardcoded domains and credentials. Verify that the app reads production configuration from the expected variables.</p>
<h2>What to export before moving</h2>
<p><strong>Source and dependencies:</strong> keep the application code, lockfile, runtime version and build instructions in a repository you control.</p>
<p><strong>Configuration:</strong> list variable names and which process uses each one. Transfer secret values through the target's secret settings, not a public export or a committed file.</p>
<p><strong>Data:</strong> identify the actual database and file stores. Downloading source code does not export database records or uploaded files.</p>
<p><strong>Integrations:</strong> record OAuth callback URLs, payment webhooks, email settings and any external API restrictions.</p>
<p><strong>Operations:</strong> identify scheduled work, queues and the checks that show a successful deployment.</p>
<h2>A practical move to Lizard</h2>
<p>First, run the exported project locally using its documented production command. Resolve missing dependencies and environment variables before changing hosts.</p>
<p>Deploy a test copy using the <a href="https://lizard.build/docs/guides/github-integration/">GitHub integration</a> or <a href="https://lizard.build/docs/deploy/upload/">source upload</a>. Add <a href="https://lizard.build/postgres">Managed Postgres</a> if your application uses PostgreSQL, then configure its connection variable explicitly. If it uses a different database, assess compatibility rather than replacing the engine blindly.</p>
<p>Test login, one database write, a file upload, a background task and any external callback. Restart the test service and confirm which data survives. Then plan the final data transfer and domain switch.</p>
<p>Keep a record of the old deployment and how to return traffic if the new one fails. If users can write data during the transition, the rollback plan must account for those new writes.</p>
<h2>Compare cost without losing part of the bill</h2>
<p>Replit's <a href="https://replit.com/pricing">pricing page</a> distinguishes plans and included usage. A replacement may split coding, development compute and hosting across several invoices.</p>
<p>Add those parts together: the coding tool, development environment, app services, database, storage and network. Apply credits under each product's rules. Include the time spent connecting and maintaining the separate tools.</p>
<p>With Lizard, the hosting part has no monthly subscription: you pay for resource use and keep unused purchased credits. This avoids a recurring hosting plan commitment while you use your own coding tool. Compare that combined cost with the Replit plan you would otherwise need; editor and model access remain separate products.</p>
<h2>FAQ</h2>
<p><strong>Can I build in Replit and host elsewhere?</strong> You can move a portable application to another host while keeping your preferred coding workflow. Check export options, dependencies, data and the production start command.</p>
<p><strong>Is Lizard an AI app builder?</strong> This comparison covers Lizard as the place to deploy and operate the application. Your coding tool remains a separate choice.</p>
<p><strong>Does exporting the repository move my database?</strong> No. Identify and export the database and file stores separately, then verify the imported data and application behaviour.</p>
<p><strong>What should I test before switching the domain?</strong> Authentication, database writes, file access, callbacks and background jobs. Also test a restart and confirm that the rollback process handles new writes.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/replit-alternatives">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[n8n webhook to Postgres: a deployment example on Lizard]]></title><description><![CDATA[An n8n webhook can write JSON to Postgres and return the saved row in one workflow. This example runs n8n on Lizard, protects the webhook with a header credential, and uses a request ID to avoid dupli]]></description><link>https://lizard-build.hashnode.dev/n8n-webhook-to-postgres-a-deployment-example-on-lizard</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/n8n-webhook-to-postgres-a-deployment-example-on-lizard</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:45:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/22fce7b6-0aef-4f84-bafc-e030599b1627.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>An n8n webhook can write JSON to Postgres and return the saved row in one workflow.</strong> This example runs n8n on Lizard, protects the webhook with a header credential, and uses a request ID to avoid duplicate rows when a sender retries.</p>
<p>The <a href="https://github.com/lizard-build/n8n-postgres-example">public repository</a> includes the pinned image, workflow, table definition and check script. This article covers a single n8n instance and JSON requests. For the broader hosting setup, start with <a href="https://lizard.build/deploy/n8n--n8n">n8n hosting on Lizard</a>.</p>
<h2>What the example runs</h2>
<p>The n8n service and its dedicated Managed Postgres run in the same region. Postgres holds both n8n's own workflow and credential records and the example's <code>webhook_events</code> table. The application uses one instance; there is no Redis queue or separate worker in this example.</p>
<p>The repository pins n8n 2.38.5 and the official image's digest. A fixed digest makes the image choice reproducible. Review later n8n releases and test upgrades before changing the pin.</p>
<p>The workflow has three nodes:</p>
<ol>
<li><strong>Webhook</strong> accepts a production POST request with an <code>X-Example-Key</code> header.</li>
<li><strong>Store event</strong> executes a parameterised Postgres query.</li>
<li><strong>Respond</strong> returns the stored request ID, JSON body and creation time.</li>
</ol>
<h2>Configure the database, owner and public URL</h2>
<p>Create a service from the repository and a separate Managed Postgres in the same Lizard project. The <a href="https://github.com/lizard-build/n8n-postgres-example#configure-a-service">repository instructions</a> list the commands and environment variables.</p>
<p>Set <code>DB_TYPE=postgresdb</code> and bind the database host, port, name, user and password to the addon's variables. A database running beside n8n is not enough: n8n must receive those values and connect successfully. See <a href="https://docs.n8n.io/deploy/host-n8n/configure-n8n/choose-n8ns-database">n8n's Postgres configuration</a>.</p>
<p>Generate and retain a separate <code>N8N_ENCRYPTION_KEY</code> before the first start. n8n uses it to protect credentials stored in its database. A new random key on each deployment would prevent n8n from reading the old credentials. Keep the key with your recovery records, outside the repository.</p>
<p>The example also configures the owner before n8n becomes reachable. It uses <code>N8N_INSTANCE_OWNER_MANAGED_BY_ENV</code> and a bcrypt password hash, following <a href="https://docs.n8n.io/deploy/host-n8n/configure-n8n/manage-settings-using-environment-variables">n8n's owner configuration</a>. The login password and the webhook key are separate secrets.</p>
<p>Set <code>N8N_HOST</code> to the service's public hostname, without a scheme or path. The start script builds the HTTPS <code>WEBHOOK_URL</code> and editor URL from that hostname. Use port 5678. Check the proxy-hop setting if you add another proxy in front of Lizard.</p>
<h2>Import and publish the workflow</h2>
<p>Create <code>webhook_events</code> with the SQL file in the repository. Import the Postgres and header credentials into n8n, then import the workflow. The credential preparation script reads secret values from the service environment and writes a private temporary import file; the public workflow contains only credential IDs and names.</p>
<p>Use the n8n editor to publish the workflow, or use <code>n8n publish:workflow --id=lizardPostgresExample</code>. CLI publication changes the database; the running n8n process needs a restart to register that change. This behaviour is documented in the <a href="https://docs.n8n.io/deploy/host-n8n/configure-n8n/use-the-command-line">n8n CLI guide</a>.</p>
<p>Check <code>/healthz/readiness</code> before sending requests. The production URL uses <code>/webhook/lizard-postgres-example</code>. The <code>/webhook-test/</code> URL belongs to an editor test session and is not a substitute for a published workflow.</p>
<h2>Send a request and test a retry</h2>
<p>With the public URL and webhook key in environment variables:</p>
<pre><code class="language-bash">curl --fail-with-body "$N8N_URL/webhook/lizard-postgres-example" \
  -H 'Content-Type: application/json' \
  -H "X-Example-Key: $EXAMPLE_WEBHOOK_TOKEN" \
  --data '{"requestId":"example-001","message":"hello"}'
</code></pre>
<p>The SQL query binds the request ID and JSON body as values. It does not build SQL by joining user input into the query text. Quotes and nested JSON remain data.</p>
<p>The table's primary key is <code>request_id</code>. On a conflict, the query returns the first saved row. Sending the same ID with a different message does not change the original payload. That is this example's retry policy; choose a different policy if your application needs updates or conflict errors.</p>
<p>Use a new request ID for each distinct event. Do not reuse a test ID to represent a new event and expect a second row.</p>
<h2>Deployment checks</h2>
<p>On 9 September 2026, we ran n8n 2.38.5 with Postgres 18.6 in <code>eu-west-lim-a</code>. The same seven checks passed before and after restarting the n8n service.</p>
<table>
<thead>
<tr>
<th>Check</th>
<th>Before restart</th>
<th>After restart</th>
</tr>
</thead>
<tbody><tr>
<td>Health endpoint</td>
<td>200</td>
<td>200</td>
</tr>
<tr>
<td>Database readiness</td>
<td>200</td>
<td>200</td>
</tr>
<tr>
<td>Request with the correct key</td>
<td>200; stored body returned</td>
<td>200; stored body returned</td>
</tr>
<tr>
<td>Same request ID with a changed body</td>
<td>Original row returned</td>
<td>Original row returned</td>
</tr>
<tr>
<td>Missing key</td>
<td>403</td>
<td>403</td>
</tr>
<tr>
<td>Wrong key</td>
<td>403</td>
<td>403</td>
</tr>
<tr>
<td>Unknown webhook path</td>
<td>404</td>
<td>404</td>
</tr>
</tbody></table>
<p>The database held one event after both runs. The JSON body and creation time matched across the restart. The test body included quotes and nested JSON. The owner also signed in and found the imported workflow in the editor.</p>
<p>Read the <a href="https://github.com/lizard-build/n8n-postgres-example/tree/main/deployment-checks">deployment record and raw results</a>. The record names the tested code commit. Run the repository's check script against your own deployment before using it for your workflows.</p>
<h2>What these checks do not cover</h2>
<p>The example does not test load, uptime, cold-start latency or a full monthly bill. It does not set up queue mode, database backups, file uploads or recovery from database loss.</p>
<p>A service restart check establishes that this deployment can read its saved workflows and credentials after restarting. It does not establish disaster recovery. For that, take a database backup, retain the encryption key, restore into a separate environment and run the workflow again.</p>
<p>This JSON workflow does not require a persistent local filesystem. Workflows that read or write local files need suitable storage and their own persistence tests. Review Persistent Volumes and n8n's storage requirements before adding that workload.</p>
<h2>FAQ</h2>
<p><strong>Does n8n need Postgres?</strong> No. Self-hosted n8n can use SQLite. This example uses Postgres to keep workflow, credential and execution records outside the application container and to demonstrate a database-writing workflow.</p>
<p><strong>Why does a webhook work in the editor but return 404 elsewhere?</strong> Check that the workflow is published and that the sender uses the production <code>/webhook/</code> path. A CLI publication also requires the running n8n process to restart.</p>
<p><strong>What must stay the same after a redeploy?</strong> The database connection must point to the intended database, and the encryption key must still decrypt its credentials. Keep the public webhook URL and authentication contract stable for callers.</p>
<p><strong>Is this a complete production n8n setup?</strong> It is a checked single-instance example. Add backups, monitoring, storage and a capacity plan for your workload before depending on it for production automation.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/n8n-webhook-postgres-example">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[How to deploy a Cursor app with PostgreSQL]]></title><description><![CDATA[To deploy a Cursor app with PostgreSQL, run the application on a host, create a database, connect it through a server-side DATABASE_URL, and apply your schema migrations. Cursor's agent can do this th]]></description><link>https://lizard-build.hashnode.dev/how-to-deploy-a-cursor-app-with-postgresql</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/how-to-deploy-a-cursor-app-with-postgresql</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:45:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/708b68ff-6422-4773-9cb3-5a708b3001c9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To deploy a Cursor app with PostgreSQL, run the application on a host, create a database, connect it through a server-side <code>DATABASE_URL</code>, and apply your schema migrations. Cursor's agent can do this through Lizard CLI. After deployment, check the public URL and confirm that a record survives a fresh browser session and a redeploy.</p>
<p>This guide is for a Next.js application you already run locally. It uses Lizard for the app and Managed Postgres for the database. You can use the prompts in Cursor's desktop agent, inspect its plan, and let it run the steps you approve.</p>
<h2>What needs to move off your laptop?</h2>
<p>Consider a task app: you add a task, mark it done, and come back later. Publishing the page is only part of deploying it. The server must reach the database, the tables must exist, and new requests must read the saved data.</p>
<table>
<thead>
<tr>
<th>Part of your app</th>
<th>What production needs</th>
</tr>
</thead>
<tbody><tr>
<td>Next.js pages and server code</td>
<td>A production build and a running Node.js service</td>
</tr>
<tr>
<td>Tasks, users, and other saved records</td>
<td>PostgreSQL that the deployed server can reach</td>
</tr>
<tr>
<td>Database credentials and API keys</td>
<td>Server-side environment variables</td>
</tr>
<tr>
<td>Schema changes</td>
<td>A migration step for the production database</td>
</tr>
<tr>
<td>A link you can share</td>
<td>A public HTTPS address</td>
</tr>
</tbody></table>
<p>Your local database and <code>.env.local</code> file do not move with a GitHub push. Decide which data you need to keep. A fresh database is suitable for a new app; moving an existing database needs a separate export and import plan.</p>
<h2>1. Check the production build in Cursor</h2>
<p>Open the application folder in Cursor. Ask the agent to inspect the project before changing its deployment settings:</p>
<pre><code class="language-text">Prepare this Next.js app for deployment with PostgreSQL. Read its package.json,
lockfile, Next.js configuration, database client, and migration files.

Run the existing checks and production build using this project's package
manager. Identify the start command, port, required environment-variable
names, and production migration command. Do not print secret values.

Check whether any page queries the database during the build. Explain what
must be available at build time and what the app can read at request time.
Show any fixes needed before deploying.
</code></pre>
<p>A development server can work while the production build fails. For a standard Next.js Node.js deployment, the build uses <code>next build</code> and the server uses <code>next start</code>. Static export and standalone output need different launch settings. Follow the <a href="https://lizard.build/docs/framework-guides/nextjs/">Next.js deployment guide</a> for the configuration your app uses.</p>
<p>Keep the lockfile in source control. Keep database credentials out of it. A variable such as <code>NEXT_PUBLIC_DATABASE_URL</code> would expose a secret to the browser: Next.js includes <code>NEXT_PUBLIC_*</code> values in client JavaScript at build time. Use a server-only <code>DATABASE_URL</code>. <a href="https://nextjs.org/docs/app/guides/environment-variables">Next.js environment-variable docs</a> explain the distinction.</p>
<h2>2. Give Cursor the current deployment instructions</h2>
<p>Cursor's agent can <a href="https://cursor.com/docs/agent/overview">run terminal commands</a>. Install Lizard CLI in Cursor's terminal if it is not already available:</p>
<pre><code class="language-bash">npm install -g @lizard-build/cli
</code></pre>
<p>Then ask the agent to run and read:</p>
<pre><code class="language-bash">lizard skills get core --json
</code></pre>
<p>The command returns the guide that matches the installed CLI. This workflow uses the CLI directly; you do not need to configure an MCP server. If Lizard requests authentication, complete the sign-in link before continuing.</p>
<p>Paste this deployment prompt into Cursor:</p>
<pre><code class="language-text">Deploy this app with Lizard and Managed Postgres. First read the full output
of `lizard skills get core --json`. Check the current project link and git
remote, then show me the target project, service, region, and resources.
Wait for approval before creating resources or changing a live service.

If this app has a GitHub remote, connect that repository without starting
the first build. If it has no GitHub remote, use local source upload.

Create or select the intended database. Set DATABASE_URL on the app service
using a reference to that database's actual name. Configure the app's other
required variables and its reviewed production migration command before
deploying. Keep secret values out of the chat and repository.

Use the build settings this project needs. Read build and runtime logs,
check the final deployment status, and return the public URL. Test the
app's database-backed action and report what passed or failed.
</code></pre>
<p>The <a href="https://lizard.build/docs/guides/deploy-from-coding-agent/">coding agent guide</a> contains the full commands for both source paths. Keep that guide available while reviewing the agent's work.</p>
<h2>3. Connect Postgres before the first deploy</h2>
<p>A service can build successfully and then fail on its first database request. Configure the connection before starting that first build or release.</p>
<p>For a <strong>new service named <code>web</code></strong> in the intended linked project, with the app at the GitHub repository root, the connection step looks like this. Replace <code>YOUR_ORG/YOUR_REPO</code> with your repository:</p>
<pre><code class="language-bash">lizard add --repo YOUR_ORG/YOUR_REPO --name web --no-deploy --json
lizard add postgres --json
lizard secrets set DATABASE_URL='${{postgres.DATABASE_URL}}' --service web --json
</code></pre>
<p>Reuse an existing service or database when that is the intended target. The example assumes the new database is named <code>postgres</code>. If Lizard returns another name, use it in the reference. The single quotes keep your shell from interpreting <code>${{...}}</code>.</p>
<p><code>--no-deploy</code> leaves time to configure the database, migration command, and other required settings. For a monorepo or another branch, also set the app directory and branch before deploying. See <a href="https://lizard.build/docs/addons/postgres/">Managed Postgres</a> for connection details.</p>
<p>If you have no GitHub remote, the agent can create an empty service and upload the local source with <code>lizard up</code>. For a GitHub service, use its GitHub deployment flow for later updates: <code>lizard up</code> changes the service to uploaded source. The two paths are separate choices.</p>
<h2>4. Apply the schema your app expects</h2>
<p>Creating PostgreSQL does not create your app's tables. A connection error and a missing-table error need different fixes.</p>
<p>Use the migration tool already in the project. For Prisma projects with committed migration files, <code>prisma migrate deploy</code> applies pending migrations in production. Prisma recommends running it through the deployment process. It does not generate Prisma Client, so the build still needs any generation step your project requires. <a href="https://docs.prisma.io/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate">Prisma's deployment docs</a> cover that setup.</p>
<p>Have Cursor configure the service's <code>preDeployCommand</code> with the reviewed migration command. Ensure the built image contains the migration files, the required CLI package, and its configuration. If the migration needs a database connection, that connection must be available when the command runs. Avoid a development reset command against data you need to keep.</p>
<p>Next.js can also read data while building a page. A database connection configured for the running server does not prove that a build-time query will succeed. Ask Cursor to check when each query runs and use the rendering behavior the page needs. The <a href="https://nextjs.org/docs/app/guides/self-hosting">Next.js self-hosting guide</a> covers runtime and build-time behavior.</p>
<h2>5. Verify saved data at the public URL</h2>
<p>Read the deployment result and open its HTTPS URL. Test the feature that needs Postgres, rather than stopping after the home page loads.</p>
<p>For a task app, use this sequence:</p>
<ol>
<li>Create a task with a name you can recognize, such as <code>deployment-check-0909</code>.</li>
<li>Reload the page and confirm the task appears.</li>
<li>Open a private browser window, sign in to the same account if needed, and check the task again. This helps catch apps that only save to browser storage.</li>
<li>Deploy a small code change through the same source path. Confirm the task still exists and that you can update it.</li>
</ol>
<p>Adapt the check to your app: a booking, note, or form submission can serve the same purpose. Use test data and check that each account can access only its own records before inviting users.</p>
<p>If a check fails, give Cursor the failed action and ask it to inspect the service logs:</p>
<pre><code class="language-bash">lizard logs --build --service web --json
lizard logs --service web --json
lizard ps --json
</code></pre>
<p>The first command reads build logs; the second reads application logs. Keep the <a href="https://lizard.build/docs/observability/logs/">logs reference</a> open for filters and troubleshooting.</p>
<h2>Why does a Cursor app work locally but fail after deployment?</h2>
<p>The deployed app may have different variables, a new database, or a different start command. Match the symptom to the relevant check:</p>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>What to check</th>
</tr>
</thead>
<tbody><tr>
<td>The page loads, but saving fails</td>
<td>The app service's database reference, database readiness, and request logs</td>
</tr>
<tr>
<td>Postgres reports a missing table</td>
<td>Whether the production migration ran against this database</td>
</tr>
<tr>
<td>The build fails on a database query</td>
<td>Whether that page reads data during the build and can reach the database then</td>
</tr>
<tr>
<td>The service never becomes healthy</td>
<td>The production start command, a listener on <code>0.0.0.0</code>, and matching app and service ports</td>
</tr>
<tr>
<td>The browser still calls <code>localhost</code></td>
<td>Hardcoded URLs or an old <code>NEXT_PUBLIC_*</code> value; public variables need a rebuild</td>
</tr>
<tr>
<td>Records disappear in another browser</td>
<td>Browser-only storage, mock data, or a different account or database</td>
</tr>
</tbody></table>
<p>Ask the agent to fix the cause shown in the logs. Repeated deployments with the same configuration will not fix a missing variable or table.</p>
<h2>How much does it cost to host the app and Postgres?</h2>
<p>Lizard uses <strong>pay as you go with no monthly subscription</strong>. New accounts receive <strong>$10 in free credits, valid for 31 days</strong>. Purchased credits do not expire. Resource usage draws from your account balance; the top-up screen shows any payment fee before you confirm. See <a href="https://lizard.build/pricing">current rates and payment terms</a>.</p>
<p>The application and Managed Postgres both consume resources. Lizard uses billing only for active resources, with charges for CPU, memory, storage, and outbound traffic under the plan's terms. Stopping an app does not stop a separate database, and retained volume storage can still incur charges.</p>
<p>Measure the app and database together under the load you expect. A trial credit is a way to test the deployment; it is not a promise of permanent free hosting or a fixed monthly cost for every app.</p>
<h2>Questions before you deploy</h2>
<h3>Do I need to export my app from Cursor?</h3>
<p>For a local project, your source files are already in the project folder. Deploy that code through a connected GitHub repository or local upload. Check that the source includes the files needed to build and run it.</p>
<h3>Does a Cursor subscription pay for Lizard hosting?</h3>
<p>No. Cursor and Lizard are separate services. This workflow uses Cursor to operate Lizard CLI; the Lizard account pays for the deployed app and database under its own plan.</p>
<h3>Can I keep my existing PostgreSQL database?</h3>
<p>Yes, if the deployed server can reach it and its connection settings meet the database provider's requirements. Configure the server-side connection string, check network access, and run the same application tests. You do not need to create another database just to use this workflow.</p>
<h3>Do I need a custom domain before deploying?</h3>
<p>Start by checking the public address Lizard returns for your web service. Once it works, follow the <a href="https://lizard.build/docs/cli/domain/">custom domain instructions</a> to connect your hostname and verify the DNS records. If your app uses login callbacks, update those URLs too.</p>
<p>Open your project in Cursor and start with the preparation prompt above. For the exact deployment sequence, use the <a href="https://lizard.build/docs/guides/deploy-from-coding-agent/">coding agent guide</a>.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/deploy-cursor-app-postgres">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Claude Code visual editor: select an element and fix its CSS]]></title><description><![CDATA[Chrome with Lizard Studio: the page, inspector and chat stay in one window. This screenshot shows the ChatGPT connection.
A visual editor for Claude Code should help you identify the part of a page yo]]></description><link>https://lizard-build.hashnode.dev/claude-code-visual-editor-select-an-element-and-fix-its-css</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/claude-code-visual-editor-select-an-element-and-fix-its-css</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:42:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/d46d6cf3-a8a0-4c18-a971-a761b868936c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://lizard.build/studio-inspector.webp" alt="Full Chrome window with Lizard Studio open beside the page, showing a selected element, style inspector and chat panel" /></p>
<p>Chrome with Lizard Studio: the page, inspector and chat stay in one window. This screenshot shows the ChatGPT connection.</p>
<p>A visual editor for Claude Code should help you identify the part of a page you want to change and check the result in the source. In Lizard Studio, you select an element in Chrome, attach it to a chat and ask Claude Code to edit your local project. The browser stays beside the conversation so you can inspect the outcome.</p>
<p>This guide uses a small spacing exercise with a result you can measure: change the gap between two buttons from <code>4px</code> to <code>24px</code>. You will check the source diff, refresh the page and inspect the gap again. It is a reproducible exercise, not a performance benchmark or a claim about how quickly an agent will finish your project.</p>
<p>We build <a href="https://lizard.build/studio">Lizard Studio</a>. Its extension and host are free and MIT licensed; your AI provider's account requirements and usage charges still apply. Complete the <a href="https://lizard.build/docs/studio/getting-started/">setup guide</a> before trying the agent steps below.</p>
<h2>What visual editing means here</h2>
<p>Lizard Studio uses an element selector and design tools to give the agent context. You describe a source change; Claude Code makes the file edit. This workflow differs from manually dragging an element and expecting the editor to write the corresponding CSS immediately.</p>
<p>The selector can identify the target on the page. An annotation can show an issue that covers a larger area. Rulers, distance measurements and responsive preview help you check a layout. The local agent connects that page context to your project files. The <a href="https://github.com/lizard-build/lizard-studio">README</a> lists the tools and connection requirements.</p>
<p>If direct drag-and-drop editing is your main requirement, compare the controls before choosing a tool. This exercise tests selecting an element, requesting a change and reviewing source code.</p>
<h2>Download the example</h2>
<p>You need Python 3 for the small static server used here, Chrome, Lizard Studio and a signed-in Claude Code installation. For an existing app, use its normal development server instead.</p>
<p>Create an empty folder for the exercise. Download <a href="https://lizard.build/studio-examples/spacing.html">spacing.html</a> into it using your browser's <strong>Save page as</strong> command, or run this from the folder:</p>
<pre><code class="language-bash">curl -fsS https://lizard.build/studio-examples/spacing.html -o spacing.html
python3 -m http.server 4175 --bind 127.0.0.1
</code></pre>
<p>Open <code>http://127.0.0.1:4175/spacing.html</code> in Chrome. Leave the server running while you work. If port 4175 is in use, choose another port and use that number in the URL.</p>
<p>The page has two sample buttons with no actions. Their row intentionally uses this CSS:</p>
<pre><code class="language-css">.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 24px;
}
</code></pre>
<p>Choose this folder in Lizard Studio so the agent can edit <code>spacing.html</code>. Opening the example on lizard.build only shows the published exercise; changing your downloaded copy requires the local URL.</p>
<h2>Select the row you want to change</h2>
<p>Start a Claude Code chat in the side panel. Use <strong>Selector</strong> and attach the <code>.actions</code> row that contains both buttons. If you select one button by mistake, select its parent row or explain that the row's gap is the target.</p>
<p>You can ask for a read-only check before editing:</p>
<pre><code class="language-text">Inspect the selected action row. Report its computed gap and the
local file that defines it. Do not change any files yet.
</code></pre>
<p>The initial gap should be <code>4px</code>. The source is the stylesheet inside your downloaded <code>spacing.html</code>. If the agent reports another file or page, correct the project folder or browser tab before requesting an edit.</p>
<p><img src="https://lizard.build/blog-images/studio/studio-inspector-detail.webp" alt="Lizard Studio inspector showing box model, colors and text styles, with the page toolbar below" /></p>
<p>Crops from the Lizard Studio interface show the style inspector and page toolbar. These values belong to the selected element on the ChatGPT overview page; the spacing exercise uses a separate local file.</p>
<h2>Ask for a small source edit</h2>
<p>Send the selected row with a request that states the result and the scope:</p>
<pre><code class="language-text">Set the selected action row's gap to 24px in spacing.html.
Keep the button labels, colors and card margin unchanged.
Show the source diff. Refresh the page and check that the
computed gap is 24px.
</code></pre>
<p>Review the agent's proposed edit. The relevant change should be equivalent to:</p>
<pre><code class="language-diff">- .actions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 24px; }
+ .actions { display: flex; flex-wrap: wrap; gap: 24px; margin-top: 24px; }
</code></pre>
<p>The agent may reformat the stylesheet. Check the actual declarations rather than requiring the same line layout as this example. There should be no reason to change either button label or the <code>.card</code> rule for this task.</p>
<p>Use your session's permission controls to approve the intended edit. A narrowly scoped request makes the diff easier to judge, but it does not replace reviewing the result.</p>
<p><img src="https://lizard.build/blog-images/studio/studio-selected-element-prompt.webp" alt="Lizard Studio request field with one selected element attached, a project folder and ChatGPT agent controls" /></p>
<p>The real request field shows an attached element as “1 selection.” This screenshot uses ChatGPT and a brightness request; for this exercise, choose Claude Code and use the spacing request above.</p>
<h2>Verify the page after a refresh</h2>
<p>The Python server does not provide hot reload. Refresh Chrome after the file changes, then ask the agent to inspect <code>.actions</code> again.</p>
<p>You can also check the computed value in Chrome DevTools:</p>
<pre><code class="language-javascript">getComputedStyle(document.querySelector('.actions')).gap
// Expected: "24px"
</code></pre>
<p>Check both desktop and a narrow viewport. The sample row uses <code>flex-wrap: wrap</code>, so buttons can move onto another line when space is limited. At each width, confirm that the page has no horizontal overflow and both labels remain visible.</p>
<p>Use this acceptance checklist:</p>
<ul>
<li>The local source contains <code>gap: 24px</code> for <code>.actions</code>.</li>
<li>A page refresh preserves the new gap.</li>
<li>The button text and colors stay unchanged.</li>
<li>The <code>.card</code> margin stays <code>32px</code>.</li>
<li>The page remains usable at a narrow width.</li>
</ul>
<p>If the project uses Git, read <code>git diff</code>. For this standalone file, your editor's comparison view can show the same change. The exercise is complete when the file and browser agree.</p>
<h2>Why a change can disappear</h2>
<p>A browser tool can change the live DOM or add an inline style without changing any project file. That can help preview an idea, but the browser loads the original source again on refresh.</p>
<p>If the result disappears, ask the agent which file it edited. Check that the browser uses the local server and that the selected folder contains the served file. An app can also have a more specific CSS rule or inline style that overrides the edited declaration.</p>
<p>For React or another framework, the source may be a component, CSS module or utility class rather than a standalone stylesheet. Ask the agent to find the rule that owns the spacing. Avoid a global button rule when only one action row needs to change.</p>
<h2>Use the same method for a real UI issue</h2>
<p>Choose an issue with a result you can describe: a card that overflows at a stated width, a heading that should wrap to fit its container or a button row with the wrong alignment. Attach the relevant element or annotated screenshot, then state what should remain unchanged.</p>
<p>For a behavior bug, ask the agent to reproduce the action and inspect console and network output. A screenshot cannot show whether an event handler ran or a request failed. Lizard Studio includes those browser tools; the <a href="https://lizard.build/studio/compare/claude-in-chrome">comparison with Claude in Chrome</a> explains the different setups.</p>
<p>You can also run the exercise with <a href="https://lizard.build/studio/chatgpt">ChatGPT in the side panel</a>. Keep the same starting file and acceptance criteria if you want to compare the two agents. Record failed attempts and clarifications, and avoid drawing speed claims from a single run.</p>
<h2>Common questions</h2>
<h3>Does Claude Code need a screenshot to edit CSS?</h3>
<p>A screenshot can help explain a visual problem. In this workflow, the selected element and live page inspection add DOM and style context, while the local folder supplies the source. Use the inputs that help identify your specific issue.</p>
<h3>Can I edit any website permanently?</h3>
<p>You need access to the source project to keep a source change, and you need to deploy it to update a live site. Inspecting a third-party page does not grant either permission or repository access.</p>
<h3>Is this a replacement for my editor?</h3>
<p>You can keep using your editor for code and diffs. Lizard Studio supplies the browser side panel and page tools. Compare the other interface choices in <a href="https://lizard.build/blog/claude-code-gui">the Claude Code GUI guide</a>.</p>
<h3>Where do I start if the panel does not connect?</h3>
<p>Follow the <a href="https://lizard.build/docs/studio/getting-started/">installation and troubleshooting guide</a>. Check the local host, your agent login and the chosen project folder before testing a source edit.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/claude-code-visual-editor">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Claude Code GUI: choose an interface for your workflow]]></title><description><![CDATA[The right Claude Code GUI depends on where you want to inspect a page, give the agent context and review its file edits. Start with the official desktop app or IDE extension if you want an interface f]]></description><link>https://lizard-build.hashnode.dev/claude-code-gui-choose-an-interface-for-your-workflow</link><guid isPermaLink="true">https://lizard-build.hashnode.dev/claude-code-gui-choose-an-interface-for-your-workflow</guid><dc:creator><![CDATA[Yura Oak]]></dc:creator><pubDate>Wed, 23 Sep 2026 18:42:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab3dce89dc545de3f46863c/ff7c9fe0-57de-4efb-bc92-17d0305c9354.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The right Claude Code GUI depends on where you want to inspect a page, give the agent context and review its file edits. Start with the official desktop app or IDE extension if you want an interface for general coding work. Consider Lizard Studio when you want the chat and design tools beside an existing Chrome tab.</p>
<p>This guide compares five interfaces by workflow rather than assigning an overall score. We build <a href="https://lizard.build/studio">Lizard Studio</a>. We checked the linked product documentation on September 14, 2026; we did not run a controlled speed or reliability benchmark across all five tools.</p>
<h2>Compare the interfaces</h2>
<table>
<thead>
<tr>
<th>Interface</th>
<th>Where you work</th>
<th>Consider it when</th>
<th>Check before choosing</th>
</tr>
</thead>
<tbody><tr>
<td>Claude Code Desktop</td>
<td>Claude's desktop app</td>
<td>You want the official standalone interface for sessions and code review.</td>
<td>Current OS and account support, and how its preview fits your app.</td>
</tr>
<tr>
<td>Claude Code for VS Code</td>
<td>Your existing editor</td>
<td>Files and editor diffs are central to your work.</td>
<td>Extension requirements and the browser setup for your workflow.</td>
</tr>
<tr>
<td>Lizard Studio</td>
<td>Chrome side panel and page toolbar</td>
<td>You want to select, annotate and measure elements beside your chat.</td>
<td>Local host, agent CLI and the folder connected to the page.</td>
</tr>
<tr>
<td>Clodex</td>
<td>A separate desktop application</td>
<td>You want direct visual controls that write edits back to source.</td>
<td>Supported projects and current plan terms.</td>
</tr>
<tr>
<td>Nimbalyst</td>
<td>A separate visual workspace</td>
<td>Your work includes visual documents and agent sessions.</td>
<td>The editors and integrations needed for your project.</td>
</tr>
</tbody></table>
<p>“Claude Code UI” is also a name used by specific projects. If you found a particular repository or app under that name, check its publisher and installation instructions rather than assuming that all GUI results describe the same product.</p>
<h2>Claude Code Desktop: the official standalone app</h2>
<p>Anthropic's Code tab brings sessions, file changes and app previews into its desktop application. Its documentation covers local and remote environments, permissions and reviewing diffs. It is a sensible first option if you want an official GUI without making your code editor the center of the session.</p>
<p>Read the <a href="https://code.claude.com/docs/en/desktop">desktop reference</a> for current capabilities and <a href="https://code.claude.com/docs/en/desktop-quickstart">desktop setup</a> for supported systems and accounts. Avoid choosing from an old screenshot: the preview and session controls can change between releases.</p>
<p>For your evaluation, open a small project and ask for one file edit. Check whether the preview reaches the route you need and whether you can trace the resulting change back to the right file. If your app depends on a particular browser login, include that requirement in the test.</p>
<h2>Claude Code for VS Code: work beside your files</h2>
<p>The official VS Code extension places Claude Code in the editor. Anthropic documents context selection, reviewing changes and connecting browser capabilities. It suits a workflow where you already navigate components and inspect diffs in VS Code.</p>
<p>Use the <a href="https://code.claude.com/docs/en/vs-code">official extension guide</a> to check installation and current browser support. The extension and the desktop app are different interfaces; compare the version you actually intend to use.</p>
<p>Try a task that crosses a component and its stylesheet. Check how you attach source context, review both changes and verify the rendered page. If you spend most of your time in files, staying in that interface may require fewer steps than moving the chat into Chrome.</p>
<h2>Lizard Studio: inspect and edit beside the live page</h2>
<p><img src="https://lizard.build/studio-inspector.webp" alt="Lizard Studio in Chrome beside the ChatGPT overview page, with a selected element, style inspector, page toolbar and chat panel" /></p>
<p>A real Lizard Studio session: the selected element and inspector sit on the page, while the request stays in Chrome’s side panel. This screenshot shows the ChatGPT connection.</p>
<p>Lizard Studio is a free, MIT-licensed Chrome extension. It runs Claude Code or ChatGPT through a local CLI and connects the session to your browser and project folder. ChatGPT is the panel's name for the Codex CLI connection.</p>
<p>The page toolbar includes Selector, Annotate, rulers, guides, a column grid, a color picker, distance measurements and responsive preview. Browser tools let the agent inspect the DOM, screenshots, console and network, then interact with the page as part of the task. See the <a href="https://github.com/lizard-build/lizard-studio">source and tool list</a>.</p>
<p>Choose this workflow when the change starts with something you see: a cramped button row, an element at the wrong size or an error that appears after a click. Select the element or mark the area, describe the result and review the source diff.</p>
<p>The setup requires a local host and an installed agent. The free extension does not include model use. Your selected provider sets account eligibility, limits and charges. Read the <a href="https://lizard.build/docs/studio/getting-started/">setup guide</a> and try the <a href="https://lizard.build/blog/claude-code-visual-editor">spacing exercise</a> before deciding.</p>
<p>The documented design workflow uses selections and prompts to request code edits. If you need to drag a control and have it write CSS directly, check tools built around that operation.</p>
<h2>Clodex: direct visual controls for source edits</h2>
<p>Clodex describes a desktop workspace that runs Claude Code against a local project and adds visual controls to its preview. Its guide covers changes such as spacing, size, typography and text, with edits written to the source files. That makes it relevant when hands-on visual editing is a central requirement.</p>
<p>Read its <a href="https://useclodex.com/claude-code/visual-editor/">visual editor guide</a> for the supported operations and limits. It states that you bring Claude access and that its desktop product has paid plans. Check current terms before installing.</p>
<p>A useful trial is a layout change in your own component structure. Check whether the visual control edits the declaration you expect, and inspect the file after a later agent request. A control that works well on a simple page may need a different workflow for nested layouts or shared design tokens.</p>
<h2>Nimbalyst: visual work alongside coding sessions</h2>
<p>Nimbalyst presents itself as a visual workspace for Claude Code and Codex, with editors for several kinds of project material. Consider it if the same task involves code, plans or visual documents and you want to work with them in one application. Its <a href="https://nimbalyst.com/">current product page</a> lists the available editors and agent connections.</p>
<p>Evaluate the editor you need rather than the number of features on the page. Open a representative file, make a small change and check the saved format. Then ask the agent to work with the result. That shows whether the visual document remains useful in your existing repository and review process.</p>
<h2>Choose from the task you repeat</h2>
<p>For code-heavy work, begin with the desktop or VS Code interface you already understand. For page-led work, test how quickly you can identify the element and supply the intended change. For manual visual design, check whether the controls write ordinary source changes that your team can review.</p>
<p>Use the same acceptance criteria for each tool:</p>
<ol>
<li>The session opens the intended repository and page.</li>
<li>The agent or editor changes the expected source file.</li>
<li>You can read and approve the diff.</li>
<li>The result persists after refresh.</li>
<li>A second viewport or related page still works.</li>
</ol>
<p>Add one requirement specific to your project, such as a CSS module, a shared component or a page that requires a test login. Do not use a different model and a different task for each interface and treat the results as a clean comparison.</p>
<h2>GUI, visual editor and browser automation</h2>
<p>These terms describe different parts of the workflow. A GUI is an interface for using the agent. A visual editor offers controls for changing a design. Browser automation lets an agent inspect or operate a page. One product can provide several of these, but the presence of one does not establish the others.</p>
<p>When you read a feature page, look for the exact operation you need. Can you select a rendered element? Does a control edit the source or only the live DOM? Can the agent inspect a failed request? How do you see and approve the file diff?</p>
<p>The <a href="https://lizard.build/studio/compare/claude-in-chrome">Lizard Studio and Claude in Chrome comparison</a> covers the two browser setups in more detail. Both can support debugging; design controls, agent choices and session setup are more useful criteria than a broad claim about browser access.</p>
<h2>Common questions</h2>
<h3>Is a GUI required to use Claude Code?</h3>
<p>No. You can use its terminal interface. A GUI changes how you give context, inspect sessions and review results; choose it when those controls help your work.</p>
<h3>Does a free GUI include free model use?</h3>
<p>Not necessarily. Lizard Studio's extension and host are free, while the connected provider supplies agent access. Check software cost and model access separately for every tool.</p>
<h3>Which one should I try for CSS fixes?</h3>
<p>Try a tool that can identify the rendered element and connect it to a source edit. Use <a href="https://lizard.build/blog/claude-code-visual-editor">this downloadable spacing exercise</a> to check the file diff and the page after refresh. If you prefer direct visual controls, include that requirement in your trial.</p>
<h3>Can I use ChatGPT in Lizard Studio instead?</h3>
<p>Yes, through the local Codex CLI connection. The <a href="https://lizard.build/studio/chatgpt">ChatGPT guide</a> explains the naming, setup and a source-editing example.</p>
<hr />
<p>Originally published at <a href="https://lizard.build/blog/claude-code-gui">Lizard (lizard.build)</a>.</p>
]]></content:encoded></item></channel></rss>