<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julia</title>
    <description>The latest articles on DEV Community by Julia (@julia_kafarska).</description>
    <link>https://dev.to/julia_kafarska</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3503874%2F7093818a-80b4-400c-9da1-59cf2a2f8940.png</url>
      <title>DEV Community: Julia</title>
      <link>https://dev.to/julia_kafarska</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julia_kafarska"/>
    <language>en</language>
    <item>
      <title>Bring Your Own Dockerfile: Deploy Anything That Runs in a Container</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Thu, 15 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/lightcloud/bring-your-own-dockerfile-deploy-anything-that-runs-in-a-container-4k3l</link>
      <guid>https://dev.to/lightcloud/bring-your-own-dockerfile-deploy-anything-that-runs-in-a-container-4k3l</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cngdlknaxk9d177cczu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cngdlknaxk9d177cczu.webp" alt="Bring Your Own Dockerfile: Deploy Anything That Runs in a Container" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deploy any app on Light Cloud with your own Dockerfile, put the &lt;code&gt;Dockerfile&lt;/code&gt; at the root of the repository, &lt;code&gt;EXPOSE&lt;/code&gt; the port your app listens on, read that port from the &lt;code&gt;PORT&lt;/code&gt; environment variable, push to GitHub and click &lt;strong&gt;Deploy&lt;/strong&gt;. Light Cloud builds your Dockerfile as it is, with a layer cache that covers every stage, and runs the image. Anything that runs in a Linux container can be deployed this way.&lt;/p&gt;

&lt;p&gt;In my run a Rust service was live 3 minutes after the first click, and a new instance answered 0.13 seconds after it started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;A small word-counting API written in Rust with &lt;a href="https://github.com/tokio-rs/axum" rel="noopener noreferrer"&gt;axum&lt;/a&gt;, a language Light Cloud does not build on its own. The Dockerfile makes it deployable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /count&lt;/code&gt; returns the characters, words and lines of a text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /health&lt;/code&gt; answers &lt;code&gt;{"status":"ok"}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A two-stage Dockerfile compiles in a large Rust image and ships only the binary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/light-cloud-com/tutorial-dockerfile-app" rel="noopener noreferrer"&gt;github.com/light-cloud-com/tutorial-dockerfile-app&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to bring your own Dockerfile
&lt;/h2&gt;

&lt;p&gt;Light Cloud builds Node.js, Python, Go, Java, .NET, PHP, Ruby and static sites without one. Bring your own when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your language is not on that list (Rust, Elixir, Deno, C++, ...);&lt;/li&gt;
&lt;li&gt;you need system packages, such as &lt;code&gt;ffmpeg&lt;/code&gt; or a PDF renderer;&lt;/li&gt;
&lt;li&gt;you already have a Dockerfile that works and want exactly that image in production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The four rules
&lt;/h2&gt;

&lt;p&gt;Whatever is inside, a Dockerfile that works on Light Cloud follows four rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Listen on &lt;code&gt;PORT&lt;/code&gt;, on &lt;code&gt;0.0.0.0&lt;/code&gt;.&lt;/strong&gt; Light Cloud sets &lt;code&gt;PORT&lt;/code&gt;; &lt;code&gt;localhost&lt;/code&gt; inside a container is not reachable from outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EXPOSE&lt;/code&gt; that port.&lt;/strong&gt; Light Cloud reads it to know where to send traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop cleanly on &lt;code&gt;SIGTERM&lt;/code&gt;.&lt;/strong&gt; That is how an old instance is told to go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log to standard output.&lt;/strong&gt; One JSON object per line with a &lt;code&gt;severity&lt;/code&gt; gets the right level in the &lt;strong&gt;Logs&lt;/strong&gt; tab.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rustup.rs/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; 1.85 or later, for the 2024 edition; I used 1.97.1. Only needed to run the app on your machine: the build happens in Docker.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; and a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;A free Light Cloud account at &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;console.light-cloud.com&lt;/a&gt; with GitHub connected.&lt;/li&gt;
&lt;li&gt;A terminal with &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;curl&lt;/a&gt;. On Windows, use &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt; and &lt;a href="https://curl.se/windows/" rel="noopener noreferrer"&gt;&lt;code&gt;curl.exe&lt;/code&gt;&lt;/a&gt;, which ships with Windows 10 and 11.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the app
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo new tutorial-dockerfile-app
&lt;span class="go"&gt;    Creating binary (application) `tutorial-dockerfile-app` package
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tutorial-dockerfile-app
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo add axum tokio &lt;span class="nt"&gt;--features&lt;/span&gt; tokio/full
&lt;span class="go"&gt;      Adding axum v0.8.9 to dependencies
      Adding tokio v1.53.1 to dependencies
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo add serde &lt;span class="nt"&gt;--features&lt;/span&gt; derive
&lt;span class="go"&gt;      Adding serde v1.0.229 to dependencies
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo add serde_json
&lt;span class="go"&gt;      Adding serde_json v1.0.151 to dependencies


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo new tutorial-dockerfile-app
&lt;span class="go"&gt;    Creating binary (application) `tutorial-dockerfile-app` package
&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tutorial-dockerfile-app
&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo add axum tokio &lt;span class="nt"&gt;--features&lt;/span&gt; tokio/full
&lt;span class="go"&gt;      Adding axum v0.8.9 to dependencies
      Adding tokio v1.53.1 to dependencies
&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo add serde &lt;span class="nt"&gt;--features&lt;/span&gt; derive
&lt;span class="go"&gt;      Adding serde v1.0.229 to dependencies
&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo add serde_json
&lt;span class="go"&gt;      Adding serde_json v1.0.151 to dependencies

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output is trimmed. &lt;code&gt;cargo new&lt;/code&gt; also creates a Git repository with a &lt;code&gt;.gitignore&lt;/code&gt; for &lt;code&gt;/target&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Write the service
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;http&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;}};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;unix&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;SignalKind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CountRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CountResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// One JSON object per line; the Logs tab reads "severity" and "message".&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="k"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"word counter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"try"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"POST /count with {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ok"&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CountRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CountResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="py"&gt;.text&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PAYLOAD_TOO_LARGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text is longer than 100000 bytes"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CountResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="py"&gt;.text&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="py"&gt;.text&lt;/span&gt;&lt;span class="nf"&gt;.split_whitespace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="py"&gt;.text&lt;/span&gt;&lt;span class="nf"&gt;.lines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Light Cloud passes the port in PORT; 8080 matches EXPOSE in the Dockerfile.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"8080"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;TcpListener&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0.0.0:{port}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"listening on port {port}"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Finish running requests when Light Cloud stops the instance with SIGTERM.&lt;/span&gt;
    &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_graceful_shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;SignalKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SIGTERM received, shutting down"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules 1, 3 and 4 are in the code: it binds &lt;code&gt;0.0.0.0&lt;/code&gt; on &lt;code&gt;PORT&lt;/code&gt;, it waits for &lt;code&gt;SIGTERM&lt;/code&gt; to shut down, and it prints JSON log lines with a &lt;code&gt;severity&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Write the Dockerfile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: compile. The Rust toolchain is large, but it never reaches production.&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rust:1.97-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Build the dependencies first, with an empty main.rs, so this slow layer is&lt;/span&gt;
&lt;span class="c"&gt;# cached and only rebuilt when Cargo.toml or Cargo.lock change.&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Cargo.toml Cargo.lock ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;src &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"fn main() {}"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; src/main.rs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cargo build &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; src

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;src/main.rs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# Stage 2: run. Only the binary goes into a small Debian image.&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; debian:bookworm-slim&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 10001 app
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/target/release/tutorial-dockerfile-app /usr/local/bin/app&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;span class="c"&gt;# Light Cloud reads EXPOSE to know which port to send traffic to.&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=8080&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["app"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three habits in there are worth copying into any Dockerfile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two stages.&lt;/strong&gt; The build stage has the compiler and every build tool; the final image has the binary and nothing else. The image you run is small and has less in it to attack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies before source.&lt;/strong&gt; Copying only &lt;code&gt;Cargo.toml&lt;/code&gt; and &lt;code&gt;Cargo.lock&lt;/code&gt; first, and building them on their own, puts the slow dependency build in a layer that survives code changes. The same pattern works as &lt;code&gt;package.json&lt;/code&gt; then &lt;code&gt;npm ci&lt;/code&gt;, or &lt;code&gt;requirements.txt&lt;/code&gt; then &lt;code&gt;pip install&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A non-root user.&lt;/strong&gt; &lt;code&gt;USER app&lt;/code&gt; means a bug in the app cannot write to the system. Port 8080 needs no special rights.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the build context small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target
.git

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Run it on your machine
&lt;/h2&gt;

&lt;p&gt;You do not need Docker for this: the same code runs with Cargo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cargo run --release
    Finished `release` profile [optimized] target(s) in 1.80s
     Running `target/release/tutorial-dockerfile-app`
{"message":"listening on port 8080","severity":"INFO"}


PS&amp;gt; wsl
$ cargo run --release
    Finished `release` profile [optimized] target(s) in 1.80s
     Running `target/release/tutorial-dockerfile-app`
{"message":"listening on port 8080","severity":"INFO"}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output is trimmed; the first run also compiles every dependency. The &lt;code&gt;SIGTERM&lt;/code&gt; handler uses Unix signals, which is right for a container that runs on Linux, so on Windows run the app inside &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/install" rel="noopener noreferrer"&gt;WSL&lt;/a&gt; (with Rust installed there) or only in Docker. Stopping it with Ctrl+C sends &lt;code&gt;SIGINT&lt;/code&gt;, not &lt;code&gt;SIGTERM&lt;/code&gt;, so you will not see the shutdown line locally. From a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -X POST http://localhost:8080/count -H "content-type: application/json" -d '{"text":"Bring your own Dockerfile.\nDeploy anything."}'
{"characters":43,"words":6,"lines":2}


PS&amp;gt; curl.exe -X POST http://localhost:8080/count -H "content-type: application/json" -d '{"text":"Bring your own Dockerfile.\nDeploy anything."}'
{"characters":43,"words":6,"lines":2}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have Docker, &lt;code&gt;docker build -t word-counter .&lt;/code&gt; and &lt;code&gt;docker run -p 8080:8080 word-counter&lt;/code&gt; run exactly what Light Cloud will run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Push and deploy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Rust word counter with a multi-stage Dockerfile"&lt;/span&gt;
&lt;span class="c"&gt;# Create an empty repository named tutorial-dockerfile-app on github.com/new first.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git remote add origin https://github.com/YOUR-USERNAME/tutorial-dockerfile-app.git
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-dockerfile-app.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;


PS&amp;gt; git add &lt;span class="nt"&gt;-A&lt;/span&gt;
PS&amp;gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Rust word counter with a multi-stage Dockerfile"&lt;/span&gt;
&lt;span class="c"&gt;# Create an empty repository named tutorial-dockerfile-app on github.com/new first.&lt;/span&gt;
PS&amp;gt; git remote add origin https://github.com/YOUR-USERNAME/tutorial-dockerfile-app.git
PS&amp;gt; git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-dockerfile-app.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;Light Cloud console&lt;/a&gt;, click &lt;strong&gt;New...&lt;/strong&gt; , then &lt;strong&gt;Deploy from GitHub&lt;/strong&gt; , and pick the repository. Light Cloud shows &lt;strong&gt;Dockerfile / Backend&lt;/strong&gt; and &lt;strong&gt;Runs on a server&lt;/strong&gt; : it found no framework it builds itself, and a Dockerfile that says how to build this one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsoxcb7cu4x20qh5e99x1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsoxcb7cu4x20qh5e99x1.webp" alt="The detection banner Dockerfile Backend, based on Dockerfile, with Runs on a server highlighted" width="799" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Port&lt;/strong&gt; under &lt;strong&gt;Advanced&lt;/strong&gt; is &lt;code&gt;8080&lt;/code&gt;, read from &lt;code&gt;EXPOSE&lt;/code&gt;. Click &lt;strong&gt;Deploy&lt;/strong&gt;. The first build compiles every dependency; in my run the service was live 3 minutes 17 seconds after the click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F139dapx64c1qzfwn0w1u.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F139dapx64c1qzfwn0w1u.webp" alt="The Production overview of tutorial-dockerfile-app with the Deployed badge and the URL card highlighted" width="799" height="333"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://main-tutorial-dockerfile-app-yourworkspace.light-cloud.io/count &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"text":"Bring your own Dockerfile.\nDeploy anything."}'&lt;/span&gt;
&lt;span class="go"&gt;{"characters":43,"words":6,"lines":2}


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl.exe &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://main-tutorial-dockerfile-app-yourworkspace.light-cloud.io/count &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"text":"Bring your own Dockerfile.\nDeploy anything."}'&lt;/span&gt;
&lt;span class="go"&gt;{"characters":43,"words":6,"lines":2}

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: See the layer cache at work
&lt;/h2&gt;

&lt;p&gt;Change something in &lt;code&gt;src/main.rs&lt;/code&gt;, commit and push. &lt;strong&gt;Deploy on push&lt;/strong&gt; rebuilds the image, and this time the dependency layers come from the cache. From the build logs of two pushes in a row:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Build&lt;/th&gt;
&lt;th&gt;Dependency stage&lt;/th&gt;
&lt;th&gt;App compile&lt;/th&gt;
&lt;th&gt;Whole build&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First, empty cache&lt;/td&gt;
&lt;td&gt;51.2 s&lt;/td&gt;
&lt;td&gt;7.5 s&lt;/td&gt;
&lt;td&gt;1 min 55 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After a code change&lt;/td&gt;
&lt;td&gt;cached&lt;/td&gt;
&lt;td&gt;8.0 s&lt;/td&gt;
&lt;td&gt;1 min 12 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The cache covers every stage of the Dockerfile, including the builder stage that never ships. That is why the order of &lt;code&gt;COPY&lt;/code&gt; lines matters: everything above the first changed file is reused.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Logs&lt;/strong&gt; tab the rollout is visible too: the new instance logs &lt;code&gt;listening on port 8080&lt;/code&gt; 0.13 seconds after it starts, and the old one logs &lt;code&gt;SIGTERM received, shutting down&lt;/code&gt; before it goes.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The deploy fails the health check: the container did not listen on the port
&lt;/h3&gt;

&lt;p&gt;The app listens on &lt;code&gt;127.0.0.1&lt;/code&gt; or on a fixed port. Bind &lt;code&gt;0.0.0.0&lt;/code&gt; and read &lt;code&gt;PORT&lt;/code&gt;. Check that &lt;code&gt;EXPOSE&lt;/code&gt; matches what the app uses when &lt;code&gt;PORT&lt;/code&gt; is not set.&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>tutorial</category>
      <category>docker</category>
    </item>
    <item>
      <title>Serverless Was a Pricing Model, Not an Architecture</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Wed, 07 Oct 2026 09:36:41 +0000</pubDate>
      <link>https://dev.to/lightcloud/serverless-was-a-pricing-model-not-an-architecture-5bme</link>
      <guid>https://dev.to/lightcloud/serverless-was-a-pricing-model-not-an-architecture-5bme</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftle7nsgwk4wp1kqdtr8g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftle7nsgwk4wp1kqdtr8g.jpg" alt="Serverless Was a Pricing Model, Not an Architecture" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In November 2014, AWS &lt;a href="https://en.wikipedia.org/wiki/AWS_Lambda" rel="noopener noreferrer"&gt;introduced Lambda&lt;/a&gt; with a sentence developers had waited a decade to hear: run code without thinking about servers. The fine print arrived immediately after the applause. Your code had to be a stateless function. It had to finish inside five minutes, a cap that &lt;a href="https://www.amazonaws.cn/en/new/2018/aws-lambda-enables-functions-that-can-run-up-to-15-minutes/" rel="noopener noreferrer"&gt;stood until October 10, 2018&lt;/a&gt;, when it grew to a still-arbitrary fifteen. It woke up cold, sometimes slowly, at exactly the moments you were being watched. And it billed per request and per gigabyte-second, which was the one part of the announcement that was genuinely new.&lt;/p&gt;

