<?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: Adnan Ali</title>
    <description>The latest articles on DEV Community by Adnan Ali (@adnan_ali_af704aeaf65d4c3).</description>
    <link>https://dev.to/adnan_ali_af704aeaf65d4c3</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%2F2114021%2F1745eff1-ea89-4fa1-82be-d87acb41651c.jpg</url>
      <title>DEV Community: Adnan Ali</title>
      <link>https://dev.to/adnan_ali_af704aeaf65d4c3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adnan_ali_af704aeaf65d4c3"/>
    <language>en</language>
    <item>
      <title>From Localhost to Production: Deploying a Full-Stack App with Cloudflare, Railway, Redis &amp; Docker.</title>
      <dc:creator>Adnan Ali</dc:creator>
      <pubDate>Sat, 26 Sep 2026 12:42:18 +0000</pubDate>
      <link>https://dev.to/adnan_ali_af704aeaf65d4c3/from-localhost-to-production-deploying-a-full-stack-app-with-cloudflare-railway-redis-docker-47fj</link>
      <guid>https://dev.to/adnan_ali_af704aeaf65d4c3/from-localhost-to-production-deploying-a-full-stack-app-with-cloudflare-railway-redis-docker-47fj</guid>
      <description>&lt;p&gt;How I Deployed a Full Stack Platform with Cloudflare, Railway, Redis and Background Workers&lt;/p&gt;

&lt;p&gt;Deploying the frontend was straightforward.&lt;/p&gt;

&lt;p&gt;Deploying the complete application was where the actual engineering started.&lt;/p&gt;

&lt;p&gt;I recently deployed &lt;a href="https://usdstation.com" rel="noopener noreferrer"&gt;USDStation&lt;/a&gt;, a platform for OpenUSD, NVIDIA Omniverse, Isaac Sim, robotics and digital twin assets.&lt;/p&gt;

&lt;p&gt;The application includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A React and Vite frontend&lt;/li&gt;
&lt;li&gt;A Node.js and Express API&lt;/li&gt;
&lt;li&gt;PostgreSQL through Prisma&lt;/li&gt;
&lt;li&gt;Redis and BullMQ&lt;/li&gt;
&lt;li&gt;A background asset-processing worker&lt;/li&gt;
&lt;li&gt;Cloudflare R2 object storage&lt;/li&gt;
&lt;li&gt;Clerk authentication with Google and GitHub OAuth&lt;/li&gt;
&lt;li&gt;Docker-based local infrastructure&lt;/li&gt;
&lt;li&gt;GitHub-based CI/CD&lt;/li&gt;
&lt;li&gt;Custom frontend and API domains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every component worked locally. The harder problem was making them communicate reliably after they became separate production services.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  |
  v
Cloudflare frontend
  |
  v