&lt;p&gt;Here's the thesis, a decade on: serverless was never an architecture. It was a pricing model, the first mainstream compute bill that hit zero when idle, and the architecture everyone spent years learning was just the container that pricing shipped in. Once you see which part was the invention and which part was the packaging, the last ten years of system design read differently, and so does what comes next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing for the meter
&lt;/h2&gt;

&lt;p&gt;Watch what teams actually built, because the meter's fingerprints are on all of it. Functions couldn't hold state, so state moved into queues, tables, and event buses, and simple call chains became distributed choreography with a dashboard per hop. Functions couldn't run long, so workflow services existed to stitch fifteen-minute fragments into things that were, functionally, long-running processes wearing a disguise. Cold starts punished the request path, so teams paid for provisioned concurrency, which is a fee for making scale-to-zero not scale to zero. Every one of these patterns has a euphemistic name and a conference talk, and every one is a workaround for a constraint that existed to make the billing granularity possible, at 2014's technology, on 2014's virtualization.&lt;/p&gt;

&lt;p&gt;None of that was malice. It was the meter shaping the buildings, the way &lt;a href="https://blog.light-cloud.com/industry/cloud-pricing-is-a-business-model" rel="noopener noreferrer"&gt;pricing always shapes architecture&lt;/a&gt; when the two live together long enough, and it produced systems whose complexity was justified by a bill rather than by a requirement. "We went serverless" too often meant "we refactored a web app into forty functions to make our idle time free", which is a trade with a name in any other market: paying with labor for a discount.&lt;/p&gt;

&lt;p&gt;In fairness to 2014, the constraints began as physics rather than ideology. Billing per request required isolation cheap enough to spin up per request, and the virtualization of the day couldn't do that for arbitrary long-running processes, so the function shape was an honest engineering compromise. AWS then built Firecracker, its microVM layer, precisely to make that isolation nearly free, and the compromise quietly expired. The constraints outlived their reason, the way constraints do once a generation of best practices has calcified around them and a certification industry depends on their permanence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tell
&lt;/h2&gt;

&lt;p&gt;The proof that the architecture was packaging arrived when the pricing escaped it. Billing by the second, scaling to zero, paying nothing while idle: all of that turned out to be perfectly possible for ordinary containers running ordinary processes with ordinary state and no duration caps, which is how &lt;a href="https://blog.light-cloud.com/dev-tools/anatomy-of-a-3-cent-preview-environment" rel="noopener noreferrer"&gt;a preview environment costs cents&lt;/a&gt; and how a &lt;a href="https://blog.light-cloud.com/cloud/same-app-four-platforms" rel="noopener noreferrer"&gt;normal full-stack app bills a few dollars&lt;/a&gt; without being rewritten into event confetti. The moment scale-to-zero economics existed without the function shape, the function shape stopped being the price of admission, and the interesting question became visible: if you could have had the bill without the contortions, what were the contortions for?&lt;/p&gt;

&lt;p&gt;That's what "serverless" should have meant from the beginning, and what we've built our platform around: the pricing revolution applied to ordinary software. Your app, your framework, your process model, billed like Lambda, shaped like nothing in particular. The revolution was in the meter. It was always in the meter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The steelman: the constraints were the point
&lt;/h2&gt;

&lt;p&gt;The strongest defense of classic serverless deserves a fair paragraph, because it's partly right. Event-driven functions are genuinely the correct architecture for a real class of work: webhooks, glue code, bursty parallel jobs, anything that's naturally a small reaction to an event, and forcing statelessness imposed a discipline that made a generation of systems more crash-tolerant than their authors would have chosen to build. Lambda also pioneered the operational bar, patching, capacity, isolation as someone else's problem, that everything since gets measured against. The people who loved it weren't wrong about what they loved.&lt;/p&gt;

&lt;p&gt;Conceded, and the concession has a boundary: a good pattern for reactive glue was sold as a destination architecture for everything, and the selling worked because the bill was irresistible and came bundled. The sin was never the function; it was teaching a decade of developers that the meter's constraints were best practices, so thoroughly that "can this be a Lambda" became a design review question, asked before "what is this program". Architectures should be chosen by the shape of the problem. Bills should reward you for choosing well, and the two decisions should never have been the same decision. Unbundling them is the actual legacy Lambda deserves: it proved the industry would rearchitect anything for a fair meter, and the fair meter no longer asks for the rearchitecture.&lt;/p&gt;

&lt;p&gt;So run the disentangling exercise on your own stack. List what's function-shaped because the problem is event-shaped, and what's function-shaped because in 2019 that was the only door to a fair bill. The first list is fine where it is. The second list is a refactoring you were paid to do once and are paying to maintain forever, and the fair bill comes without it now. How long is your second list?&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>cloud</category>
      <category>serverless</category>
    </item>
    <item>
      <title>FastAPI in Production: From main.py to a Public URL</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Mon, 05 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/lightcloud/fastapi-in-production-from-mainpy-to-a-public-url-4mme</link>
      <guid>https://dev.to/lightcloud/fastapi-in-production-from-mainpy-to-a-public-url-4mme</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffufqkxd6b0yvlbcay5dw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffufqkxd6b0yvlbcay5dw.webp" alt="FastAPI in Production: From main.py to a Public URL" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deploy a FastAPI app on Light Cloud, put &lt;code&gt;fastapi[standard]&lt;/code&gt; in &lt;code&gt;requirements.txt&lt;/code&gt;, keep the app object as &lt;code&gt;app&lt;/code&gt; in &lt;code&gt;main.py&lt;/code&gt;, push to GitHub and click &lt;strong&gt;Deploy&lt;/strong&gt; in the console. Light Cloud detects FastAPI, installs the requirements and starts &lt;code&gt;uvicorn main:app&lt;/code&gt; on the container's port. No Dockerfile, no Procfile. The interactive docs at &lt;code&gt;/docs&lt;/code&gt; are online with the API.&lt;/p&gt;

&lt;p&gt;In my run the first deploy took 97 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;A small book API with validation by &lt;a href="https://docs.pydantic.dev/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt;, live on a &lt;code&gt;light-cloud.io&lt;/code&gt; address, with its Swagger docs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdaoq6e23xvoevrxawmfv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdaoq6e23xvoevrxawmfv.webp" alt="The Swagger UI docs page titled Reading List API with the routes GET /, GET /health, GET /books, POST /books and GET /books/{book_id}" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The title, &lt;strong&gt;Reading List API&lt;/strong&gt; , comes from an environment variable set in the console.&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/light-cloud-com/tutorial-fastapi-api" rel="noopener noreferrer"&gt;github.com/light-cloud-com/tutorial-fastapi-api&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.python.org/downloads/" rel="noopener noreferrer"&gt;Python&lt;/a&gt; 3.10 or later (what FastAPI 0.141 requires); I used Python 3.13.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; and a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;A free Light Cloud account at &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;console.light-cloud.com&lt;/a&gt; with GitHub connected.&lt;/li&gt;
&lt;li&gt;A terminal with &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;curl&lt;/a&gt;. On Windows, use &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt; and &lt;a href="https://curl.se/windows/" rel="noopener noreferrer"&gt;&lt;code&gt;curl.exe&lt;/code&gt;&lt;/a&gt;, which ships with Windows 10 and 11.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the project and a virtual environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir tutorial-fastapi-api
$ cd tutorial-fastapi-api
$ python3 -m venv .venv
$ source .venv/bin/activate


PS&amp;gt; mkdir tutorial-fastapi-api
PS&amp;gt; cd tutorial-fastapi-api
PS&amp;gt; py -m venv .venv
PS&amp;gt; .venv\Scripts\Activate.ps1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, &lt;code&gt;mkdir&lt;/code&gt; also prints a short table describing the new folder. Your prompt shows &lt;code&gt;(.venv)&lt;/code&gt; once the virtual environment is active.&lt;/p&gt;

&lt;p&gt;List the dependencies. &lt;code&gt;fastapi[standard]&lt;/code&gt; brings uvicorn and the &lt;code&gt;fastapi&lt;/code&gt; command with it; the version pin makes the build install exactly what you tested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fastapi[standard]==0.141.1


$ pip install -r requirements.txt
Successfully installed ... fastapi-0.141.1 ... pydantic-2.13.5 ... starlette-1.7.0 ... uvicorn-0.54.0 ...


PS&amp;gt; pip install -r requirements.txt
Successfully installed ... fastapi-0.141.1 ... pydantic-2.13.5 ... starlette-1.7.0 ... uvicorn-0.54.0 ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output is trimmed to the main packages.&lt;/p&gt;

&lt;p&gt;Keep the virtual environment out of Git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.venv/
__pycache__ /
.env

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Write the API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;

&lt;span class="c1"&gt;# The title shows at the top of the interactive docs at /docs.
# Set API_TITLE on Light Cloud to change it without touching the code.
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_TITLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Book API&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BookIn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="c1"&gt;# In memory, so the list starts empty again after every restart.
&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from FastAPI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_books&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BookIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/books/{book_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;book_id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Book not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter for the deploy. The file is called &lt;code&gt;main.py&lt;/code&gt; and the FastAPI object is called &lt;code&gt;app&lt;/code&gt;: that is what Light Cloud starts, as &lt;code&gt;main:app&lt;/code&gt;. And the code never picks a port; the server that runs it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Run it on your machine
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fastapi dev main.py
&lt;span class="go"&gt; 🌐 Server started at http://127.0.0.1:8000
    Documentation at http://127.0.0.1:8000/docs


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fastapi dev main.py
&lt;span class="go"&gt; 🌐 Server started at http://127.0.0.1:8000
    Documentation at http://127.0.0.1:8000/docs

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output is trimmed. In a second terminal, add a book and send one that breaks the rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/books &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -d '{"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999}'
{"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999,"id":1}
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/books &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -d '{"title":"","author":"Nobody","year":3000}'
{"detail":[{"type":"string_too_short","loc":["body","title"],"msg":"String should have at least 1 character","input":"","ctx":{"min_length":1}},{"type":"less_than_equal","loc":["body","year"],"msg":"Input should be less than or equal to 2100","input":3000,"ctx":{"le":2100}}]}


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl.exe &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/books &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="go"&gt;  -d '{"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999}'
{"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999,"id":1}
&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl.exe &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/books &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="go"&gt;  -d '{"title":"","author":"Nobody","year":3000}'
{"detail":[{"type":"string_too_short","loc":["body","title"],"msg":"String should have at least 1 character","input":"","ctx":{"min_length":1}},{"type":"less_than_equal","loc":["body","year"],"msg":"Input should be less than or equal to 2100","input":3000,"ctx":{"le":2100}}]}

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second request gets a &lt;code&gt;422&lt;/code&gt; with both problems listed. Pydantic checked the body against &lt;code&gt;BookIn&lt;/code&gt; before your function ran. Stop the server with Ctrl+C.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Push it to GitHub
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
&lt;span class="nv"&gt;$ &lt;/span&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"FastAPI book API"&lt;/span&gt;
&lt;span class="c"&gt;# Create an empty repository named tutorial-fastapi-api on github.com/new first.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git remote add origin https://github.com/YOUR-USERNAME/tutorial-fastapi-api.git
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-fastapi-api.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;


PS&amp;gt; git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
PS&amp;gt; git add &lt;span class="nt"&gt;-A&lt;/span&gt;
PS&amp;gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"FastAPI book API"&lt;/span&gt;
&lt;span class="c"&gt;# Create an empty repository named tutorial-fastapi-api on github.com/new first.&lt;/span&gt;
PS&amp;gt; git remote add origin https://github.com/YOUR-USERNAME/tutorial-fastapi-api.git
PS&amp;gt; git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-fastapi-api.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that &lt;code&gt;git status&lt;/code&gt; did not list &lt;code&gt;.venv&lt;/code&gt;: the build installs the packages itself from &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Deploy it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;Light Cloud console&lt;/a&gt;, click &lt;strong&gt;New...&lt;/strong&gt; , then &lt;strong&gt;Deploy from GitHub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;tutorial-fastapi-api&lt;/code&gt; and click it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Light Cloud shows &lt;strong&gt;FastAPI / Backend&lt;/strong&gt; , based on &lt;code&gt;requirements.txt&lt;/code&gt;, and &lt;strong&gt;Runs on a server&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ulhnqtlom9z8vkxarp1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ulhnqtlom9z8vkxarp1.webp" alt="The detection banner FastAPI Backend based on requirements.txt, with Runs on a server highlighted" width="799" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Advanced - build settings, environment variables, domain, scaling&lt;/strong&gt;. &lt;strong&gt;Port&lt;/strong&gt; is already &lt;code&gt;8000&lt;/code&gt;, the port uvicorn is started on. Leave it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnz1l33gr09pefs2p80d5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnz1l33gr09pefs2p80d5.webp" alt="The Advanced section with Port 8000 highlighted" width="720" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Environment variables&lt;/strong&gt; , click &lt;strong&gt;Add variable&lt;/strong&gt; and add &lt;code&gt;API_TITLE&lt;/code&gt; with the value &lt;code&gt;Reading List API&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F334vsv6xx1f3u04t274j.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F334vsv6xx1f3u04t274j.webp" alt="The Environment variables section with API_TITLE set to Reading List API" width="720" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Deploy&lt;/strong&gt; and wait for &lt;strong&gt;Deployed&lt;/strong&gt; (97 seconds in my run: the first build installs every package). The &lt;strong&gt;URL&lt;/strong&gt; card has the address:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyhsktolhh899dhn6ccpc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyhsktolhh899dhn6ccpc.webp" alt="The Production overview of tutorial-fastapi-api with the Deployed badge and the URL card highlighted" width="799" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Use the live API and its docs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl https://main-tutorial-fastapi-api-yourworkspace.light-cloud.io/
{"message":"Hello from FastAPI","docs":"/docs"}
$ curl -X POST https://main-tutorial-fastapi-api-yourworkspace.light-cloud.io/books -H "content-type: application/json" \
  -d '{"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999}'
{"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999,"id":1}


PS&amp;gt; curl.exe https://main-tutorial-fastapi-api-yourworkspace.light-cloud.io/
{"message":"Hello from FastAPI","docs":"/docs"}
PS&amp;gt; curl.exe -X POST https://main-tutorial-fastapi-api-yourworkspace.light-cloud.io/books -H "content-type: application/json" `
  -d '{"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999}'
{"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999,"id":1}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first request after a quiet period takes a few seconds (3 seconds in my run) while an instance starts.&lt;/p&gt;

&lt;p&gt;Now open &lt;code&gt;/docs&lt;/code&gt; on your address. Swagger UI lists every route with the title from &lt;code&gt;API_TITLE&lt;/code&gt;. Expand &lt;strong&gt;POST /books&lt;/strong&gt; , click &lt;strong&gt;Try it out&lt;/strong&gt; , edit the example and click &lt;strong&gt;Execute&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ef4yl5oqvgyhzsil6kk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ef4yl5oqvgyhzsil6kk.webp" alt="POST /books in Swagger UI with a Clean Code request body and the server response 201 with the created book" width="800" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;id&lt;/code&gt; may differ. In my run it was &lt;code&gt;3&lt;/code&gt;, although I had added only one book before, from a different terminal. That is the in-memory list at work: each instance keeps its own copy, and the next section explains why that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where data lives
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;books&lt;/code&gt; dictionary lives in the memory of one running instance. On Light Cloud, as on any cloud platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a new deploy replaces the instances, so the list starts empty;&lt;/li&gt;
&lt;li&gt;with &lt;strong&gt;Min instances&lt;/strong&gt; at 0, the API stops when idle and starts empty on the next request;&lt;/li&gt;
&lt;li&gt;under load several instances run at once, each with its own list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is fine for a demo and wrong for real data. Store it in a database: &lt;a href="https://blog.light-cloud.com/tutorials/microservices-on-light-cloud-part-1-deploy" rel="noopener noreferrer"&gt;a frontend, two APIs and a database&lt;/a&gt; shows a FastAPI service with PostgreSQL through &lt;code&gt;psycopg&lt;/code&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The deploy fails because the app cannot be found
&lt;/h3&gt;

&lt;p&gt;Light Cloud starts &lt;code&gt;uvicorn main:app&lt;/code&gt;, or &lt;code&gt;app:app&lt;/code&gt; if the file is &lt;code&gt;app.py&lt;/code&gt;. If your app is in &lt;code&gt;src/api.py&lt;/code&gt; or the object has another name, uvicorn cannot find it. Move the entry point to &lt;code&gt;main.py&lt;/code&gt; at the root with &lt;code&gt;app = FastAPI()&lt;/code&gt;, or set &lt;strong&gt;Root directory&lt;/strong&gt; to the folder that contains &lt;code&gt;main.py&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  FastAPI is not detected
&lt;/h3&gt;

&lt;p&gt;Light Cloud looks for &lt;code&gt;fastapi&lt;/code&gt; in &lt;code&gt;requirements.txt&lt;/code&gt; or &lt;code&gt;pyproject.toml&lt;/code&gt; at the root (or in the &lt;strong&gt;Root directory&lt;/strong&gt; ). A project that installs FastAPI only through another tool's lock file is not detected; add a &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Put a Node.js Express API Online: Port, Environment Variables and Logs</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Sun, 04 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/lightcloud/put-a-nodejs-express-api-online-port-environment-variables-and-logs-nki</link>
      <guid>https://dev.to/lightcloud/put-a-nodejs-express-api-online-port-environment-variables-and-logs-nki</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg0vdow6gmspx8cay7cy0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg0vdow6gmspx8cay7cy0.webp" alt="Put a Node.js Express API Online: Port, Environment Variables and Logs" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To put an Express API online with Light Cloud, make it listen on the port in &lt;code&gt;process.env.PORT&lt;/code&gt;, read its settings from environment variables, push it to GitHub and choose &lt;strong&gt;Deploy from GitHub&lt;/strong&gt; in the console. Light Cloud detects Express, runs &lt;code&gt;npm ci&lt;/code&gt; and &lt;code&gt;npm start&lt;/code&gt; in a container and gives the API an HTTPS address. No Dockerfile is needed. If every log line is JSON with a &lt;code&gt;severity&lt;/code&gt; field, the &lt;strong&gt;Logs&lt;/strong&gt; tab shows each request with its level.&lt;/p&gt;

&lt;p&gt;In my run the first deploy took 72 seconds, and a changed environment variable was live 59 seconds after clicking &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;A small Express 5 API with three routes, live on a &lt;code&gt;light-cloud.io&lt;/code&gt; address:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /&lt;/code&gt; returns a greeting and the Node.js version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /hello/:name&lt;/code&gt; greets by name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /health&lt;/code&gt; answers &lt;code&gt;{"status":"ok"}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the way it picks up three habits every Node.js API needs in the cloud: it listens on the port it is given, it reads settings from environment variables, and it writes one JSON log line per request.&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/light-cloud-com/tutorial-express-api" rel="noopener noreferrer"&gt;github.com/light-cloud-com/tutorial-express-api&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; 18 or later (Express 5 requires 18); I used Node.js 22.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; and a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;A free Light Cloud account at &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;console.light-cloud.com&lt;/a&gt; with GitHub connected. The free Hobby plan includes one service, which is enough for this API.&lt;/li&gt;
&lt;li&gt;A terminal with &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;curl&lt;/a&gt;. On Windows, use &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt; and &lt;a href="https://curl.se/windows/" rel="noopener noreferrer"&gt;&lt;code&gt;curl.exe&lt;/code&gt;&lt;/a&gt;, which ships with Windows 10 and 11.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;tutorial-express-api
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tutorial-express-api
&lt;span class="nv"&gt;$ &lt;/span&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;express
added 68 packages, and audited 69 packages &lt;span class="k"&gt;in &lt;/span&gt;692ms

found 0 vulnerabilities
&lt;span class="nv"&gt;$ &lt;/span&gt;npm pkg &lt;span class="nb"&gt;set type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;module scripts.start&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"node server.js"&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server.js


PS&amp;gt; &lt;span class="nb"&gt;mkdir &lt;/span&gt;tutorial-express-api
PS&amp;gt; &lt;span class="nb"&gt;cd &lt;/span&gt;tutorial-express-api
PS&amp;gt; npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
PS&amp;gt; npm &lt;span class="nb"&gt;install &lt;/span&gt;express
added 68 packages, and audited 69 packages &lt;span class="k"&gt;in &lt;/span&gt;692ms

found 0 vulnerabilities
PS&amp;gt; npm pkg &lt;span class="nb"&gt;set type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;module scripts.start&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"node server.js"&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server.js

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, &lt;code&gt;mkdir&lt;/code&gt; also prints a short table describing the new folder. The last command sets up &lt;code&gt;package.json&lt;/code&gt;: &lt;code&gt;type=module&lt;/code&gt; lets you use &lt;code&gt;import&lt;/code&gt;, and the &lt;code&gt;start&lt;/code&gt; script is how Light Cloud starts the app. Output is trimmed.&lt;/p&gt;

&lt;p&gt;Keep &lt;code&gt;node_modules&lt;/code&gt; out of Git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.env

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Write the API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Light Cloud tells the app which port to listen on through PORT.&lt;/span&gt;
&lt;span class="c1"&gt;// 8080 is the fallback for running it on your own machine.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Settings come from environment variables, so the same code runs&lt;/span&gt;
&lt;span class="c1"&gt;// locally and in production with different values.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GREETING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GREETING&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// One JSON object per line. The Logs tab reads "severity" for the level.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;finish&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WARNING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INFO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GREETING&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from Express`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/hello/:name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GREETING&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INFO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Light Cloud stops old instances with SIGTERM after a new version is live.&lt;/span&gt;
&lt;span class="c1"&gt;// Finish the requests in flight, then exit.&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INFO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGTERM received, shutting down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three habits, in the code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The port comes from &lt;code&gt;PORT&lt;/code&gt;.&lt;/strong&gt; In the cloud, the platform decides the port. A hard-coded port is the most common reason a Node.js app deploys but never answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings come from environment variables.&lt;/strong&gt; &lt;code&gt;GREETING&lt;/code&gt; is a stand-in for real settings such as an API key or a database address: the value lives on the platform, not in the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs are JSON with a &lt;code&gt;severity&lt;/code&gt;.&lt;/strong&gt; Each request is logged when it finishes, as &lt;code&gt;INFO&lt;/code&gt;, &lt;code&gt;WARNING&lt;/code&gt; for 4xx or &lt;code&gt;ERROR&lt;/code&gt; for 5xx.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;SIGTERM&lt;/code&gt; handler is a small extra: when a new version goes live, the old instance gets a few seconds to finish what it is doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Run it on your machine
&lt;/h2&gt;

&lt;p&gt;Start the API in one terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm start
&lt;span class="go"&gt;{"severity":"INFO","message":"listening on port 8080"}


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm start
&lt;span class="go"&gt;{"severity":"INFO","message":"listening on port 8080"}

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it from a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl http://localhost:8080/hello/Ada
{"message":"Hello, Ada!"}
$ curl http://localhost:8080/nope
{"error":"Not found"}


PS&amp;gt; curl.exe http://localhost:8080/hello/Ada
{"message":"Hello, Ada!"}
PS&amp;gt; curl.exe http://localhost:8080/nope
{"error":"Not found"}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first terminal now shows one log line per request, the second as a warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/hello/Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"WARNING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/nope"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stop the API with Ctrl+C.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Push it to GitHub
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
&lt;span class="nv"&gt;$ &lt;/span&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Express API with PORT, env vars and JSON logs"&lt;/span&gt;
&lt;span class="c"&gt;# Create an empty repository named tutorial-express-api on github.com/new first.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git remote add origin https://github.com/YOUR-USERNAME/tutorial-express-api.git
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-express-api.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;


PS&amp;gt; git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
PS&amp;gt; git add &lt;span class="nt"&gt;-A&lt;/span&gt;
PS&amp;gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Express API with PORT, env vars and JSON logs"&lt;/span&gt;
&lt;span class="c"&gt;# Create an empty repository named tutorial-express-api on github.com/new first.&lt;/span&gt;
PS&amp;gt; git remote add origin https://github.com/YOUR-USERNAME/tutorial-express-api.git
PS&amp;gt; git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-express-api.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Deploy it with an environment variable
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;Light Cloud console&lt;/a&gt;, click &lt;strong&gt;New...&lt;/strong&gt; , then &lt;strong&gt;Deploy from GitHub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;tutorial-express-api&lt;/code&gt; and click it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Light Cloud shows &lt;strong&gt;Express / Backend&lt;/strong&gt; and &lt;strong&gt;Runs on a server&lt;/strong&gt; : unlike a static website, an API needs a running process, so it gets a container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1xn8kw6yt7iow1gqw2t.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1xn8kw6yt7iow1gqw2t.webp" alt="The detection banner Express Backend based on package.json and package-lock.json, with Runs on a server highlighted" width="799" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Advanced - build settings, environment variables, domain, scaling&lt;/strong&gt;. &lt;strong&gt;Port&lt;/strong&gt; is &lt;code&gt;8080&lt;/code&gt;. Light Cloud passes it to the app as &lt;code&gt;PORT&lt;/code&gt;, which is exactly what &lt;code&gt;server.js&lt;/code&gt; reads. Leave it as it is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3d1bdv607ejysa4x95jk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3d1bdv607ejysa4x95jk.webp" alt="The Advanced section with Port 8080 highlighted" width="720" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Environment variables&lt;/strong&gt; , click &lt;strong&gt;Add variable&lt;/strong&gt;. Enter &lt;code&gt;GREETING&lt;/code&gt; as the name and &lt;code&gt;Hi&lt;/code&gt; as the value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs79sotgzwdgku8bhlpur.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs79sotgzwdgku8bhlpur.webp" alt="The Environment variables section with GREETING set to Hi" width="720" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Deploy&lt;/strong&gt; and wait for &lt;strong&gt;Deployed&lt;/strong&gt; (72 seconds in my run; a container takes longer than a static site). The &lt;strong&gt;URL&lt;/strong&gt; card has the API's address:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1456md0g38h19vvd6sdf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1456md0g38h19vvd6sdf.webp" alt="The Production overview of tutorial-express-api with the Deployed badge and the URL card highlighted" width="799" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Call the live API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl https://main-tutorial-express-api-yourworkspace.light-cloud.io/
&lt;span class="go"&gt;{"message":"Hi from Express","node":"v22.23.3"}
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl https://main-tutorial-express-api-yourworkspace.light-cloud.io/hello/Ada
&lt;span class="go"&gt;{"message":"Hi, Ada!"}


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl.exe https://main-tutorial-express-api-yourworkspace.light-cloud.io/
&lt;span class="go"&gt;{"message":"Hi from Express","node":"v22.23.3"}
&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl.exe https://main-tutorial-express-api-yourworkspace.light-cloud.io/hello/Ada
&lt;span class="go"&gt;{"message":"Hi, Ada!"}

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The greeting is now &lt;code&gt;Hi&lt;/code&gt;: the value from the console, not the &lt;code&gt;Hello&lt;/code&gt; fallback in the code.&lt;/p&gt;

&lt;p&gt;The first request after a quiet period can take a few seconds (3.6 seconds in my run). With &lt;strong&gt;Min instances&lt;/strong&gt; at 0 the API sleeps when nobody uses it, which keeps it free, and wakes on the next request. &lt;a href="https://blog.light-cloud.com/tutorials/microservices-on-light-cloud-part-4-cold-starts" rel="noopener noreferrer"&gt;Cold starts, measured&lt;/a&gt; explains the trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Change a setting without touching the code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;Settings&lt;/strong&gt; tab of &lt;strong&gt;Production&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Next to &lt;strong&gt;Environment Variables&lt;/strong&gt; , click &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Change the value of &lt;code&gt;GREETING&lt;/code&gt; to &lt;code&gt;Welcome&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe8h08uqfenkmrnfk22w6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe8h08uqfenkmrnfk22w6.webp" alt="The Environment Variables editor with GREETING changed to Welcome and the Save button highlighted" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Saving redeploys the same commit with the new value. About a minute later (59 seconds in my run):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl https://main-tutorial-express-api-yourworkspace.light-cloud.io/
{"message":"Welcome from Express","node":"v22.23.3"}


PS&amp;gt; curl.exe https://main-tutorial-express-api-yourworkspace.light-cloud.io/
{"message":"Welcome from Express","node":"v22.23.3"}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 8: Read the logs
&lt;/h2&gt;

&lt;p&gt;Open the &lt;strong&gt;Logs&lt;/strong&gt; tab. Every line your API printed is here, next to lines Light Cloud adds itself, such as &lt;code&gt;GET https://.../nope 404&lt;/code&gt; for each request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6efee2hd1rkjew6qfddi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6efee2hd1rkjew6qfddi.webp" alt="The Logs tab with the request lines, a WARNING line for the 404 request and the listening on port 8080 line highlighted" width="799" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because the lines are JSON with a &lt;code&gt;severity&lt;/code&gt;, each one has the right level: the request to &lt;code&gt;/nope&lt;/code&gt; is a &lt;strong&gt;WARNING&lt;/strong&gt; , the others are &lt;strong&gt;INFO&lt;/strong&gt;. Click a line to open it. Everything else the API logged is under &lt;strong&gt;JSON Payload&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fic2fy61k8koepnn0g1wr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fic2fy61k8koepnn0g1wr.webp" alt="An expanded log line with Severity WARNING and the JSON payload method GET, status 404, path /nope" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two more lines are worth finding after Step 7: &lt;code&gt;listening on port 8080&lt;/code&gt; from the new instance, and &lt;code&gt;SIGTERM received, shutting down&lt;/code&gt; from the old one. That is the graceful shutdown from Step 2 at work.&lt;/p&gt;

&lt;p&gt;To see only problems, set the &lt;strong&gt;level&lt;/strong&gt; filter to &lt;strong&gt;warnings and above&lt;/strong&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The deploy fails: container failed to start and listen on the port
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable within the allocated timeout.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app listens on a fixed port instead of &lt;code&gt;PORT&lt;/code&gt;. I tried it with &lt;code&gt;app.listen(3000)&lt;/code&gt;: the deploy failed after about four minutes, and the previous version kept serving the whole time. Read the port with &lt;code&gt;Number(process.env.PORT) || 8080&lt;/code&gt;, commit and push. If your app must use another port, set &lt;strong&gt;Port&lt;/strong&gt; under &lt;strong&gt;Advanced&lt;/strong&gt; to match instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  The deploy fails in the build step with npm ci
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm ci&lt;/code&gt; needs a &lt;code&gt;package-lock.json&lt;/code&gt; that matches &lt;code&gt;package.json&lt;/code&gt;. Run &lt;code&gt;npm install&lt;/code&gt; on your machine, commit &lt;code&gt;package-lock.json&lt;/code&gt; and push again.&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>tutorial</category>
      <category>node</category>
    </item>
    <item>
      <title>Deploy a SvelteKit Site as Static Files: adapter-static and Prerendering</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Sat, 03 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/lightcloud/deploy-a-sveltekit-site-as-static-files-adapter-static-and-prerendering-1bp</link>
      <guid>https://dev.to/lightcloud/deploy-a-sveltekit-site-as-static-files-adapter-static-and-prerendering-1bp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pzx6bmsmi3gukpc3vit.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pzx6bmsmi3gukpc3vit.webp" alt="Deploy a SvelteKit Site as Static Files: adapter-static and Prerendering" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deploy a SvelteKit site on Light Cloud as static files, create it with &lt;code&gt;adapter-static&lt;/code&gt;, add &lt;code&gt;export const prerender = true&lt;/code&gt; to &lt;code&gt;src/routes/+layout.ts&lt;/code&gt;, push it to GitHub and click &lt;strong&gt;Deploy&lt;/strong&gt; in the console. Light Cloud detects SvelteKit and adapter-static, runs &lt;code&gt;npm run build&lt;/code&gt; and serves the &lt;code&gt;build&lt;/code&gt; folder from the edge. Each page is a real HTML file, so &lt;code&gt;/about&lt;/code&gt; opened directly shows the About page.&lt;/p&gt;

&lt;p&gt;In my run the deploy took 30 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;A two-page SvelteKit site, Home and About, prerendered to HTML at build time and live on a &lt;code&gt;light-cloud.io&lt;/code&gt; address. It follows the visitor's light or dark system setting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwlq42hbcqwcwf7vs2iwg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwlq42hbcqwcwf7vs2iwg.webp" alt="The finished SvelteKit site served by Light Cloud: Home and About links above the headline Hello from Light Cloud" width="760" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live demo: &lt;a href="https://main-tutorial-sveltekit-app-examples.light-cloud.io" rel="noopener noreferrer"&gt;main-tutorial-sveltekit-app-examples.light-cloud.io&lt;/a&gt;. Source code: &lt;a href="https://github.com/light-cloud-com/tutorial-sveltekit-app" rel="noopener noreferrer"&gt;github.com/light-cloud-com/tutorial-sveltekit-app&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static or server: which adapter?
&lt;/h2&gt;

&lt;p&gt;SvelteKit builds for a target through an adapter. The two that matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;adapter-static&lt;/strong&gt; builds every page to HTML ahead of time. Nothing runs on a server; the files are served from the edge. Good for sites, docs, blogs and portfolios, and it runs on the free plan. This guide uses it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;adapter-node&lt;/strong&gt; builds a Node.js server that renders pages on each request. You need it for form actions, server &lt;code&gt;load&lt;/code&gt; functions that read per-visitor data, or API routes. On Light Cloud that runs as a container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are not sure, start static. Moving to adapter-node later is a change of adapter, not a rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; 20.19 or later, or 22.12 or later (what Vite 8 requires); I used Node.js 24.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; and a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;A free Light Cloud account at &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;console.light-cloud.com&lt;/a&gt; with GitHub connected.&lt;/li&gt;
&lt;li&gt;On Windows, use &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt;. The last check uses &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;curl&lt;/a&gt; (on Windows, &lt;a href="https://curl.se/windows/" rel="noopener noreferrer"&gt;&lt;code&gt;curl.exe&lt;/code&gt;&lt;/a&gt;, which ships with Windows 10 and 11).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the SvelteKit app
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://svelte.dev/docs/cli/sv-create" rel="noopener noreferrer"&gt;Svelte CLI&lt;/a&gt;, &lt;code&gt;sv&lt;/code&gt;, can add adapter-static while it creates the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx sv@latest create tutorial-sveltekit-app --template minimal --types ts --add sveltekit-adapter="adapter:static" --install npm
┌ Welcome to the Svelte CLI! (v0.17.1)
│
◆ Project created
│
◆ Successfully setup add-ons: sveltekit-adapter
│
◆ Successfully installed dependencies with npm
│
└ You're all set!


PS&amp;gt; npx sv@latest create tutorial-sveltekit-app --template minimal --types ts --add sveltekit-adapter="adapter:static" --install npm
┌ Welcome to the Svelte CLI! (v0.17.1)
│
◆ Project created
│
◆ Successfully setup add-ons: sveltekit-adapter
│
◆ Successfully installed dependencies with npm
│
└ You're all set!

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--template minimal&lt;/code&gt; gives an almost empty site and &lt;code&gt;--types ts&lt;/code&gt; sets up TypeScript. Output is trimmed.&lt;/p&gt;

&lt;p&gt;Looking for &lt;code&gt;svelte.config.js&lt;/code&gt;? New projects no longer have one. The adapter is set in &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sveltejs/adapter-static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sveltekit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sveltejs/kit/vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;sveltekit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;compilerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Force runes mode for the project, except for libraries. Can be removed in svelte 6.&lt;/span&gt;
                &lt;span class="na"&gt;runes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[/\\]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Turn on prerendering
&lt;/h2&gt;

&lt;p&gt;adapter-static can only publish pages that were built ahead of time, and SvelteKit does not prerender by default. Build the new project as it is and it stops with &lt;code&gt;Error: Encountered dynamic routes&lt;/code&gt; (the full message is under Troubleshooting).&lt;/p&gt;

&lt;p&gt;One line fixes it for the whole site. Create this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prerender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Add the pages
&lt;/h2&gt;

&lt;p&gt;A shared layout with two links and a few styles. &lt;code&gt;color-scheme: light dark&lt;/code&gt; lets the browser follow the visitor's system theme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$app/state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;favicon&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/assets/favicon.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$props&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;svelte:head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;favicon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svelte:head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://blog.light-cloud.com/"&lt;/span&gt; &lt;span class="na"&gt;aria-current=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://blog.light-cloud.com/about"&lt;/span&gt; &lt;span class="na"&gt;aria-current=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;render&lt;/span&gt; &lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nd"&gt;:global&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="py"&gt;color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;:global&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#d93a00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ff8a4c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;aria-current&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'page'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the starter home page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svelte:head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svelte:head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello from Light Cloud&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;A SvelteKit site, prerendered with adapter-static.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In SvelteKit a folder is a route, so the About page lives in an &lt;code&gt;about&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svelte:head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svelte:head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This page was prerendered to HTML at build time and is served from the edge.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Run it and build it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run dev
  VITE v8.3.1 ready in 441 ms

  ➜ Local: http://localhost:5173/
$ npm run build
✓ built in 974ms
&amp;gt; Using @sveltejs/adapter-static
  Wrote site to "build"
  ✔ done
$ ls build
_app
about.html
index.html
robots.txt


PS&amp;gt; npm run dev
  VITE v8.3.1 ready in 441 ms

  ➜ Local: http://localhost:5173/
PS&amp;gt; npm run build
✓ built in 974ms
&amp;gt; Using @sveltejs/adapter-static
  Wrote site to "build"
  ✔ done
PS&amp;gt; Get-ChildItem build -Name
_app
about.html
index.html
robots.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:5173&lt;/code&gt;, click between the two pages, then stop the server with Ctrl+C before you build. Build output is trimmed.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; folder is the whole site: one HTML file per page, plus the JavaScript in &lt;code&gt;_app&lt;/code&gt; that makes links switch pages without a reload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Push it to GitHub
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
&lt;span class="nv"&gt;$ &lt;/span&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"SvelteKit site with adapter-static"&lt;/span&gt;
&lt;span class="c"&gt;# Create an empty repository named tutorial-sveltekit-app on github.com/new first.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git remote add origin https://github.com/YOUR-USERNAME/tutorial-sveltekit-app.git
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-sveltekit-app.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;


PS&amp;gt; git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
PS&amp;gt; git add &lt;span class="nt"&gt;-A&lt;/span&gt;
PS&amp;gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"SvelteKit site with adapter-static"&lt;/span&gt;
&lt;span class="c"&gt;# Create an empty repository named tutorial-sveltekit-app on github.com/new first.&lt;/span&gt;
PS&amp;gt; git remote add origin https://github.com/YOUR-USERNAME/tutorial-sveltekit-app.git
PS&amp;gt; git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-sveltekit-app.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project's &lt;code&gt;.gitignore&lt;/code&gt; already keeps &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.svelte-kit&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; out of Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Deploy it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;Light Cloud console&lt;/a&gt;, click &lt;strong&gt;New...&lt;/strong&gt; , then &lt;strong&gt;Deploy from GitHub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;tutorial-sveltekit-app&lt;/code&gt; and click it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Light Cloud shows &lt;strong&gt;SvelteKit&lt;/strong&gt; and, in the reasons, &lt;strong&gt;adapter-static&lt;/strong&gt; , with &lt;strong&gt;Served from the edge&lt;/strong&gt;. It found adapter-static in &lt;code&gt;package.json&lt;/code&gt;, so it deploys the site as static files rather than as a server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq4dy0isn7onjldb9px5l.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq4dy0isn7onjldb9px5l.webp" alt="The detection banner SvelteKit, based on package.json, package-lock.json, SvelteKit detected, adapter-static, with Served from the edge highlighted" width="799" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Optional: under &lt;strong&gt;Advanced - build settings, environment variables, domain, scaling&lt;/strong&gt; you can see &lt;code&gt;npm ci&lt;/code&gt;, &lt;code&gt;npm run build&lt;/code&gt; and the output directory &lt;code&gt;build&lt;/code&gt;. Nothing to change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa0g8zs94q2ocvwoedpar.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa0g8zs94q2ocvwoedpar.webp" alt="The Advanced section with Install command npm ci, Output directory build and Build command npm run build highlighted" width="720" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Deploy&lt;/strong&gt; and wait for &lt;strong&gt;Deployed&lt;/strong&gt; (30 seconds in my run). Open the address from the &lt;strong&gt;URL&lt;/strong&gt; card:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtwj140lbhls8i5dmi5i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtwj140lbhls8i5dmi5i.webp" alt="The Production overview with the Deployed badge and the URL card highlighted" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From now on, every push to &lt;code&gt;main&lt;/code&gt; redeploys the site by itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Open a page directly
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;https://main-tutorial-sveltekit-app-yourworkspace.light-cloud.io/about&lt;/code&gt; in a new tab, or refresh the page while on About. Light Cloud serves &lt;code&gt;about.html&lt;/code&gt; for &lt;code&gt;/about&lt;/code&gt;, so the page comes straight from the prerendered file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg59z03oojqku4244lqzg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg59z03oojqku4244lqzg.webp" alt="The About page opened directly at /about, with the About link in bold" width="760" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see that the page really comes prerendered, right-click it and choose &lt;strong&gt;View Page Source&lt;/strong&gt; : the HTML already contains &lt;code&gt;&amp;lt;h1&amp;gt;About&amp;lt;/h1&amp;gt;&lt;/code&gt;, before any JavaScript runs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/about/&lt;/code&gt; with a trailing slash works too. A path with no page, such as &lt;code&gt;/nope&lt;/code&gt;, falls back to &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Error: Encountered dynamic routes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@sveltejs/adapter-static: all routes must be fully prerenderable, but found the following routes that are dynamic:
  - src/routes/
error during build:
Error: Encountered dynamic routes

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prerendering is off. Add &lt;code&gt;export const prerender = true;&lt;/code&gt; to &lt;code&gt;src/routes/+layout.ts&lt;/code&gt; (Step 2), build again, commit and push.&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>tutorial</category>
      <category>svelte</category>
    </item>
    <item>
      <title>Regulators vs. the Duopoly</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Fri, 02 Oct 2026 13:56:08 +0000</pubDate>
      <link>https://dev.to/lightcloud/regulators-vs-the-duopoly-3ljh</link>
      <guid>https://dev.to/lightcloud/regulators-vs-the-duopoly-3ljh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F404amnkss27ptk8p1z4g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F404amnkss27ptk8p1z4g.jpg" alt="Regulators vs. the Duopoly" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On March 31, 2026, the UK's Competition and Markets Authority &lt;a href="https://www.cloudswitched.com/news/cma-cloud-market-verdict-uk-multicloud-business-2026" rel="noopener noreferrer"&gt;drew a line under its three-year cloud investigation&lt;/a&gt;, accepting voluntary commitments from AWS and Microsoft on egress fees and interoperability. The mountain labored for three years, produced findings that "competition is not working well", recommended the strongest tool in its new kit, and then delivered a memorandum of understanding.&lt;/p&gt;

&lt;p&gt;That arc is worth studying, not mocking, because the CMA case is the cleanest specimen we have of the question this post is about: when regulators finally move against cloud concentration, which remedies change anything real, and which are theater performed for the annual report? My scorecard, argued below: portability mandates and licensing interventions are real, egress concessions are a fee that died years ago wearing a medal, and the credible threat that got traded away was the only thing the duopoly actually feared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three years in four beats
&lt;/h2&gt;

&lt;p&gt;Beat one: the CMA's investigation &lt;a href="https://www.datacenterdynamics.com/en/news/competition-is-not-working-well-says-uk-cma-concluding-cloud-market-investigation/" rel="noopener noreferrer"&gt;found the structural problems&lt;/a&gt; everyone suspected: AWS and Microsoft each holding roughly 30-40% of the UK market, egress fees and technical barriers suppressing switching, and Microsoft's software licensing practices tilting the field against rivals running its products. Beat two: the &lt;a href="https://assets.publishing.service.gov.uk/media/688b20e6ff8c05468cb7b120/summary_of_final_decision.pdf" rel="noopener noreferrer"&gt;final decision in July 2025&lt;/a&gt; recommended designating both companies with strategic market status under the Digital Markets, Competition and Consumers Act, the UK's new regime for binding, tailored conduct rules. Beat three: March 2026, &lt;a href="https://www.macfarlanes.com/insights/102mqfd/cma-decision-to-shelve-cloud-services-sms-investigations-raises-questions-around/" rel="noopener noreferrer"&gt;the SMS route was shelved&lt;/a&gt; in favor of the voluntary commitments. Beat four, and the one to watch: a separate &lt;a href="https://www.computerweekly.com/news/366640828/CMA-to-launch-strategic-market-status-investigation-into-Microsoft-Amazon-Web-Services-off-the-hook" rel="noopener noreferrer"&gt;SMS investigation into Microsoft's software licensing&lt;/a&gt; spun up regardless, aimed at the practice the findings called out most sharply.&lt;/p&gt;

&lt;p&gt;The UK case carries weight beyond the UK, which is why the shelving mattered. The DMCCA regime was built specifically to move faster than classic competition law, other regulators are watching its first outings the way case law gets watched, and the cloud investigation was its most prominent test. A debut that ends in voluntary undertakings teaches every watching authority, and every watched company, what the new powers cost to invoke. Precedents about hesitation travel as far as precedents about action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sorting the remedies
&lt;/h2&gt;

&lt;p&gt;Run each remedy through one test: does it change what a customer can do, or what a vendor must say?&lt;/p&gt;

&lt;p&gt;Egress commitments fail the test instantly, and we've &lt;a href="https://blog.light-cloud.com/multi-cloud/egress-fees-are-dead-lock-in-isnt" rel="noopener noreferrer"&gt;written the obituary already&lt;/a&gt;: exit egress died globally in 2024 under EU Data Act pressure, so promising a regulator the corpse stays dead costs the duopoly nothing and headlines beautifully. It's the pattern from that post repeating at regulatory scale: concessions cluster on the locks vendors stopped defending, never on the ones doing the holding. The same sorting works on any jurisdiction's package, which is the reason to learn it once: the EU's Data Act portability rules pass the test because they changed what a customer can do with their feet, while a dozen codes of conduct since have failed it politely.&lt;/p&gt;

&lt;p&gt;Interoperability commitments are contingent: real if they arrive with technical specificity, deadlines, and someone with authority to measure compliance; theater if they stay at the altitude of "committed to open standards", which is where voluntary undertakings historically retire. The licensing intervention is the real one, and the CMA's own sequencing admits it, because licensing is where the findings located actual harm, and licensing is precisely what got carved out for the binding-powers track rather than the handshake track. And the SMS designation itself was the remedy that mattered most by never being used: the threat of tailored, enforceable conduct rules is what brought voluntary offers to the table at all. Trading it away converts a bargaining position into paper, and the price of that trade will be visible in how the commitments age.&lt;/p&gt;

&lt;h2&gt;
  
  
  The steelman: the handshake might be right
&lt;/h2&gt;

&lt;p&gt;The defense of the CMA's choice is better than it looks. Voluntary commitments arrive in months; SMS designations arrive after years of process and litigation, against opponents with infinite legal budgets, under a regime so new it has no case law, and regulatory humility about untested powers is a virtue, not cowardice. There's also a genuine question of timing: between the Data Act, &lt;a href="https://blog.light-cloud.com/sovereignty/your-regulator-wants-an-exit-plan" rel="noopener noreferrer"&gt;DORA's exit requirements&lt;/a&gt;, and the &lt;a href="https://blog.light-cloud.com/industry/neoclouds-and-the-great-unbundling" rel="noopener noreferrer"&gt;unbundling the market did on its own&lt;/a&gt;, a lot of what a 2023 remedy would have targeted has already moved. Regulate the market you have, not the one your case opened against.&lt;/p&gt;

&lt;p&gt;Concede the speed argument whole. The answer is that speed and credibility aren't the same axis: a fast remedy with no enforcement mechanism is just fast, and the entire history of voluntary undertakings in tech is a history of compliance narrowing to the letter while the practice regrows around it. The honest verdict on the CMA case won't be available until the licensing probe shows whether the binding track has teeth. That's the scoreboard to watch, and it's refreshingly concrete: either Microsoft's licensing terms for rivals change in measurable ways by 2027, or the UK's shiny new digital markets regime met the duopoly and blinked.&lt;/p&gt;

&lt;p&gt;For everyone downstream, the practical reading is the one this blog keeps arriving at from every direction: regulation will occasionally hand you a right, and rights you can't exercise are decor. The Data Act gave you free exit; &lt;a href="https://blog.light-cloud.com/multi-cloud/multi-cloud-overrated-portability-isnt" rel="noopener noreferrer"&gt;portability&lt;/a&gt; is what makes it usable. The CMA may yet fix licensing; your architecture decides whether you could act on the fix. Regulators move in years and blink under pressure. Your exit plan doesn't have to do either. Which one are you betting on?&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>cloud</category>
      <category>multicloud</category>
    </item>
    <item>
      <title>How to Deploy an Angular App on Light Cloud, Deep Links Included</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Fri, 02 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/lightcloud/how-to-deploy-an-angular-app-on-light-cloud-deep-links-included-2402</link>
      <guid>https://dev.to/lightcloud/how-to-deploy-an-angular-app-on-light-cloud-deep-links-included-2402</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprtrrcibl2ehk83eeshl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprtrrcibl2ehk83eeshl.webp" alt="How to Deploy an Angular App on Light Cloud, Deep Links Included" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deploy an Angular app on Light Cloud, push it to GitHub, choose &lt;strong&gt;Deploy from GitHub&lt;/strong&gt; in the console and click &lt;strong&gt;Deploy&lt;/strong&gt;. Light Cloud detects Angular, reads &lt;code&gt;angular.json&lt;/code&gt; to find the nested build folder (&lt;code&gt;dist/&amp;lt;project name&amp;gt;/browser&lt;/code&gt;), runs &lt;code&gt;npm ci&lt;/code&gt; and &lt;code&gt;npm run build&lt;/code&gt;, and serves the result from the edge. Angular Router deep links such as &lt;code&gt;/about&lt;/code&gt; work without any redirect rules.&lt;/p&gt;

&lt;p&gt;In my run the first deploy took 41 seconds, and a pushed change was live about a minute after &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;A small Angular app with two pages, Home and About, linked with Angular Router and live on a &lt;code&gt;light-cloud.io&lt;/code&gt; address. It follows the visitor's light or dark system setting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c9ir5ck4ii35hpggp8c.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c9ir5ck4ii35hpggp8c.webp" alt="The finished Angular site served by Light Cloud: Home and About links above the headline Hello from Light Cloud" width="760" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live demo: &lt;a href="https://main-tutorial-angular-website-examples.light-cloud.io" rel="noopener noreferrer"&gt;main-tutorial-angular-website-examples.light-cloud.io&lt;/a&gt;. Source code: &lt;a href="https://github.com/light-cloud-com/tutorial-angular-website" rel="noopener noreferrer"&gt;github.com/light-cloud-com/tutorial-angular-website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; 22.22.3 or later, or 24.15.0 or later. This is what the &lt;a href="https://angular.dev/tools/cli" rel="noopener noreferrer"&gt;Angular CLI&lt;/a&gt; 22.2 requires; I used Node.js 24.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; and a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;A free Light Cloud account at &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;console.light-cloud.com&lt;/a&gt; with GitHub connected.&lt;/li&gt;
&lt;li&gt;On Windows, use &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt;. The last check uses &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;curl&lt;/a&gt; (on Windows, &lt;a href="https://curl.se/windows/" rel="noopener noreferrer"&gt;&lt;code&gt;curl.exe&lt;/code&gt;&lt;/a&gt;, which ships with Windows 10 and 11).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New to deploying from GitHub? The &lt;a href="https://blog.light-cloud.com/tutorials/deploy-a-react-app" rel="noopener noreferrer"&gt;React guide&lt;/a&gt; walks through the same console screens in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Angular app
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;--defaults&lt;/code&gt; accepts the recommended answers without asking, and &lt;code&gt;--ssr=false&lt;/code&gt; keeps the app static, with no server-side rendering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx @angular/cli@latest new tutorial-angular-website &lt;span class="nt"&gt;--defaults&lt;/span&gt; &lt;span class="nt"&gt;--ssr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;span class="go"&gt;CREATE tutorial-angular-website/angular.json (1957 bytes)
CREATE tutorial-angular-website/package.json (800 bytes)
CREATE tutorial-angular-website/src/main.ts (222 bytes)
CREATE tutorial-angular-website/src/app/app.ts (307 bytes)
CREATE tutorial-angular-website/src/app/app.routes.ts (77 bytes)
✔ Packages installed successfully.
    Successfully initialized git.


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx @angular/cli@latest new tutorial-angular-website &lt;span class="nt"&gt;--defaults&lt;/span&gt; &lt;span class="nt"&gt;--ssr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;span class="go"&gt;CREATE tutorial-angular-website/angular.json (1957 bytes)
CREATE tutorial-angular-website/package.json (800 bytes)
CREATE tutorial-angular-website/src/main.ts (222 bytes)
CREATE tutorial-angular-website/src/app/app.ts (307 bytes)
CREATE tutorial-angular-website/src/app/app.routes.ts (77 bytes)
✔ Packages installed successfully.
    Successfully initialized git.

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output is trimmed; the CLI lists every file it creates. It also installs the packages and makes the first Git commit for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Run it and build it once
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tutorial-angular-website
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm start
&lt;span class="go"&gt;Watch mode enabled. Watching for file changes...
  ➜ Local: http://localhost:4200/
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run build
&lt;span class="go"&gt;Initial chunk files | Names | Raw size | Estimated transfer size
main-AMMXP5ZW.js | main | 218.81 kB | 59.99 kB
styles-5INURTSO.css | styles | 0 bytes | 0 bytes

Application bundle generation complete. [1.918 seconds]

Output location: /Users/you/tutorial-angular-website/dist/tutorial-angular-website


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tutorial-angular-website
&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm start
&lt;span class="go"&gt;Watch mode enabled. Watching for file changes...
  ➜ Local: http://localhost:4200/
&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run build
&lt;span class="go"&gt;Initial chunk files | Names | Raw size | Estimated transfer size
main-AMMXP5ZW.js | main | 218.81 kB | 59.99 kB
styles-5INURTSO.css | styles | 0 bytes | 0 bytes

Application bundle generation complete. [1.918 seconds]

Output location: C:\Users\you\tutorial-angular-website\dist\tutorial-angular-website

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:4200&lt;/code&gt; to see the Angular starter page, then stop the server with Ctrl+C before you build. Output is trimmed.&lt;/p&gt;

&lt;p&gt;Look at the output location: the site's files end up one level deeper, in &lt;code&gt;dist/tutorial-angular-website/browser&lt;/code&gt;. That nested folder is the one a host must publish, and it is the most common reason an Angular deploy shows a blank page elsewhere. Light Cloud finds it for you in Step 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Push it to GitHub
&lt;/h2&gt;

&lt;p&gt;The Angular CLI already created the repository and the first commit, so one command creates the GitHub repository and pushes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create an empty repository named tutorial-angular-website on github.com/new first.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git remote add origin https://github.com/YOUR-USERNAME/tutorial-angular-website.git
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-angular-website.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;


&lt;span class="c"&gt;# Create an empty repository named tutorial-angular-website on github.com/new first.&lt;/span&gt;
PS&amp;gt; git remote add origin https://github.com/YOUR-USERNAME/tutorial-angular-website.git
PS&amp;gt; git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
To https://github.com/YOUR-USERNAME/tutorial-angular-website.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] main -&amp;gt; main
branch &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branch is named after your Git setting &lt;code&gt;init.defaultBranch&lt;/code&gt;. If yours is &lt;code&gt;master&lt;/code&gt;, that is fine; Light Cloud deploys whichever branch the repository uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Deploy it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;Light Cloud console&lt;/a&gt;, click &lt;strong&gt;New...&lt;/strong&gt; , then &lt;strong&gt;Deploy from GitHub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;tutorial-angular-website&lt;/code&gt; and click it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Light Cloud shows &lt;strong&gt;Angular / Frontend&lt;/strong&gt; and &lt;strong&gt;Served from the edge&lt;/strong&gt;. The banner also lists what it read from &lt;code&gt;angular.json&lt;/code&gt;: &lt;strong&gt;Custom output: dist/tutorial-angular-website/browser&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1720dxvn77y11eovey7u.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1720dxvn77y11eovey7u.webp" alt="The detection banner Angular Frontend with the custom output dist/tutorial-angular-website/browser, and Served from the edge highlighted" width="752" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Optional: open &lt;strong&gt;Advanced - build settings, environment variables, domain, scaling&lt;/strong&gt;. The &lt;strong&gt;Output directory&lt;/strong&gt; already holds the nested folder, next to &lt;code&gt;npm ci&lt;/code&gt; and &lt;code&gt;npm run build&lt;/code&gt;. Nothing to change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7vh65752g0lgjvc6roy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7vh65752g0lgjvc6roy.webp" alt="The Advanced section with Install command npm ci, Output directory dist/tutorial-angular-website/browser and Build command npm run build highlighted" width="720" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Deploy&lt;/strong&gt; and wait for &lt;strong&gt;Deployed&lt;/strong&gt; (41 seconds in my run). Open the address from the &lt;strong&gt;URL&lt;/strong&gt; card:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwauttwmxj0pcv5y7d2kl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwauttwmxj0pcv5y7d2kl.webp" alt="The Production overview with the Deployed badge and the URL card highlighted" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Add a second page with Angular Router
&lt;/h2&gt;

&lt;p&gt;The starter has a router but no routes. Generate two page components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx ng generate component home
&lt;span class="go"&gt;CREATE src/app/home/home.css (0 bytes)
CREATE src/app/home/home.spec.ts (518 bytes)
CREATE src/app/home/home.ts (177 bytes)
CREATE src/app/home/home.html (19 bytes)
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx ng generate component about
&lt;span class="go"&gt;CREATE src/app/about/about.css (0 bytes)
CREATE src/app/about/about.spec.ts (525 bytes)
CREATE src/app/about/about.ts (181 bytes)
CREATE src/app/about/about.html (20 bytes)


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx ng generate component home
&lt;span class="go"&gt;CREATE src/app/home/home.css (0 bytes)
CREATE src/app/home/home.spec.ts (518 bytes)
CREATE src/app/home/home.ts (177 bytes)
CREATE src/app/home/home.html (19 bytes)
&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx ng generate component about
&lt;span class="go"&gt;CREATE src/app/about/about.css (0 bytes)
CREATE src/app/about/about.spec.ts (525 bytes)
CREATE src/app/about/about.ts (181 bytes)
CREATE src/app/about/about.html (20 bytes)

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give each page a path. &lt;code&gt;title&lt;/code&gt; sets the browser tab title:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;About&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./about/about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./home/home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;About&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;About&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the whole starter page with two links and the router outlet, where the current page appears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt; &lt;span class="na"&gt;[routerLinkActiveOptions]=&lt;/span&gt;&lt;span class="s"&gt;"{ exact: true }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/about"&lt;/span&gt; &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;router-outlet&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The links need &lt;code&gt;RouterLink&lt;/code&gt; and &lt;code&gt;RouterLinkActive&lt;/code&gt; in the component's imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RouterLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RouterLinkActive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RouterOutlet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RouterOutlet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RouterLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RouterLinkActive&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write the two pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello from Light Cloud&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;An Angular app built with the Angular CLI and served from the edge.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;


&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This page lives at /about. Open it directly or refresh it, and Angular Router still shows it.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a few global styles. &lt;code&gt;color-scheme: light dark&lt;/code&gt; lets the browser follow the visitor's system theme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#c3002f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ff6b8b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check it with &lt;code&gt;npm start&lt;/code&gt;, then commit and push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-A&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add Home and About pages with Angular Router"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-A&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add Home and About pages with Angular Router"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deploy on push&lt;/strong&gt; is on by default, so Light Cloud builds the new version by itself. The &lt;strong&gt;Deployments&lt;/strong&gt; tab lists it as deployed by &lt;strong&gt;GitHub Auto-Deploy&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw6tadoku6sbahmddijel.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw6tadoku6sbahmddijel.webp" alt="The Deployments tab with the commit Add Home and About pages deployed by GitHub Auto-Deploy in 39 seconds" width="799" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my run the new pages were live about a minute after &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The starter's test in &lt;code&gt;src/app/app.spec.ts&lt;/code&gt; still looks for the old heading, so &lt;code&gt;npx ng test&lt;/code&gt; now fails. The deploy does not run tests, but the &lt;a href="https://github.com/light-cloud-com/tutorial-angular-website" rel="noopener noreferrer"&gt;repository&lt;/a&gt; has the updated test if you want it green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Open a deep link
&lt;/h2&gt;

&lt;p&gt;Angular Router uses real-looking paths such as &lt;code&gt;/about&lt;/code&gt;. There is no &lt;code&gt;about&lt;/code&gt; file in the build, so on many static hosts opening that address directly returns a 404. On Light Cloud any path that is not a file is answered with &lt;code&gt;index.html&lt;/code&gt;, and Angular Router shows the right page.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;https://main-tutorial-angular-website-yourworkspace.light-cloud.io/about&lt;/code&gt; directly, or refresh the page while on About:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70sppq63n6u8x9sb174t.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70sppq63n6u8x9sb174t.webp" alt="The About page opened directly at /about, with the About link in bold" width="760" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Angular CLI requires a minimum Node.js version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Node.js version v22.16.0 detected.
The Angular CLI requires a minimum Node.js version of v22.22.3 or v24.15.0 or v26.0.0.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your Node.js is older than Angular 22 needs. Install a current Node.js 22 or 24 from &lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;nodejs.org&lt;/a&gt; and run the command again.&lt;/p&gt;

&lt;h3&gt;
  
  
  The site shows a blank page or "Not found"
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Output directory&lt;/strong&gt; does not point at the folder with &lt;code&gt;index.html&lt;/code&gt;. For the Angular CLI it is &lt;code&gt;dist/&amp;lt;project name&amp;gt;/browser&lt;/code&gt;. If you renamed the project in &lt;code&gt;angular.json&lt;/code&gt; or set your own &lt;code&gt;outputPath&lt;/code&gt;, open &lt;strong&gt;Production&lt;/strong&gt; , then the &lt;strong&gt;Settings&lt;/strong&gt; tab, click &lt;strong&gt;Edit&lt;/strong&gt; next to &lt;strong&gt;Build Settings&lt;/strong&gt; , correct the &lt;strong&gt;Output directory&lt;/strong&gt; and redeploy.&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>tutorial</category>
      <category>angular</category>
    </item>
    <item>
      <title>Deploy a React App in Minutes: Your First Light Cloud Website, for Free</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Wed, 30 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/lightcloud/deploy-a-react-app-in-minutes-your-first-light-cloud-website-for-free-4j4h</link>
      <guid>https://dev.to/lightcloud/deploy-a-react-app-in-minutes-your-first-light-cloud-website-for-free-4j4h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtsi6rnzmzzi46snbsvv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtsi6rnzmzzi46snbsvv.webp" alt="Deploy a React App in Minutes: Your First Light Cloud Website, for Free" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deploy a React app on Light Cloud, push your project to GitHub, choose &lt;strong&gt;Deploy from GitHub&lt;/strong&gt; in the console, pick the repository and click &lt;strong&gt;Deploy&lt;/strong&gt;. Light Cloud detects React from &lt;code&gt;package.json&lt;/code&gt;, runs &lt;code&gt;npm ci&lt;/code&gt; and &lt;code&gt;npm run build&lt;/code&gt;, and serves the &lt;code&gt;dist&lt;/code&gt; folder from the edge at an HTTPS address. It works on the free plan, and every later &lt;code&gt;git push&lt;/code&gt; redeploys the site by itself.&lt;/p&gt;

&lt;p&gt;In my run the first build took 15 seconds, and an update was live 50 seconds after &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;A React website made with the official Vite starter, live on its own &lt;code&gt;light-cloud.io&lt;/code&gt; address, then updated with one pushed change.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdr55qoxpj4b9a3dwb6ef.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdr55qoxpj4b9a3dwb6ef.webp" alt="The finished site, served by Light Cloud: the Vite and React starter page with the headline Hello from Light Cloud" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live demo: &lt;a href="https://main-tutorial-react-website-examples.light-cloud.io" rel="noopener noreferrer"&gt;main-tutorial-react-website-examples.light-cloud.io&lt;/a&gt;. Source code: &lt;a href="https://github.com/light-cloud-com/tutorial-react-website" rel="noopener noreferrer"&gt;github.com/light-cloud-com/tutorial-react-website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; 20.19 or later, or 22.12 or later (what Vite requires); I used Node.js 22.16.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; and a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;A free Light Cloud account at &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;console.light-cloud.com&lt;/a&gt;, with GitHub connected. The console asks you to connect it the first time you choose &lt;strong&gt;Deploy from GitHub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On Windows, run the commands in &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the React app
&lt;/h2&gt;

&lt;p&gt;Vite's starter gives you a working React app in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm create vite@latest tutorial-react-website -- --template react --no-interactive
◇ Scaffolding project in /Users/you/tutorial-react-website...
└ Done. Now run:

  cd tutorial-react-website
  npm install
  npm run dev


PS&amp;gt; npm create vite@latest tutorial-react-website -- --template react --no-interactive
◇ Scaffolding project in C:\Users\you\tutorial-react-website...
└ Done. Now run:

  cd tutorial-react-website
  npm install
  npm run dev

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--template react&lt;/code&gt; picks React with JavaScript (use &lt;code&gt;react-ts&lt;/code&gt; for TypeScript) and &lt;code&gt;--no-interactive&lt;/code&gt; skips the questions. Output is trimmed; the path shows your own folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Run it on your machine
&lt;/h2&gt;

&lt;p&gt;Install the dependencies and start the development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tutorial-react-website
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="go"&gt;found 0 vulnerabilities
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run dev
&lt;span class="go"&gt;
  VITE v8.3.1 ready in 487 ms

  ➜ Local: http://localhost:5173/


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tutorial-react-website
&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="go"&gt;found 0 vulnerabilities
&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run dev
&lt;span class="go"&gt;
  VITE v8.3.1 ready in 487 ms

  ➜ Local: http://localhost:5173/

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:5173&lt;/code&gt; and you should see the starter page. Your Vite version may be newer. Stop the server with Ctrl+C.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Push it to GitHub
&lt;/h2&gt;

&lt;p&gt;Light Cloud deploys from a Git repository. Commit the project, create an empty repository on &lt;a href="https://github.com/new" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and push to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"React website from the Vite starter"&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create an empty repository named tutorial-react-website on github.com/new first.
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git remote add origin https://github.com/YOUR-USERNAME/tutorial-react-website.git
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;span class="go"&gt;To https://github.com/YOUR-USERNAME/tutorial-react-website.git
&lt;/span&gt;&lt;span class="gp"&gt; * [new branch] main -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;main
&lt;span class="go"&gt;branch 'main' set up to track 'origin/main'.


&lt;/span&gt;&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git init &lt;span class="nt"&gt;-b&lt;/span&gt; main
&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"React website from the Vite starter"&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create an empty repository named tutorial-react-website on github.com/new first.
&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git remote add origin https://github.com/YOUR-USERNAME/tutorial-react-website.git
&lt;span class="gp"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;span class="go"&gt;To https://github.com/YOUR-USERNAME/tutorial-react-website.git
&lt;/span&gt;&lt;span class="gp"&gt; * [new branch] main -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;main
&lt;span class="go"&gt;branch 'main' set up to track 'origin/main'.

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave the new repository empty (no README), so the push goes through. The starter's &lt;code&gt;.gitignore&lt;/code&gt; already keeps &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;dist&lt;/code&gt; out of Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Deploy from GitHub
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://console.light-cloud.com" rel="noopener noreferrer"&gt;Light Cloud console&lt;/a&gt;, click &lt;strong&gt;New...&lt;/strong&gt; at the top of the sidebar, then &lt;strong&gt;Deploy from GitHub&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgzohgqrc022vga2ijtpf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgzohgqrc022vga2ijtpf.webp" alt="The New menu in the sidebar with Deploy from GitHub highlighted" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Type the repository name into &lt;strong&gt;Search your repositories...&lt;/strong&gt; and click it. A new repository can take a few seconds to appear.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0y6imxdo16j44hsmyjoy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0y6imxdo16j44hsmyjoy.webp" alt="The repository search showing tutorial-react-website highlighted" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Light Cloud reads the repository and shows what it found: &lt;strong&gt;React / Frontend&lt;/strong&gt; , based on &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt;, &lt;strong&gt;Served from the edge&lt;/strong&gt;. A React app compiles to static files, so no server runs for it; the files are delivered from a content delivery network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5rrvmiqdayhf37nlj12.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5rrvmiqdayhf37nlj12.webp" alt="The detection banner React Frontend and the Served from the edge label highlighted" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Optional: open &lt;strong&gt;Advanced - build settings, environment variables, domain, scaling&lt;/strong&gt; to see what Light Cloud will run. It has already filled in &lt;code&gt;npm ci&lt;/code&gt; as the &lt;strong&gt;Install command&lt;/strong&gt; , &lt;code&gt;npm run build&lt;/code&gt; as the &lt;strong&gt;Build command&lt;/strong&gt; and &lt;code&gt;dist&lt;/code&gt; as the &lt;strong&gt;Output directory&lt;/strong&gt;. You do not need to change anything.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqxsi36oub0dndvju9i6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqxsi36oub0dndvju9i6.webp" alt="The Advanced section with Install command npm ci, Output directory dist and Build command npm run build highlighted" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Deploy&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7c3uq8fh1cwltcmf8u6w.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7c3uq8fh1cwltcmf8u6w.webp" alt="The Environments page with Production deploying" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Open your site
&lt;/h2&gt;

&lt;p&gt;When &lt;strong&gt;Production&lt;/strong&gt; shows &lt;strong&gt;Deployed&lt;/strong&gt; (15 seconds in my run), open it. The &lt;strong&gt;URL&lt;/strong&gt; card on the &lt;strong&gt;Overview&lt;/strong&gt; tab links to your site:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhe4bt11l41s3jb2k1676.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhe4bt11l41s3jb2k1676.webp" alt="The Production overview with the Deployed badge and the URL card highlighted" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The address follows the pattern &lt;code&gt;https://main-&amp;lt;app name&amp;gt;-&amp;lt;workspace&amp;gt;.light-cloud.io&lt;/code&gt;, and HTTPS is already set up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxa3h0qg8mwgq513tdfk6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxa3h0qg8mwgq513tdfk6.webp" alt="The live React starter page served from Light Cloud" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Update the site with git push
&lt;/h2&gt;

&lt;p&gt;Change the headline in &lt;code&gt;src/App.jsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello from Light Cloud&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"Change the headline"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push


PS&amp;gt; git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"Change the headline"&lt;/span&gt;
PS&amp;gt; git push

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is all. &lt;strong&gt;Deploy on push&lt;/strong&gt; is on by default, so Light Cloud builds the new version when GitHub tells it about the push. The &lt;strong&gt;Deployments&lt;/strong&gt; tab lists it as deployed by &lt;strong&gt;GitHub Auto-Deploy&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwrk31zlw25shzitxe4cq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwrk31zlw25shzitxe4cq.webp" alt="The Deployments tab with the new commit Change the headline deployed by GitHub Auto-Deploy in 13 seconds" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my run the new headline was live 50 seconds after &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Check that client-side routes work
&lt;/h2&gt;

&lt;p&gt;If you add &lt;a href="https://reactrouter.com/" rel="noopener noreferrer"&gt;React Router&lt;/a&gt; later, pages like &lt;code&gt;/about&lt;/code&gt; only exist in the browser. Light Cloud answers any path that is not a real file with your &lt;code&gt;index.html&lt;/code&gt;, so those links work when opened directly or refreshed:&lt;/p&gt;

&lt;p&gt;To try it now, open &lt;code&gt;https://main-tutorial-react-website-yourworkspace.light-cloud.io/about/team&lt;/code&gt;, a page that does not exist yet. Instead of a 404 error, you get the home page: that is &lt;code&gt;index.html&lt;/code&gt;, where React Router would take over.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The repository does not appear in the search
&lt;/h3&gt;

&lt;p&gt;The Light Cloud GitHub App does not have access to it yet. Click &lt;strong&gt;Add or configure an account&lt;/strong&gt; under the search box and grant access to the repository (or to all repositories). A repository created a moment ago can also take a few seconds to show up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The build fails with "npm ci can only install packages when your package.json and package-lock.json are in sync"
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;npm install&lt;/code&gt; on your machine, commit the updated &lt;code&gt;package-lock.json&lt;/code&gt; and push again.&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>tutorial</category>
      <category>react</category>
    </item>
    <item>
      <title>What "Open" Means After HashiCorp</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Wed, 30 Sep 2026 08:22:35 +0000</pubDate>
      <link>https://dev.to/lightcloud/what-open-means-after-hashicorp-4c2p</link>
      <guid>https://dev.to/lightcloud/what-open-means-after-hashicorp-4c2p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdcaauzi5cwghaghvgtvf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdcaauzi5cwghaghvgtvf.jpg" alt="What " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In August 2023, HashiCorp &lt;a href="https://en.wikipedia.org/wiki/OpenTofu" rel="noopener noreferrer"&gt;swapped Terraform's license&lt;/a&gt; from the Mozilla Public License to the Business Source License, and thousands of engineering teams discovered simultaneously that a load-bearing piece of their infrastructure had changed its legal nature overnight, without a version bump, without a migration guide, without asking. The code was identical the next morning. The deal was not.&lt;/p&gt;

&lt;p&gt;What followed is now a complete story with a beginning, middle, and instructive end: the community forked, the fork survived, and the company got acquired anyway, by &lt;a href="https://www.theregister.com/2025/02/28/ibm_hashicorp_deal_closing/" rel="noopener noreferrer"&gt;IBM, for $6.4 billion, closing February 27, 2025&lt;/a&gt;. This post is about what that story settled. My claim: after HashiCorp, license risk belongs in the same planning category as provider outages and &lt;a href="https://blog.light-cloud.com/industry/the-vmware-lesson" rel="noopener noreferrer"&gt;vendor acquisitions&lt;/a&gt;, because "open source" turned out to describe a current state rather than a commitment, and the difference between those two things is your exposure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The timeline, compressed
&lt;/h2&gt;

&lt;p&gt;The BSL's mechanics matter more than its reputation. It's not a rug-pull to proprietary: source stays visible, most end users remain unaffected, and each release converts back to open source after four years. What it restricts is use "in a competitive way" with HashiCorp's own offerings, which is to say it was a targeted strike at the vendors building commercial products on Terraform. Precise, defensible, and quietly radical, because it demonstrated that a license everyone had treated as ground truth was actually a policy, revisable by whoever holds the copyright.&lt;/p&gt;

&lt;p&gt;The community's answer arrived within weeks: a coalition &lt;a href="https://encore.dev/articles/opentofu-vs-terraform-2026" rel="noopener noreferrer"&gt;forked Terraform 1.5&lt;/a&gt;, the last MPL release, and OpenTofu went to the Linux Foundation with commercial backers whose businesses depended on the fork existing. Then the ending nobody's talking point predicted: IBM bought the whole company. The BSL didn't block the acquisition or get reversed by it; instead the license question acquired a second layer, because now the copyright that can rewrite the deal sits inside a portfolio strategy nobody outside Armonk controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the fork actually proved
&lt;/h2&gt;

&lt;p&gt;Forks were always open source's constitutional theory: the community's right of exit, the reason single-vendor control was supposedly tolerable. Theory, mostly, because executed forks that thrive are rare; forking is expensive, thankless, and usually starves. OpenTofu is the counterexample that matters precisely because it worked: foundation governance, real releases, real adoption, vendors funding it out of existential need rather than idealism.&lt;/p&gt;

&lt;p&gt;But look at the conditions that made it work, because they're the actual checklist. A permissive license existed to fork from. The affected parties were companies with revenue, not volunteers with weekends. The project's surface area was forkable by a small expert group. And a neutral foundation stood ready to hold the keys. Remove any one of those and the constitutional theory reverts to theory, which is exactly the audit you should run on every load-bearing dependency you have: not "is it open today" but "if the deal changed tomorrow, does a credible fork coalition exist, and would we be in it or waiting on it?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Depending on VC-backed open source without getting burned
&lt;/h2&gt;

&lt;p&gt;The structural fact underneath all of this is unglamorous: a venture-backed company whose product is open source has promised its investors an outcome, and the commons it built is the asset it will eventually monetize harder. That's not cynicism; it's the deal working as designed, and it has a small set of endings, relicense, acquisition, or both. HashiCorp did both. Pretending otherwise is how teams end up surprised by events that were structurally predictable the day the Series C closed.&lt;/p&gt;

&lt;p&gt;So read the boring documents before you build on the shiny repo. Who holds the trademark and the copyright, one company or a foundation? Does a contributor license agreement concentrate relicensing power? What does the license convert to, and when? Is governance a committee or a courtesy? None of this appears in the quickstart, all of it determines what happens to you in year four, and the &lt;a href="https://blog.light-cloud.com/devops/graph-not-text-file" rel="noopener noreferrer"&gt;graph-versus-text argument&lt;/a&gt; we've made about infrastructure applies to its legal layer too: the dependency structure you can't see is the one that gets you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The steelman: HashiCorp had a point
&lt;/h2&gt;

&lt;p&gt;The uncomfortable case for the relicense deserves its space. Building commercial products on someone else's open source while contributing little back is legal free-riding, hyperscalers industrialized it, and a company watching others monetize its R&amp;amp;D isn't paranoid to respond. The BSL genuinely spared end users. MongoDB and Elastic walked similar roads and their businesses survived, their ecosystems adapted, and some of their forks even made the original vendors sharper. If open source companies can't capture value, the argument goes, there will eventually be less open source, and everyone loses slower but loses.&lt;/p&gt;

&lt;p&gt;Most of that stands. What doesn't survive is the conclusion that nothing changed for the people downstream. The old equilibrium let you treat a license as terrain, fixed, walkable, boring; the new one requires treating it as weather. Both are livable. Only one of them is what a decade of "just use the open source tool" advice assumed, and infrastructure teams got moved from the first world to the second without a memo. The memo is what this post is for.&lt;/p&gt;

&lt;p&gt;The operational conclusion fits in a sentence: depend on open source exactly as much as you like, and know your exit the way &lt;a href="https://blog.light-cloud.com/sovereignty/your-regulator-wants-an-exit-plan" rel="noopener noreferrer"&gt;regulators now make banks know theirs&lt;/a&gt;. A fork you could join, a compatible alternative you've tested, an abstraction that makes the tool swappable, any of these converts a relicense from crisis to changelog. Which of your load-bearing dependencies could rewrite its deal tomorrow, and do you know your answer, or just your hope?&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Microservices Part 6: Trace One Request Through the Logs</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Tue, 29 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/lightcloud/microservices-part-6-trace-one-request-through-the-logs-4hk6</link>
      <guid>https://dev.to/lightcloud/microservices-part-6-trace-one-request-through-the-logs-4hk6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6g0c8cg3bu6lj903up4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6g0c8cg3bu6lj903up4.webp" alt="Microservices Part 6: Trace One Request Through the Logs" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To follow one request across microservices on Light Cloud, give it an ID, pass that ID to every service in an &lt;code&gt;x-request-id&lt;/code&gt; header, and print it in every log line as JSON with a &lt;code&gt;severity&lt;/code&gt; field. Then search each service's &lt;strong&gt;Logs&lt;/strong&gt; tab for the ID: every line the request produced appears, in every service, and the level filter narrows the view to warnings and errors.&lt;/p&gt;

&lt;p&gt;This is Part 6, the last part of the series. It uses Bean There from &lt;a href="https://blog.light-cloud.com/tutorials/microservices-on-light-cloud-part-1-deploy" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;, where the request ID was already built in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Log lines that the Logs tab understands: JSON with &lt;code&gt;severity&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;requestId&lt;/code&gt; and a &lt;code&gt;message&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A traced order: one ID, found in orders-api and in catalog-api.&lt;/li&gt;
&lt;li&gt;A traced failure: an order for a product that does not exist, found with the &lt;strong&gt;warnings and above&lt;/strong&gt; filter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/light-cloud-com/tutorial-microservices" rel="noopener noreferrer"&gt;github.com/light-cloud-com/tutorial-microservices&lt;/a&gt;, tag &lt;code&gt;part-6&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bean There from Parts 1 to 5.&lt;/li&gt;
&lt;li&gt;A terminal with &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;curl&lt;/a&gt; (on Windows, &lt;a href="https://curl.se/windows/" rel="noopener noreferrer"&gt;&lt;code&gt;curl.exe&lt;/code&gt;&lt;/a&gt; in &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt;) and &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Get the Part 6 code
&lt;/h2&gt;

&lt;p&gt;Sync your fork or pull, as in the earlier parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git pull upstream main
&lt;span class="nv"&gt;$ &lt;/span&gt;git push origin main


PS&amp;gt; git pull upstream main
PS&amp;gt; git push origin main

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both APIs changed, so both redeploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: How the request ID travels
&lt;/h2&gt;

&lt;p&gt;Each API gives every request an ID, or keeps the one the caller sent, and returns it in the response. orders-api passes it on when it calls catalog-api:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-request-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-request-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;


&lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;CATALOG_API_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/internal/products/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/reserve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-internal-secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INTERNAL_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-request-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;catalog-api does the same on its side, keeping the ID it receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-request-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-request-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Write logs the Logs tab understands
&lt;/h2&gt;

&lt;p&gt;Part 6 adds a &lt;code&gt;severity&lt;/code&gt; field to every log line. The &lt;strong&gt;Logs&lt;/strong&gt; tab reads it: lines get the right level and colour, and the level filter works. Without it, a line shows as &lt;strong&gt;DEFAULT&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// One JSON object per line. "severity" is what the Logs tab's level filter&lt;/span&gt;
&lt;span class="c1"&gt;// reads (INFO, WARNING, ERROR); requestId ties lines from all services together.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INFO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;catalog-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;extra&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INFO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;severity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;service&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orders-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;requestId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="nx"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Part 6 also fixes a bug from earlier parts: an order for a product that does not exist used to answer "Not enough stock". It now returns &lt;code&gt;404 Product not found&lt;/code&gt; and logs a warning in both services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Send a traced order
&lt;/h2&gt;

&lt;p&gt;Place an order with an ID you choose, so it is easy to find. &lt;code&gt;-i&lt;/code&gt; shows the response headers, including the ID coming back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://main-orders-api-yourworkspace.light-cloud.io/orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-request-id: order-7f3a"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"product_id":2,"quantity":1}'&lt;/span&gt;
HTTP/2 201
x-request-id: order-7f3a

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:9,&lt;span class="s2"&gt;"product"&lt;/span&gt;:&lt;span class="s2"&gt;"Pour-over kettle"&lt;/span&gt;,&lt;span class="s2"&gt;"quantity"&lt;/span&gt;:1,&lt;span class="s2"&gt;"total_cents"&lt;/span&gt;:5900&lt;span class="o"&gt;}&lt;/span&gt;


PS&amp;gt; curl.exe &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://main-orders-api-yourworkspace.light-cloud.io/orders &lt;span class="sb"&gt;`&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-request-id: order-7f3a"&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"product_id":2,"quantity":1}'&lt;/span&gt;
HTTP/2 201
x-request-id: order-7f3a

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:9,&lt;span class="s2"&gt;"product"&lt;/span&gt;:&lt;span class="s2"&gt;"Pour-over kettle"&lt;/span&gt;,&lt;span class="s2"&gt;"quantity"&lt;/span&gt;:1,&lt;span class="s2"&gt;"total_cents"&lt;/span&gt;:5900&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other headers are left out here. Your order &lt;code&gt;id&lt;/code&gt; will differ. In a real app the browser would not choose the ID; the first service creates one, and you find it in the response or in the first log line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Find it in both services
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;orders-api&lt;/strong&gt; , &lt;strong&gt;Production&lt;/strong&gt; , then the &lt;strong&gt;Logs&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Type the ID, &lt;code&gt;order-7f3a&lt;/code&gt;, into &lt;strong&gt;search lines&lt;/strong&gt; and press Enter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsjapco6b1h9tnfcrklol.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsjapco6b1h9tnfcrklol.webp" alt="orders-api's Logs tab searched for order-7f3a, showing placing order and order placed at INFO level" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two lines: &lt;code&gt;placing order&lt;/code&gt; and &lt;code&gt;order placed&lt;/code&gt;. Click a line to open it. Under &lt;strong&gt;JSON Payload&lt;/strong&gt; you see every field the service logged, including the &lt;code&gt;requestId&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frbkchmppzzag6aeai12j.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frbkchmppzzag6aeai12j.webp" alt="An expanded log line with its metadata and the JSON payload showing requestId order-7f3a" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Now open &lt;strong&gt;catalog-api&lt;/strong&gt; , &lt;strong&gt;Production&lt;/strong&gt; , &lt;strong&gt;Logs&lt;/strong&gt; , and search for the same ID.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3w5e7ws6lywxpxo9u0k.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3w5e7ws6lywxpxo9u0k.webp" alt="catalog-api's Logs tab searched for order-7f3a, showing the reserved stock line" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reserved stock&lt;/code&gt;: the same request, one service further along. Together the two searches show the order's whole path, in order, with timestamps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Find failures with the level filter
&lt;/h2&gt;

&lt;p&gt;Send an order that fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -s -i -X POST https://main-orders-api-yourworkspace.light-cloud.io/orders \
  -H "content-type: application/json" -H "x-request-id: order-404b" \
  -d '{"product_id":999,"quantity":1}'
HTTP/2 404
x-request-id: order-404b

{"detail":"Product not found"}


PS&amp;gt; curl.exe -s -i -X POST https://main-orders-api-yourworkspace.light-cloud.io/orders `
  -H "content-type: application/json" -H "x-request-id: order-404b" `
  -d '{"product_id":999,"quantity":1}'
HTTP/2 404
x-request-id: order-404b

{"detail":"Product not found"}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now find it without knowing the ID. In orders-api's &lt;strong&gt;Logs&lt;/strong&gt; tab, clear the search, open the &lt;strong&gt;level&lt;/strong&gt; filter and choose &lt;strong&gt;warnings and above&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faal6g83wwc007klh6ie5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faal6g83wwc007klh6ie5.webp" alt="The level filter open with presets all, info and above, warnings and above and errors only, and the individual levels below" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only warnings and errors remain. Open &lt;code&gt;order for unknown product&lt;/code&gt; to see which product and which request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0jyslwgxlhe0qv4nhz6p.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0jyslwgxlhe0qv4nhz6p.webp" alt="The Logs tab filtered to warnings, with the order for unknown product line expanded and its JSON payload showing product_id 999 and requestId order-404b" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two kinds of lines show up. Your own &lt;code&gt;order for unknown product&lt;/code&gt; warning, and a line Light Cloud writes for every HTTP request, here &lt;code&gt;POST https://main-orders-api-yourworkspace.light-cloud.io/orders 404&lt;/code&gt;. Requests that end with a 4xx status are marked &lt;strong&gt;WARNING&lt;/strong&gt; automatically, so this filter finds failed requests even in a service that logs nothing itself. With the &lt;code&gt;requestId&lt;/code&gt; from the payload you can now search catalog-api for the other half of the story.&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>tutorial</category>
      <category>node</category>
    </item>
    <item>
      <title>Microservices Part 5: Preview a Branch and Roll Back</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Mon, 28 Sep 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/lightcloud/microservices-part-5-preview-a-branch-and-roll-back-41nh</link>
      <guid>https://dev.to/lightcloud/microservices-part-5-preview-a-branch-and-roll-back-41nh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy7w14z9z8h9wi25mmdhi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy7w14z9z8h9wi25mmdhi.webp" alt="Microservices Part 5: Preview a Branch and Roll Back" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To ship a change to one microservice without breaking the others on Light Cloud, push it to a Git branch: Light Cloud deploys the branch to its own URLs, posts the preview links on your pull request, and after the merge redeploys only the services the change touched. If a bad deploy still reaches production, &lt;strong&gt;Rollback&lt;/strong&gt; on the &lt;strong&gt;Deployments&lt;/strong&gt; tab serves an earlier build again; in my test the service was fixed in 13 seconds.&lt;/p&gt;

&lt;p&gt;This is Part 5 of the series, on the Bean There shop from &lt;a href="https://blog.light-cloud.com/tutorials/microservices-on-light-cloud-part-1-deploy" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;. &lt;strong&gt;Deploy every branch&lt;/strong&gt; and &lt;strong&gt;Remove stale environments&lt;/strong&gt; were switched on when the apps were created, which is the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A feature, "stock status" ("in stock", "low", "sold out"), that changes catalog-api and the web app.&lt;/li&gt;
&lt;li&gt;A full preview of the feature on branch URLs, wired so the preview shop talks to the preview APIs.&lt;/li&gt;
&lt;li&gt;A merged pull request, and then a deliberate bug in production that you roll back and revert.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/light-cloud-com/tutorial-microservices" rel="noopener noreferrer"&gt;github.com/light-cloud-com/tutorial-microservices&lt;/a&gt;, tag &lt;code&gt;part-5&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bean There from Parts 1 to 4, deployed from your fork.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; and a terminal with &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;curl&lt;/a&gt; (on Windows, &lt;a href="https://curl.se/windows/" rel="noopener noreferrer"&gt;&lt;code&gt;curl.exe&lt;/code&gt;&lt;/a&gt; in &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Push a feature branch
&lt;/h2&gt;

&lt;p&gt;Create a branch and change catalog-api so &lt;code&gt;/products&lt;/code&gt; also returns a &lt;code&gt;stock_status&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature/stock-status
Switched to a new branch &lt;span class="s1"&gt;'feature/stock-status'&lt;/span&gt;


PS&amp;gt; git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature/stock-status
Switched to a new branch &lt;span class="s1"&gt;'feature/stock-status'&lt;/span&gt;


app.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/products"&lt;/span&gt;, async &lt;span class="o"&gt;(&lt;/span&gt;req, res&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  const &lt;span class="o"&gt;{&lt;/span&gt; rows &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; await db.query&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="sb"&gt;`&lt;/span&gt;SELECT &lt;span class="k"&gt;*&lt;/span&gt;, CASE WHEN stock &lt;span class="o"&gt;=&lt;/span&gt; 0 THEN &lt;span class="s1"&gt;'sold out'&lt;/span&gt; WHEN stock &amp;lt; 10 THEN &lt;span class="s1"&gt;'low'&lt;/span&gt; ELSE &lt;span class="s1"&gt;'in stock'&lt;/span&gt; END AS stock_status
     FROM products ORDER BY &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push the branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"catalog-api: add stock_status to /products"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin feature/stock-status


PS&amp;gt; git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"catalog-api: add stock_status to /products"&lt;/span&gt;
PS&amp;gt; git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin feature/stock-status

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About a minute later each app has a second environment next to &lt;strong&gt;Production&lt;/strong&gt; , named after the branch and deployed from it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gu0nrr37386in35xxs3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gu0nrr37386in35xxs3.webp" alt="catalog-api's Environments list with Production and a new Stock Status environment on feature/stock-status" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that &lt;strong&gt;all three apps&lt;/strong&gt; got a branch environment, even though only &lt;code&gt;catalog-api/&lt;/code&gt; changed. A new branch creates an environment for every app on the repository; after that, pushes only redeploy the apps whose folder changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Test the changed service on its own URL
&lt;/h2&gt;

&lt;p&gt;A branch environment's address is the branch name (with &lt;code&gt;/&lt;/code&gt; turned into &lt;code&gt;-&lt;/code&gt;), the app name and your workspace. Open the branch and production side by side in your browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://feature-stock-status-catalog-api-yourworkspace.light-cloud.io/products&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://main-catalog-api-yourworkspace.light-cloud.io/products&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first product on the branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Espresso beans, 1 kg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price_cents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stock_status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in stock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same product in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"Espresso beans, 1 kg"&lt;/span&gt;,&lt;span class="s2"&gt;"price_cents"&lt;/span&gt;:2400,&lt;span class="s2"&gt;"stock"&lt;/span&gt;:39&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branch has the new field; production does not. Your stock numbers will differ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Know what a preview is connected to
&lt;/h2&gt;

&lt;p&gt;A branch environment starts with the app's &lt;strong&gt;default&lt;/strong&gt; variables, the ones you entered when you created the app. For Bean There that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The branch catalog-api uses the &lt;strong&gt;production database&lt;/strong&gt;. Stock it reserves is real stock.&lt;/li&gt;
&lt;li&gt;The branch orders-api calls the &lt;strong&gt;production&lt;/strong&gt; catalog-api.&lt;/li&gt;
&lt;li&gt;The branch web app calls the &lt;strong&gt;production&lt;/strong&gt; APIs, so it does not show your change at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a change to one API that you test with curl, that is fine. To click through the whole feature, point the preview services at each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Wire a full preview
&lt;/h2&gt;

&lt;p&gt;First, show the status in the shop. Still on the branch, the web app displays &lt;code&gt;stock_status&lt;/code&gt; when the API sends it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;span &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;stock &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.stock_status === &lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt; ? &lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt; &lt;/span&gt;:&lt;span class="p"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;}&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;product.stock_status ? &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.stock_status&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.stock&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; : &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.stock&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;stock&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&amp;lt;/span&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push; only the branch web app redeploys. Then set these variables in each &lt;strong&gt;Stock Status&lt;/strong&gt; environment (open the app, &lt;strong&gt;Stock Status&lt;/strong&gt; , &lt;strong&gt;Settings&lt;/strong&gt; , &lt;strong&gt;Environment Variables&lt;/strong&gt; , &lt;strong&gt;Edit&lt;/strong&gt; , then &lt;strong&gt;Save&lt;/strong&gt; ):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;App&lt;/th&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;web&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VITE_CATALOG_API_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://feature-stock-status-catalog-api-yourworkspace.light-cloud.io&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;web&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VITE_ORDERS_API_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://feature-stock-status-orders-api-yourworkspace.light-cloud.io&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;catalog-api&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WEB_ORIGIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://feature-stock-status-web-yourworkspace.light-cloud.io&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;orders-api&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WEB_ORIGIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://feature-stock-status-web-yourworkspace.light-cloud.io&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;orders-api&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CATALOG_API_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://feature-stock-status-catalog-api-yourworkspace.light-cloud.io&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fai26qhg7gtv03z89i43q.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fai26qhg7gtv03z89i43q.webp" alt="The web app's Stock Status environment with VITE_ORDERS_API_URL and VITE_CATALOG_API_URL pointing at the branch APIs" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These variables belong to the branch environment only; production is not touched. About a minute after the last save, open the preview shop at &lt;code&gt;https://feature-stock-status-web-yourworkspace.light-cloud.io&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F65t69zbwx7eqo4w01k4i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F65t69zbwx7eqo4w01k4i.webp" alt="The preview shop listing each product with its stock status, for example in stock (39)" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The preview shows the new status; the production shop still shows "39 in stock". Browse, but do not place orders here: the preview still shares the production database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Open a pull request
&lt;/h2&gt;

&lt;p&gt;Open your repository on GitHub. It shows a banner for the branch you just pushed: click &lt;strong&gt;Compare &amp;amp; pull request&lt;/strong&gt; , check that it goes from &lt;code&gt;feature/stock-status&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt;, give it a title such as &lt;code&gt;Show stock status in the shop&lt;/code&gt;, and click &lt;strong&gt;Create pull request&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Light Cloud comments on the pull request with a preview link for each app, and adds a check per app that turns green when its deployment is ready:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futy4au587tpml4ckjuoq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futy4au587tpml4ckjuoq.webp" alt="The pull request on GitHub with light-cloud-ci comments linking the catalog-api and web previews, each marked Ready" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reviewers can open the links without access to the Light Cloud console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Merge
&lt;/h2&gt;

&lt;p&gt;Merge the branch into &lt;code&gt;main&lt;/code&gt; and delete it, with Git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;pull&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;merge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;feature/stock-status&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;push&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;feature/stock-status&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="err"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;pull&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;merge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;feature/stock-status&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;push&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;PS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;feature/stock-status&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub marks the pull request as merged as soon as its commits reach &lt;code&gt;main&lt;/code&gt;. (The &lt;strong&gt;Merge pull request&lt;/strong&gt; and &lt;strong&gt;Delete branch&lt;/strong&gt; buttons on GitHub do the same.)&lt;/p&gt;

&lt;p&gt;Two things happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Production redeploys only what changed.&lt;/strong&gt; The merge touched &lt;code&gt;catalog-api/&lt;/code&gt; and &lt;code&gt;web/&lt;/code&gt;, so those two redeploy; orders-api keeps running its current version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The previews disappear.&lt;/strong&gt; Deleting the branch removes the three &lt;strong&gt;Stock Status&lt;/strong&gt; environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ev05q7v0kda5bj1rj4a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ev05q7v0kda5bj1rj4a.webp" alt="catalog-api's Environments list with only Production left, deploying the merge commit" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Roll back a bad deploy
&lt;/h2&gt;

&lt;p&gt;Previews catch a lot, but not everything. To practise, push a change straight to &lt;code&gt;main&lt;/code&gt; that builds fine and is wrong: it divides every price by 100.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Prices&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;whole&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;currency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;units&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;price&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;badge.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;res.json(rows.map((row)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;price_cents:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;row.price_cents&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;)));&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minute after the push, the shop sells espresso beans for $0.24:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwfk7904ht7rrdtw14om9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwfk7904ht7rrdtw14om9.webp" alt="The live shop showing prices of 0.24 and 0.59 dollars after the bad deploy" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Roll back:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;catalog-api&lt;/strong&gt; , &lt;strong&gt;Production&lt;/strong&gt; , then the &lt;strong&gt;Deployments&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Rollback&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fb43yvjxdazbace6s11.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fb43yvjxdazbace6s11.webp" alt="The Deployments tab with the Rollback button highlighted next to Redeploy" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick the last good deployment. The one marked &lt;strong&gt;CURRENT&lt;/strong&gt; is the bad one; choose the one below it, here the merge commit &lt;code&gt;b49c2b4&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ueowzcwp0gyj6bfgtjr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ueowzcwp0gyj6bfgtjr.webp" alt="The Roll back to a previous deployment list with the merge commit b49c2b4 highlighted below the current deployment" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the confirmation and click &lt;strong&gt;Roll back&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50cspt65jymo4q258a6q.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50cspt65jymo4q258a6q.webp" alt="The Roll back deployment dialog explaining that no rebuild happens and that variables and database changes stay as they are" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dialog says exactly what a rollback does: the environment serves the code from the earlier deployment, with &lt;strong&gt;no rebuild&lt;/strong&gt;. Current environment variables stay as they are, and database changes made since are not undone. Because nothing is built, it is fast: in my test catalog-api served the correct prices again &lt;strong&gt;13 seconds&lt;/strong&gt; after I clicked &lt;strong&gt;Roll back&lt;/strong&gt; , against about 70 seconds for a normal deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Fix main
&lt;/h2&gt;

&lt;p&gt;The bad commit is still on &lt;code&gt;main&lt;/code&gt;, and the next push would deploy it again. Revert it and push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt; &lt;span class="nx"&gt;revert&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;edit&lt;/span&gt; &lt;span class="nx"&gt;HEAD&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="nx"&gt;f7829&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;Revert&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;catalog-api: prices in whole units&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
 &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nf"&gt;insertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nf"&gt;deletions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt;


&lt;span class="nx"&gt;PS&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt; &lt;span class="nx"&gt;revert&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;edit&lt;/span&gt; &lt;span class="nx"&gt;HEAD&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="nx"&gt;f7829&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;Revert&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;catalog-api: prices in whole units&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
 &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nf"&gt;insertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nf"&gt;deletions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;PS&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your commit id will differ. The push deploys the reverted code, and &lt;code&gt;main&lt;/code&gt; and production agree again.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The preview shop shows production data, not my change
&lt;/h3&gt;

&lt;p&gt;The branch web app still uses the app's default &lt;code&gt;VITE_*&lt;/code&gt; variables, which point at production. Set them on the branch environment (Step 4); saving rebuilds the preview with the new values.&lt;/p&gt;

&lt;h3&gt;
  
  
  The preview shop shows a CORS error
&lt;/h3&gt;

&lt;p&gt;The branch APIs only allow the origins in their own &lt;code&gt;WEB_ORIGIN&lt;/code&gt;. Add the preview web address to &lt;code&gt;WEB_ORIGIN&lt;/code&gt; on the branch catalog-api and orders-api.&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>tutorial</category>
      <category>git</category>
    </item>
    <item>
      <title>The VMware Lesson</title>
      <dc:creator>Julia</dc:creator>
      <pubDate>Mon, 28 Sep 2026 07:44:26 +0000</pubDate>
      <link>https://dev.to/lightcloud/the-vmware-lesson-37eh</link>
      <guid>https://dev.to/lightcloud/the-vmware-lesson-37eh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fywbpm179vha2o0b9wxw7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fywbpm179vha2o0b9wxw7.jpg" alt="The VMware Lesson" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a New York court filing, AT&amp;amp;T, a company that negotiates for a living, stated that its VMware renewal under Broadcom's new licensing model amounted to &lt;a href="https://regmedia.co.uk/2024/09/05/pacer_att_broadcom_complaint.pdf" rel="noopener noreferrer"&gt;a price increase of roughly 1,050%&lt;/a&gt;. Not fifty percent. Not double. Ten and a half times, for software already running, holding up systems that included services used by first responders, which is the detail that got the lawsuit its headlines.&lt;/p&gt;

&lt;p&gt;The AT&amp;amp;T number is the famous one, and it isn't the ceiling: European cloud providers organized under CISPE reported member increases &lt;a href="https://www.networkworld.com/article/3994107/vmware-customers-in-europe-face-up-to-1500-price-increases-under-broadcom-ownership.html" rel="noopener noreferrer"&gt;between 800% and 1,500%&lt;/a&gt;. This post is about why those numbers are the size they are, because the answer isn't greed as a personality trait. The answer is arithmetic performed on an install base that couldn't leave, and that arithmetic is the single most instructive case study available for anyone who signs infrastructure contracts. The lesson isn't "avoid Broadcom". It's that every locked-in vendor relationship you hold is an asset on someone's future balance sheet, priced at exactly your inability to exit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The timeline of a repricing
&lt;/h2&gt;

&lt;p&gt;The speed is the tell. Broadcom closed its roughly $69 billion VMware acquisition on November 22, 2023, and &lt;a href="https://redmondmag.com/articles/2023/12/13/broadcom-kills-perpetual-vmware-licenses.aspx" rel="noopener noreferrer"&gt;nineteen days later announced the end of perpetual licenses&lt;/a&gt;, the licensing model on which two decades of enterprise datacenters had been built. Products were fused into a handful of subscription bundles, so customers wanting one component now bought suites. The partner program was culled. Renewals began arriving at multiples of prior spend, and when customers balked, the follow-through included cease-and-desist letters to lapsed subscribers and litigation involving names as large as Siemens' US operations, while &lt;a href="https://www.sdxcentral.com/news/broadcom-vmware-faces-eu-lawsuit-and-security-update-complaints/" rel="noopener noreferrer"&gt;AT&amp;amp;T's suit&lt;/a&gt; alleged bad-faith pressure to convert to subscriptions it didn't want.&lt;/p&gt;

&lt;p&gt;No exploratory phase, no gradual drift: a playbook executed in weeks, which tells you it was written before the ink dried. And by the only measure Broadcom answers to, &lt;a href="https://www.networkworld.com/article/4053783/broadcoms-vmware-strategy-pays-off-financially-but-customers-not-as-keen-as-wall-street.html" rel="noopener noreferrer"&gt;it worked; the strategy has paid off financially&lt;/a&gt;, whatever the customer satisfaction surveys say. That's the part to absorb without flinching. This wasn't a miscalculation being punished by the market. It was a calculation being rewarded by it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the numbers are that size
&lt;/h2&gt;

&lt;p&gt;Here's the mechanism, and it's the same one this blog keeps circling from different directions. A hypervisor is close to the deepest possible position in the &lt;a href="https://blog.light-cloud.com/multi-cloud/egress-fees-are-dead-lock-in-isnt" rel="noopener noreferrer"&gt;lock-in taxonomy&lt;/a&gt;: it sits under everything, touching every VM, every runbook, every disaster-recovery plan and every ops engineer's muscle memory, so leaving it isn't a migration, it's a re-platforming of the entire estate. Broadcom's pricing team didn't pick 1,000% out of spite. They estimated the cost of that re-platforming, in money, time, and risk, and priced renewals just under it, because any price below your cost of leaving is, in cold economic terms, a price you'll pay.&lt;/p&gt;

&lt;p&gt;That's the general formula hiding inside the case study: &lt;strong&gt;your renewal price converges on your exit cost.&lt;/strong&gt; Not on the product's value, not on what you paid last year, not on the relationship. On what leaving would cost you, as estimated by a party with every incentive to estimate it well. Most vendors don't run the formula to its limit because reputation, competition, and growth still discipline them. An acquirer buying a mature install base has exactly none of those constraints; the reputational damage lands on a brand it didn't build, the growth story is the extraction itself, and the customers, by construction, can't leave quickly. The contract terms AT&amp;amp;T thought protected it turned into two years of litigation instead of protection, which is what contracts are worth against a counterparty that has priced your litigation budget too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson, generalized
&lt;/h2&gt;

&lt;p&gt;So run the VMware scenario against your own stack, because the scenario is portable. Every "strategic vendor" you depend on, virtualization, observability, identity, database, CI, is one acquisition announcement away from new ownership with a spreadsheet, and the &lt;a href="https://blog.light-cloud.com/industry/neoclouds-and-the-great-unbundling" rel="noopener noreferrer"&gt;consolidation era&lt;/a&gt; guarantees a steady supply of announcements. The questions that matter are the ones this case answers: What would a rational new owner charge us, given our real cost of leaving? How long would leaving actually take, tested, not &lt;a href="https://blog.light-cloud.com/sovereignty/your-regulator-wants-an-exit-plan" rel="noopener noreferrer"&gt;asserted&lt;/a&gt;? Which dependencies sit so deep that their exit cost is effectively infinite, and did we price that when we adopted them?&lt;/p&gt;

&lt;p&gt;Notice the VMware exodus that did happen: the customers who moved fastest weren't the angriest, they were the ones whose environments were already portable, boring, and mapped. Everyone else discovered that fury is not a migration plan. The time to compress your exit cost is while your vendor still has competitors and you still have alternatives, because the day the acquisition closes, your exit cost stops being an internal metric and becomes someone else's revenue target.&lt;/p&gt;

&lt;h2&gt;
  
  
  The steelman: Broadcom's defense has real parts
&lt;/h2&gt;

&lt;p&gt;The uncomfortable defense deserves an honest hearing. VMware's perpetual-license pricing arguably underpriced two decades of critical software; subscriptions are the industry norm everywhere else; the product portfolio genuinely needed rationalization, and some percentage of the outrage is customers discovering the market value of something they'd been getting cheap. Markets also responded exactly as textbooks predict: virtualization alternatives, from Proxmox to the cloud providers themselves, have feasted on the discontent, and in five years the ecosystem may be healthier for the shock. Broadcom, on this reading, just marked an asset to market.&lt;/p&gt;

&lt;p&gt;Mostly conceded, and it sharpens the lesson rather than blunting it. If the repricing was rational and legal, and it was, then it's repeatable, and "our vendor would never" is not a control. The only variable you govern in that equation is the exit cost, which means portability isn't a nice-to-have on some architectural wishlist; it's the one lever that caps what any future owner of any vendor can charge you. We build for that lever, and this case is why we get to skip the abstract argument for it. The concrete version fits in one question, and AT&amp;amp;T's lawyers can tell you the price of answering it late: if your deepest vendor were acquired this quarter, what number would appear on your next renewal, and what number would it cost you to say no?&lt;/p&gt;

</description>
      <category>lightcloud</category>
      <category>cloudplatform</category>
      <category>cloud</category>
      <category>multicloud</category>
    </item>
  </channel>
</rss>