Railway API
  |-- PostgreSQL
  |-- Cloudflare R2
  `-- Redis --&amp;gt; Background worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clerk sits alongside this flow and manages authentication.&lt;/p&gt;

&lt;p&gt;The main system-design decision was to give each service one responsibility:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Pages&lt;/td&gt;
&lt;td&gt;Serve the frontend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Railway API&lt;/td&gt;
&lt;td&gt;Authentication, validation and orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Store users, assets and processing state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare R2&lt;/td&gt;
&lt;td&gt;Store asset files and previews&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Transport background jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Railway worker&lt;/td&gt;
&lt;td&gt;Process queued assets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;Manage identity and OAuth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Fixing the Railway deployment
&lt;/h2&gt;

&lt;p&gt;The repository contains separate frontend and backend directories. My first Railway deployment failed while Railpack was preparing the build. The API had not even started, so changing an Express route would not have solved it.&lt;/p&gt;

&lt;p&gt;That taught me to classify deployment failures by stage:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Repository detection&lt;/li&gt;
&lt;li&gt;Dependency installation&lt;/li&gt;
&lt;li&gt;Application build&lt;/li&gt;
&lt;li&gt;Runtime startup&lt;/li&gt;
&lt;li&gt;Health check&lt;/li&gt;
&lt;li&gt;External service connectivity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the repository contained multiple applications, Railway needed the correct backend root, build command and start command.&lt;/p&gt;

&lt;p&gt;The backend lifecycle is defined through scripts such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node dist/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start:worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node dist/workers/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prisma:generate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prisma generate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prisma:migrate:prod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prisma migrate deploy"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository is deployed twice on Railway. The API service runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker uses the same code and build output but runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start:worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps shared models and processing logic in one codebase while separating the runtime responsibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separating the API and worker
&lt;/h2&gt;

&lt;p&gt;USDStation processes uploaded 3D assets. Some operations take longer than an ordinary HTTP request should remain open.&lt;/p&gt;

&lt;p&gt;Running them inside the API would increase response times, consume API resources and risk losing work when requests disconnect. Instead, the API publishes a BullMQ job to Redis:&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;await&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;process-asset&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;assetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fileId&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A separate worker consumes the job:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;asset-processing&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;job&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;await&lt;/span&gt; &lt;span class="nf"&gt;processAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redisConnection&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 production flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API receives request
  -&amp;gt; validates the user and asset
  -&amp;gt; stores the initial state
  -&amp;gt; adds a Redis job
  -&amp;gt; responds to the user
  -&amp;gt; worker processes the job
  -&amp;gt; worker updates PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API remains responsive, and the worker can be restarted or scaled independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The localhost trap
&lt;/h2&gt;

&lt;p&gt;One local configuration did not make sense in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REDIS_HOST=localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside a container, &lt;code&gt;localhost&lt;/code&gt; refers to that container. For the API container, it means the API container. For the worker container, it means the worker container. Redis is a separate service with its own network address.&lt;/p&gt;

&lt;p&gt;Production therefore uses the deployed Redis connection details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REDIS_URL=redis://:&amp;lt;password&amp;gt;@&amp;lt;private-host&amp;gt;:&amp;lt;port&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real value is stored in Railway's environment configuration and is never committed to Git. Both the API and worker receive the same Redis connection, allowing BullMQ to connect the two processes.&lt;/p&gt;

&lt;p&gt;For local development, I run Redis through Docker Compose with &lt;code&gt;redis:7-alpine&lt;/code&gt;. This provides a predictable Redis version, persistent local data and a reproducible environment for other developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping files outside containers
&lt;/h2&gt;

&lt;p&gt;Container filesystems should be treated as temporary. A container may be rebuilt, restarted or replaced, so files stored only inside it can disappear.&lt;/p&gt;

&lt;p&gt;Uploaded assets therefore live in Cloudflare R2 instead of the API container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend requests a signed URL
  -&amp;gt; API authenticates the user
  -&amp;gt; API generates the R2 URL
  -&amp;gt; browser uploads directly to R2
  -&amp;gt; API stores metadata in PostgreSQL
  -&amp;gt; API adds a Redis job
  -&amp;gt; worker processes the stored object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Direct browser-to-R2 uploads also prevent the API from becoming a bandwidth bottleneck. PostgreSQL stores structured data, R2 stores binary files, Redis stores queue state, and Railway provides the compute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Cloudflare and Railway
&lt;/h2&gt;

&lt;p&gt;The frontend and backend use separate production domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend: https://usdstation.com
API:      https://api.usdstation.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connecting them required more than creating a DNS record.&lt;/p&gt;

&lt;p&gt;The frontend needs its production API URL during the Vite build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VITE_API_URL=https://api.usdstation.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vite variables are compiled into the generated bundle. Changing a dashboard value does not modify an existing build, so the frontend must be rebuilt.&lt;/p&gt;

&lt;p&gt;The API also needs to accept the production frontend through CORS:&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="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="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;allowedFrontendOrigins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the connection to work, these values must agree:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare DNS&lt;/li&gt;
&lt;li&gt;Railway custom domain&lt;/li&gt;
&lt;li&gt;Frontend API URL&lt;/li&gt;
&lt;li&gt;Backend CORS origin&lt;/li&gt;
&lt;li&gt;Clerk application domain&lt;/li&gt;
&lt;li&gt;OAuth callback URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Correct DNS proves that traffic reaches a destination. It does not prove that the application accepts that traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production authentication
&lt;/h2&gt;

&lt;p&gt;Authentication worked locally, but production required its own Clerk configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production Clerk keys&lt;/li&gt;
&lt;li&gt;A verified application domain&lt;/li&gt;
&lt;li&gt;Google OAuth credentials&lt;/li&gt;
&lt;li&gt;GitHub OAuth credentials&lt;/li&gt;
&lt;li&gt;Exact callback URLs&lt;/li&gt;
&lt;li&gt;Correct frontend origins&lt;/li&gt;
&lt;li&gt;Matching backend CORS rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The login flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  -&amp;gt; USDStation
  -&amp;gt; Clerk
  -&amp;gt; Google or GitHub
  -&amp;gt; Clerk callback
  -&amp;gt; authenticated USDStation session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A provider button appearing on the sign-in page does not prove that OAuth works. I tested both providers from the public domain and verified that they reached their real authorization flows with the correct callback.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD and production verification
&lt;/h2&gt;

&lt;p&gt;GitHub is the source of truth for deployment. The CI flow validates the code before Cloudflare and Railway deploy their respective services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install locked dependencies
  -&amp;gt; generate the Prisma client
  -&amp;gt; compile backend TypeScript
  -&amp;gt; lint and build the frontend
  -&amp;gt; report success or failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These stages answer different questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI:&lt;/strong&gt; Is this revision buildable?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Can it run in the target environment?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production testing:&lt;/strong&gt; Can a real user complete the workflow?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I did not consider the deployment complete when every dashboard showed green. I verified that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The frontend loaded from the public domain.&lt;/li&gt;
&lt;li&gt;The API responded through its custom domain.&lt;/li&gt;
&lt;li&gt;PostgreSQL and Redis were reachable.&lt;/li&gt;
&lt;li&gt;The worker connected and consumed jobs.&lt;/li&gt;
&lt;li&gt;Signed R2 operations worked.&lt;/li&gt;
&lt;li&gt;Google and GitHub reached their authorization flows.&lt;/li&gt;
&lt;li&gt;Processing state was persisted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;This deployment reinforced several practical lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A successful build is not a successful deployment.&lt;/li&gt;
&lt;li&gt;Build failures and runtime failures require different debugging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;localhost&lt;/code&gt; refers to the current container.&lt;/li&gt;
&lt;li&gt;Long-running work should be separated from API requests.&lt;/li&gt;
&lt;li&gt;Containers should not hold permanent uploaded files.&lt;/li&gt;
&lt;li&gt;Frontend environment variables often exist at build time.&lt;/li&gt;
&lt;li&gt;DNS, CORS and OAuth configuration must describe the same system.&lt;/li&gt;
&lt;li&gt;Green dashboards do not replace end-to-end testing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The biggest lesson was that production engineering is about boundaries.&lt;/p&gt;

&lt;p&gt;The frontend presents the product. The API validates and coordinates. PostgreSQL stores permanent state. R2 stores files. Redis transports jobs. The worker performs background processing. Clerk manages identity.&lt;/p&gt;

&lt;p&gt;Those services are deployed independently, but to the user they must behave like one application.&lt;/p&gt;

&lt;p&gt;The complete system is live at &lt;a href="https://usdstation.com" rel="noopener noreferrer"&gt;usdstation.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>node</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
