<?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>Forem: Render</title>
    <description>The latest articles on Forem by Render (@render).</description>
    <link>https://forem.com/render</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.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F957%2F5391d549-ab5f-416b-b639-e831a049b552.png</url>
      <title>Forem: Render</title>
      <link>https://forem.com/render</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/render"/>
    <language>en</language>
    <item>
      <title>How Render Scaled Knative to Support 100k+ Free-Tier Apps</title>
      <dc:creator>Stephen Barlow</dc:creator>
      <pubDate>Tue, 03 Oct 2023 18:37:29 +0000</pubDate>
      <link>https://forem.com/render/how-render-scaled-knative-to-support-100k-free-tier-apps-5g3g</link>
      <guid>https://forem.com/render/how-render-scaled-knative-to-support-100k-free-tier-apps-5g3g</guid>
      <description>&lt;p&gt;&lt;strong&gt;By Hieu Nguyen - October 3, 2023&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In November 2021, Render introduced a &lt;a href="https://render.com/docs/free" rel="noopener noreferrer"&gt;free tier&lt;/a&gt; for hobbyist developers and teams who want to kick the tires. Adoption grew at a steady, predictable rate—until Heroku announced the end of &lt;em&gt;their&lt;/em&gt; free offering ten months later:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bal39z76f3nvuhtln53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bal39z76f3nvuhtln53.png" alt="Graph of free-tier app creation" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render's free-tier adoption rate doubled immediately and grew from there&lt;/strong&gt; (awesome), causing our infrastructure to creak under the load (less awesome). In the span of a month, we experienced four incidents related to this surge. We knew that if Free usage continued to grow (and it very much has—as of this writing, &lt;strong&gt;tens of thousands&lt;/strong&gt; of free-tier apps are created each week), we needed to make it much more scalable. This post describes the first step we took along that path.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we initially built Free
&lt;/h2&gt;

&lt;p&gt;Some background: unlike other services on Render, free-tier web services "scale to zero" (as in, they stop running) if they go 15 minutes without receiving traffic. They start up again whenever they next receive an incoming request. This hibernation behavior helps us provide a no-cost offering without breaking the bank.&lt;/p&gt;

&lt;p&gt;However, this desired behavior presented an immediate development challenge. Render uses Kubernetes (K8s) behind the scenes, and K8s didn't natively support scale-to-zero (&lt;a href="https://github.com/kubernetes/enhancements/pull/2022" rel="noopener noreferrer"&gt;it still doesn't, as of September 2023&lt;/a&gt;). In looking for a solution that did, we found and settled on &lt;a href="https://knative.dev/docs/" rel="noopener noreferrer"&gt;Knative&lt;/a&gt; (kay-NAY-tiv). Knative extended Kubernetes with serverless support—a natural fit for services that would regularly spin up and down.&lt;/p&gt;

&lt;p&gt;In the interest of shipping quickly, we deployed Knative with its default configuration. And, until our growth spurt nearly a year later, those defaults worked without issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we hit a wall
&lt;/h2&gt;

&lt;p&gt;With the free-tier surge, the total number of apps on Render effectively quadrupled. This put significant strain on the networking layer of each of our Kubernetes clusters. To understand the nature of that strain, let's look at how this layer operates.&lt;/p&gt;

&lt;p&gt;Two networking components run on every node in every cluster: &lt;a href="https://github.com/projectcalico/calico" rel="noopener noreferrer"&gt;Calico&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/" rel="noopener noreferrer"&gt;kube-proxy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calico&lt;/strong&gt; mainly takes care of IP address management, or IPAM: assigning IP addresses to Pods and Services (we're using capital-S &lt;strong&gt;Service&lt;/strong&gt; to refer to a &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/" rel="noopener noreferrer"&gt;Kubernetes Service&lt;/a&gt;, to distinguish from the services that customers create on Render.). It also enforces &lt;a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/" rel="noopener noreferrer"&gt;Network Policies&lt;/a&gt; by managing iptables rules on the node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;kube-proxy&lt;/strong&gt; configures a different set of routing rules on the node to ensure traffic destined for a Service is load-balanced across all backing Pods.&lt;/p&gt;

&lt;p&gt;Both of these components do their jobs by listening for creates, updates, and deletes to all Pods and Services in the cluster. As you can imagine, having &lt;em&gt;more&lt;/em&gt; Pods and Services that changed &lt;em&gt;more&lt;/em&gt; frequently resulted in &lt;em&gt;more&lt;/em&gt; work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;More work meant more CPU consumption.&lt;/strong&gt; Remember, both Calico and kube-proxy run on &lt;em&gt;every&lt;/em&gt; node. The more CPU these components used, the less we had left to run our customers' apps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;More work meant higher update latency.&lt;/strong&gt; As the work queue grew, each networking change took longer to propagate due to increased time spent waiting in the queue. This delay is defined as the &lt;strong&gt;network programming latency&lt;/strong&gt;, or NPL (read more about NPL &lt;a href="https://github.com/kubernetes/community/blob/master/sig-scalability/slos/network_programming_latency.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;). When there was high NPL, traffic could be routed using stale rules that led nowhere (the Pod had already been destroyed), causing connections to fail intermittently.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To mitigate these issues, we needed to reduce the overhead each free-tier app added to our networking machinery.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Serviceless" Knative
&lt;/h2&gt;

&lt;p&gt;As mentioned, we'd deployed out-of-the-box Knative to handle free-tier resource provisioning. We took a closer look at exactly &lt;em&gt;what&lt;/em&gt; K8s primitives were being provisioned for each free-tier app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One Pod (for running the application code). Expected.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2N + 1&lt;/code&gt; Services, where &lt;code&gt;N&lt;/code&gt; is the number of times the app was deployed. This is because Knative manages changes with &lt;a href="https://knative.dev/docs/concepts/serving-resources/revisions/" rel="noopener noreferrer"&gt;Revisions&lt;/a&gt;, and retained resources belonging to historical Revisions. _Un_expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We figured the Pod needed to stay, but did we really need all those Kubernetes Services? What if we could get away with fewer—or even zero?&lt;/p&gt;

&lt;p&gt;We dove deeper into how those resources interacted in a cluster:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwv6sdfqf5xdvzrlugo4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwv6sdfqf5xdvzrlugo4.png" alt="Knative defaults in Render K8s clusters" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And learned what each of the Knative-provisioned Services (in purple above) was for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Placeholder Service&lt;/strong&gt; was a dummy service that existed to prevent naming collisions among resources for Knative-managed apps. There was one for every free-tier app.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Public Service&lt;/strong&gt; routed incoming traffic to the app from the &lt;em&gt;public internet&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Private Service&lt;/strong&gt; routed incoming &lt;em&gt;cluster-local&lt;/em&gt; traffic based on whether the app was scaled up.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If scaled up&lt;/strong&gt;, traffic was routed to the Pod.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If scaled down&lt;/strong&gt;, traffic was routed to the cluster's Knative proxy (called the &lt;a href="https://knative.dev/docs/serving/knative-kubernetes-services/#service-activator" rel="noopener noreferrer"&gt;activator&lt;/a&gt;), which handled scaling up the app by creating a Pod.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Armed with this newfound knowledge, we devised a path to remove all of these Services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step by step
&lt;/h3&gt;

&lt;p&gt;We started simple with the dummy &lt;strong&gt;Placeholder Service&lt;/strong&gt;, which did &lt;em&gt;literally nothing&lt;/em&gt;. There was no risk of naming collisions among our Knative-managed resources, so we updated the Knative Route controller to stop creating the Placeholder Service. ❌&lt;/p&gt;

&lt;p&gt;Next! While the &lt;strong&gt;Public Service&lt;/strong&gt; (for public internet routing) is needed for plenty of Knative use cases out there, in Render-land, all requests from the public Internet must pass through our load-balancing layer. This means requests are guaranteed to be &lt;em&gt;cluster-local&lt;/em&gt; by the time they reach Pods, so the &lt;strong&gt;Public Service&lt;/strong&gt; &lt;em&gt;also&lt;/em&gt; had nothing to do! We patched Knative to stop reconciling it and its related Endpoint resources. ❌&lt;/p&gt;

&lt;p&gt;Finally, the &lt;strong&gt;Private Service&lt;/strong&gt; (for cluster-local routing). We put together the concepts that Services are used to balance load across backing Pods, and that a free-tier app can have at most only one Pod receiving traffic at a time, making load balancing &lt;em&gt;slightly&lt;/em&gt; unnecessary. There were two changes we needed to make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Streamline traffic to flow exclusively through the activator, as we no longer had a Service to split traffic to when the app is scaled up. With a little experimentation, we discovered that the activator could both wake Pods &lt;em&gt;and&lt;/em&gt; reverse-proxy to a woke Pod, even though that behavior wasn't documented! We just needed to set the right headers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Patch the activator to listen for changes to Pod readiness states, and route directly to Pod IP addresses (thanks, Calico!). By default, the activator listens for changes to EndpointSlices, but those are tied to the Services we were hoping to delete.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And just like that, the &lt;strong&gt;Private Service&lt;/strong&gt; was no more. ❌&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Want to go deeper under the hood? Check out an abridged version of &lt;a href="https://render.com/blog/knative-design-doc" rel="noopener noreferrer"&gt;the design doc&lt;/a&gt; for removing the &lt;strong&gt;Private Service&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the end of this entire optimization pass, the networking architecture for a free-tier app had been simplified to the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnjezl9jiucxttzjkuu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnjezl9jiucxttzjkuu9.png" alt="Free-tier architecture after Knative Service removal" width="656" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Zero&lt;/em&gt; Kubernetes Services per free-tier app! Predictably, K8s Service counts plummeted across our clusters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj014v8xpr7iurd5vsrij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj014v8xpr7iurd5vsrij.png" alt="Chart of Service count by cluster over time" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these improvements, Calico and kube-proxy's combined usage fell by &lt;strong&gt;hundreds of CPU seconds&lt;/strong&gt; in our largest cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feig0jos5mwknqm1h42kt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feig0jos5mwknqm1h42kt.png" alt="Chart of CPU usage over time" width="570" height="834"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With compute resources freed up, free-tier network latency and stability improved dramatically. But even so, we knew we had more work to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  A moving target
&lt;/h2&gt;

&lt;p&gt;Our Knative tweaks bought us some much-needed breathing room, but ultimately, free-tier usage began to put a strain even on this optimized architecture. The time was quickly approaching for us to rip out Knative entirely, in favor of a home-grown solution that was tailor-made for Render's needs.&lt;/p&gt;

&lt;p&gt;But that's a story for another post!&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;h3&gt;
  
  
  Related reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://render.com/blog/knative-design-doc" rel="noopener noreferrer"&gt;Design doc for removing the &lt;strong&gt;Private Service&lt;/strong&gt;&lt;/a&gt; (abridged)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://knative.dev/docs/concepts/" rel="noopener noreferrer"&gt;Knative documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://render.com/careers" rel="noopener noreferrer"&gt;Careers at Render&lt;/a&gt; 😉&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>knative</category>
    </item>
    <item>
      <title>Migration Guide: Heroku to Render</title>
      <dc:creator>Audrey Maldonado</dc:creator>
      <pubDate>Thu, 25 Aug 2022 20:43:00 +0000</pubDate>
      <link>https://forem.com/render/migration-guide-heroku-to-render-53p6</link>
      <guid>https://forem.com/render/migration-guide-heroku-to-render-53p6</guid>
      <description>&lt;p&gt;&lt;a href="https://render.com/docs/migrate-from-heroku" rel="noopener noreferrer"&gt;This guide&lt;/a&gt; will help you migrate a Heroku app, Heroku Postgres database, and Heroku Redis instance to Render. Using Render to run your web services and databases is very similar to running them on Heroku. Additionally, many users have found that Render saves them money and provides additional functionality not available from Heroku. Read more about the &lt;a href="https://dev.to/render-vs-heroku-comparison"&gt;differences between Render and Heroku&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This guide and associated Heroku CLI plugin covers most migration use cases. It supports migrating a Heroku app and its Heroku Postgres and Heroku Redis add-ons. You may continue using other Heroku add-ons while your app is running on Render by copying over the appropriate environment variables -- e.g., if you are using Heroku's Sengrid add-on, set &lt;code&gt;SENDGRID_USERNAME&lt;/code&gt; and &lt;code&gt;SENDGRID_PASSWORD&lt;/code&gt; in the Render Dashboard. Please contact us at &lt;a href="mailto:support@render.com"&gt;support@render.com&lt;/a&gt; if you experience any problems migrating from Heroku to Render.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Heroku app is using one of Heroku's &lt;a href="https://devcenter.heroku.com/articles/buildpacks#officially-supported-buildpacks" rel="noopener noreferrer"&gt;official buildpacks&lt;/a&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;Heroku app is not using multiple buildpacks&lt;sup id="fnref2"&gt;2&lt;/sup&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Migration Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Generate a &lt;code&gt;Dockerfile.render&lt;/code&gt; and &lt;code&gt;render.yaml&lt;/code&gt; using Render's Heroku CLI Plugin&lt;/li&gt;
&lt;li&gt;Create Resources on Render&lt;/li&gt;
&lt;li&gt;Configure Environment Variables&lt;/li&gt;
&lt;li&gt;Copy Data From PostgreSQL&lt;/li&gt;
&lt;li&gt;Update DNS Configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Concept Mapping
&lt;/h2&gt;

&lt;p&gt;Before you start the migration, review the following table to understand how some Heroku concepts map to Render concepts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Heroku&lt;/th&gt;
&lt;th&gt;Render&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web Process (within a Heroku app)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/docs/web-services"&gt;Web Service&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker Process (within a Heroku app)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/docs/background-workers"&gt;Background Worker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dyno&lt;/td&gt;
&lt;td&gt;An instance of your service on Render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heroku Postgres&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/docs/databases"&gt;Render PostgreSQL&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heroku Redis&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/docs/redis"&gt;Render Redis&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heroku Scheduler&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/docs/cronjobs"&gt;Cron Job&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config Vars&lt;/td&gt;
&lt;td&gt;Environment Variables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 1: Generate a &lt;code&gt;Dockerfile.render&lt;/code&gt; and &lt;code&gt;render.yaml&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install Render's Heroku CLI Plugin
&lt;/h3&gt;

&lt;p&gt;Render has created a plugin for the Heroku CLI to reduce some of the manual migration steps. Install the plugin with the following command.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
The CLI plugin will &lt;strong&gt;not&lt;/strong&gt; modify your Heroku app. It only performs read operations.&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku plugins:install @renderinc/heroku-import
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run CLI Plugin
&lt;/h3&gt;

&lt;p&gt;The CLI plugin will read information about your app and its add-ons and create two files: &lt;code&gt;Dockerfile.render&lt;/code&gt; and &lt;code&gt;render.yaml&lt;/code&gt;. Here is what the process looks like. Run it from the root of the repository containing your app's code.&lt;/p&gt;

&lt;p&gt;You will then be prompted to select the Heroku Postgres and Heroku Redis add-ons you would like to migrate to Render. After making your selections, a &lt;code&gt;Dockerfile.render&lt;/code&gt; and &lt;code&gt;render.yaml&lt;/code&gt; will be created for you, and instructions will be shown.&lt;/p&gt;

&lt;h4&gt;
  
  
  Excluded Environment Variables
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DATABASE_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HEROKU_&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PASSWORD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REDIS_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REDIS_TLS_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SECRET&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Add &lt;code&gt;Dockerfile.render&lt;/code&gt; and &lt;code&gt;render.yaml&lt;/code&gt; to Source Control
&lt;/h3&gt;

&lt;p&gt;The CLI plugin has now created two files: &lt;code&gt;Dockerfile.render&lt;/code&gt; and &lt;code&gt;render.yaml&lt;/code&gt;. The &lt;code&gt;Dockerfile.render&lt;/code&gt; defines how to build your app on Render using a Heroku buildpack. The &lt;code&gt;render.yaml&lt;/code&gt; is Render's &lt;a href="https://dev.to/docs/infrastructure-as-code"&gt;Infrastructure-as-Code&lt;/a&gt; file. It can be used to define multiple services and databases running on Render and their relationship to each other.&lt;/p&gt;

&lt;p&gt;Now, add these two files to source control with the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add Dockerfile.render render.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'Prepare app for deploy to Render'&lt;/span&gt;
git push origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create Resources on Render
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://dashboard.render.com" rel="noopener noreferrer"&gt;Render Dashboard&lt;/a&gt;, select &lt;strong&gt;New +&lt;/strong&gt; and then &lt;strong&gt;Blueprint&lt;/strong&gt;. Connect your &lt;a href="https://dev.to/docs/github"&gt;GitHub&lt;/a&gt; or &lt;a href="https://dev.to/docs/gitlab"&gt;GitLab&lt;/a&gt; account to Render if you haven't already, and then search for and select your repository.&lt;/p&gt;

&lt;p&gt;Render will create a plan to build and deploy your service along with a Render PostgreSQL database and Render Redis service if your Heroku app was using these and you selected them for import in the Run CLI Plugin step above.&lt;/p&gt;

&lt;p&gt;Review the plan and click &lt;strong&gt;Apply&lt;/strong&gt;, and Render will create your resources.&lt;/p&gt;

&lt;p&gt;If all resources were created successfully, continue to the next step. If there was an error you cannot resolve, please contact us at &lt;a href="mailto:support@render.com"&gt;support@render.com&lt;/a&gt;. We're here to help!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Configure Environment Variables
&lt;/h2&gt;

&lt;p&gt;You may need to manually create environment variables that contain secrets like passwords or API tokens using the Render Dashboard. The CLI plugin excluded environment variables with names containing values indicating they may contain secrets.&lt;/p&gt;

&lt;p&gt;To create or update environment variables using the Render Dashboard,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;strong&gt;Dashboard&lt;/strong&gt; on the left&lt;/li&gt;
&lt;li&gt;Select the service you just created from the list&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Environment&lt;/strong&gt; tab and create or update environment variables (check the CLI plugin output if you'd like to copy from your Heroku Config Vars)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 4: Copy Data from PostgreSQL
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PostgreSQL
&lt;/h3&gt;

&lt;p&gt;If you have created a Render PostgreSQL database, you may want to copy the data from your Heroku Postgres database to Render.&lt;/p&gt;

&lt;p&gt;Put your Heroku app into maintenance mode so that no new data is written to the database during the copy. &lt;code&gt;&amp;lt;HEROKU APP NAME&amp;gt;&lt;/code&gt; is the Heroku app that owns the Heroku Postgres add-on.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
This data migration process requires some downtime.&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku maintenance:on &lt;span class="nt"&gt;--app&lt;/span&gt; &amp;lt;HEROKU APP NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a backup of the data in you Heroku Postgres database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku pg:backups:capture &lt;span class="nt"&gt;--app&lt;/span&gt; &amp;lt;HEROKU APP NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download the backup. This will download a file named &lt;code&gt;latest.dump&lt;/code&gt; to your local computer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku pg:backups:download &lt;span class="nt"&gt;--app&lt;/span&gt; &amp;lt;HEROKU APP NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import &lt;code&gt;latest.dump&lt;/code&gt; into your Render PostgreSQL database. The value for &lt;code&gt;&amp;lt;EXTERNAL CONNECTION STRING&amp;gt;&lt;/code&gt; can be found on the Render Dashboard page for your database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_restore &lt;span class="nt"&gt;--verbose&lt;/span&gt;  &lt;span class="nt"&gt;--no-acl&lt;/span&gt; &lt;span class="nt"&gt;--no-owner&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;EXTERNAL CONNECTION STRING&amp;gt; latest.dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;If your database is larger than 20GB or under heavy load, use &lt;a href="https://help.heroku.com/7U1BTYHB/how-can-i-take-a-logical-backup-of-large-heroku-postgres-databases" rel="noopener noreferrer"&gt;Heroku's instructions&lt;/a&gt; to create a backup of your data. After that has completed, you can use the same &lt;code&gt;pg_restore&lt;/code&gt; command above to import the data to your Render PostgreSQL database.&lt;/p&gt;

&lt;p&gt;Consider using the &lt;code&gt;--jobs&lt;/code&gt; flag available to both the &lt;code&gt;pg_dump&lt;/code&gt; and &lt;code&gt;pg_restore&lt;/code&gt; commands to reduce the time required for backup and restore.&lt;/p&gt;



&lt;h2&gt;
  
  
  Step 5: Update DNS Configuration
&lt;/h2&gt;

&lt;p&gt;If your Heroku app is using a custom domain, follow the instructions to &lt;a href="https://dev.to/docs/custom-domains#configuring-dns-to-point-to-render"&gt;update your DNS configuration to point to Render&lt;/a&gt; instead of Heroku. Note that some downtime may be required between when your DNS changes propogate and when Render provisions a TLS certificate for your domain.&lt;/p&gt;






&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;If you would like to migrate a Heroku app to Render that is not using an official Heroku buildpack please contact us at &lt;a href="mailto:support@render.com"&gt;support@render.com&lt;/a&gt; for assistance. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;We are working on supporting migration of Heroku apps that use multiple buildpacks. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>heroku</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>...It Works Fine Locally</title>
      <dc:creator>Audrey Maldonado</dc:creator>
      <pubDate>Fri, 19 Aug 2022 17:40:15 +0000</pubDate>
      <link>https://forem.com/render/it-works-fine-locally-1jam</link>
      <guid>https://forem.com/render/it-works-fine-locally-1jam</guid>
      <description>&lt;p&gt;Our latest blog post, &lt;em&gt;by Support Engineer Alan Pinnell-Smith&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When you're ready to deploy to production, it can be frustrating to find that your project &lt;em&gt;that worked perfectly on your machine&lt;/em&gt; is now throwing errors in the cloud. As you can imagine, the Support Engineering team at Render helps customers fix these issues on a regular basis, so we’ve created a checklist to help prevent them in the first place and save us all some time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Code
&lt;/h2&gt;

&lt;p&gt;Deploying a project can feel tough, especially when you're starting out.&lt;/p&gt;

&lt;p&gt;Web development used to be so straightforward. Open up a text editor, write some HTML, add a few images, FTP it all up to a server and you have a website live on the Internet! Simpler times — boring, non-dynamic, table-layout, under-construction-GIF, “best viewed on Netscape Navigator”, hit-counter times, but certainly a lot simpler.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3kb36zm1wo7io1pbe8tr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3kb36zm1wo7io1pbe8tr.png" alt="Render re-imagined as a website from the 1990s" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fast-forward to today, and the number of languages, frameworks, tools, design patterns and a multitude of other aspects surrounding web development are staggering and overwhelming. I've been tinkering on the web since the mid-90s (as you can probably tell from the references above) but the sheer number of choices available to the modern-day web developer makes me wonder how anyone can ever know where to start. In addition to the flood of choices, the pace at which these things evolve can be very hard to keep up with (I'm looking at you, JavaScript!).&lt;/p&gt;

&lt;p&gt;Luckily, there are also a huge number of helpful resources out there: guides, video courses, the obligatory blog/to-do list tutorials, and our very own &lt;a href="https://dev.to/docs"&gt;Render Quickstarts&lt;/a&gt; and &lt;a href="https://github.com/render-examples" rel="noopener noreferrer"&gt;Examples&lt;/a&gt;. There's also the ever-helpful Stack Overflow when you hit that brick wall of an unexpected error message. Tools like project scaffolding, ORMs, and development servers all make it easier to get started with a new project on your machine. But when it comes to deploying your code, there isn’t enough &lt;a href="https://dev.to/blog/why-render"&gt;help online&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying Code
&lt;/h2&gt;

&lt;p&gt;When I made the change in my career from being a web developer (mainly Ruby) to a Support Engineer a few years ago, one phrase quickly became apparent as a common sign-off: "… it works fine locally". Along with its bedfellows "works for me" and "works on my machine", these are phrases we hear often in development, and especially in developer support.&lt;/p&gt;

&lt;p&gt;Don't get me wrong: it's certainly good to know that there are no glaring code issues preventing a project from spinning up at all. However, the considerations required to deploy a project in the cloud are an essential part of being a web developer these days. Even if it works fine locally, chances are it’s not going to work fine when you put your shiny new project on someone else's machine or platform.&lt;/p&gt;

&lt;p&gt;We're all keen to get our work out there on the web as soon as possible, but environments will differ — sometimes by a lot. You may develop on Windows or macOS or something more exotic, but almost always, you will deploy to some flavor of Linux. Every language, framework and tool will have a particular version and likely a sprawling list of dependencies (each with its own version, dependency and compatibility requirements) goes all the way down. Again, we're lucky to have some great package management tools to keep all that in check for us.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/docs/native-environments"&gt;Render Native Environments&lt;/a&gt; help the most commonly used project configurations get up and running as quickly as possible, and we inspect your code to supply some defaults along the way. But because every project is different, the required configuration can be too.&lt;/p&gt;

&lt;p&gt;Here's a checklist that, while focused on Render, will help you regardless of where you're deploying your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;This list won't apply to every case, but being mindful of these points may make deploying your application, and debugging any issues with that deployment, a little more tractable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fof5nehjz5r560ybs8ij8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fof5nehjz5r560ybs8ij8.png" alt="A checklist to avoid common deployment errors" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Versioning&lt;/strong&gt;: Explicitly define versions. Language, package manager and packages. Render has &lt;a href="https://dev.to/docs/native-environments#support-languages-and-runtimes"&gt;default language versions&lt;/a&gt; for native environments, but we'd still recommend you set your own version, e.g. &lt;a href="https://dev.to/docs/node-version"&gt;Node&lt;/a&gt;, &lt;a href="https://dev.to/docs/python-version"&gt;Python&lt;/a&gt;, &lt;a href="https://dev.to/docs/ruby-version"&gt;Ruby&lt;/a&gt;, etc. This way, you can be sure it's the same as the one you developed on and will continue to stay the same if the default changes in future.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;: Keep configuration in the environment. That may sound familiar if you've ever looked at &lt;a href="https://12factor.net/" rel="noopener noreferrer"&gt;The Twelve-Factor App&lt;/a&gt;. External service URLs, API keys/tokens, SMTP config, etc. will be easier to manage per environment if stored in &lt;a href="https://dev.to/docs/configure-environment-variables"&gt;environment variables or as secret files&lt;/a&gt;. And if you need to share these across services, check out Render’s &lt;a href="https://dev.to/docs/configure-environment-variables#2-environment-groups"&gt;Environment Groups&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add a Health Check&lt;/strong&gt;: Create an HTTP endpoint in your app to check if your application is live. Web Services on Render allow you to specify a &lt;a href="https://dev.to/docs/deploys#health-checks"&gt;health check path&lt;/a&gt; that's used to check that a new deploy is working as expected before transitioning requests to it. It is also used to check if your app is still up after a deploy; Render automatically restarts your app if the health check endpoint is unresponsive. &lt;br&gt; &lt;br&gt; A health check endpoint should touch critical parts of your app, for example, include a simple database connection in it or ping an API endpoint. If your checks pass, return a response code between 200-399 and the deploy will be put live. If the health check responds with a code 400+ the deployment will be marked as a failure and will not proceed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Production Builds&lt;/strong&gt;: Verify the dependencies being installed when you build your app in the cloud. Use the same tools you did for development, e.g. npm/Yarn (Node), pip/Poetry (Python), Bundler (Ruby), etc. These tools can also have dependency groups, like devDependencies/dependencies in Node's package.json; make sure your packages are in the right group and be aware that &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-install#:~:text=With%20the%20%2D%2Dproduction,production%3Dfalse." rel="noopener noreferrer"&gt;production mode may not install development group packages&lt;/a&gt;. &lt;br&gt; &lt;br&gt; Many frameworks also have dev tooling that does things like hot code reloading or just-in-time compilation. The Rails asset pipeline or a Webpack dev server are good examples of this. These development helpers are usually unsuitable for production, so you should build/precompile/transpile as your particular project requires. &lt;br&gt; &lt;br&gt; To keep your Render build command(s) tracked along with your code, it can be handy to put it in a shell script and use that to set up the environment.  A simple Node based example would be:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sh&lt;/span&gt;&lt;span class="cp"&gt;

#!/usr/bin/env bash
&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;exit&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="nx"&gt;errexit&lt;/span&gt;

&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt;
&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Booting your application&lt;/strong&gt;: Make sure you start your app in production mode and are not using a development server. If you're having trouble with a deploy it can sometimes help to try starting your app in production mode on your dev machine to see if it still works fine locally. &lt;br&gt; &lt;br&gt; For &lt;a href="https://dev.to/docs/web-services"&gt;Web Services&lt;/a&gt; in particular ensure the app server has its host &amp;amp; port set to 0.0.0.0 and the PORT environment variable, respectively. You may also want to consider concurrency settings appropriate for your chosen service plan, e.g. worker counts in &lt;a href="https://github.com/hunterloftis/throng#specify-the-number-of-workers" rel="noopener noreferrer"&gt;Throng&lt;/a&gt; (Node), &lt;a href="https://docs.gunicorn.org/en/stable/settings.html#worker-processes" rel="noopener noreferrer"&gt;Gunicorn&lt;/a&gt; (Python), &lt;a href="https://github.com/puma/puma#clustered-mode" rel="noopener noreferrer"&gt;Puma&lt;/a&gt; (Ruby), etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File Storage&lt;/strong&gt;: Service runtime instance filesystems are not persistent.  Render service filesystems (and most platforms) are ephemeral, meaning files written after they’re started do not persist after a restart/new deploy.  If you need to persist uploaded/generated files between deploys you will need a &lt;a href="https://dev.to/docs/disks"&gt;Render Disk&lt;/a&gt;. &lt;br&gt; &lt;br&gt; While an ephemeral filesystem may feel counterintuitive at first – "why can't I just save my uploads to my instance?!" – it makes scaling and recovery from outages much more straightforward. Cloud-deployed instances benefit from being readily disposable/recreatable. &lt;br&gt; &lt;br&gt; When you do need to store longer-term data, you can add a &lt;a href="https://dev.to/docs/disks"&gt;Render Disk&lt;/a&gt; for file persistence. Depending on your data storage needs, it may be also worth considering a dedicated database like PostgreSQL or an object storage service like AWS S3. It’s easy to forget about this during development and end up with your application relying on local filesystem storage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom domains and SSL/TLS&lt;/strong&gt;: Configure and secure your custom domain. You're unlikely to be running a dev environment on a custom domain or over HTTPS, so you may need to make some configuration updates to ensure your app is aware of how it may receive requests (&lt;a href="https://docs.djangoproject.com/en/4.0/ref/settings/#allowed-hosts" rel="noopener noreferrer"&gt;like ALLOWED_HOSTS in Django&lt;/a&gt;, etc.). You may also want to set up a redirect in your code to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301" rel="noopener noreferrer"&gt;301&lt;/a&gt; from the default service-name.onrender.com to your custom domain to keep it canonical. &lt;br&gt; &lt;br&gt; &lt;a href="https://dev.to/docs/tls"&gt;SSL/TLS Certificates are fully managed on Render&lt;/a&gt; and redirection of HTTP requests to HTTPS is automatic. You may need to ensure your assets use HTTPS in production if using a full URL and not just a relative path, to avoid any &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content/How_to_fix_website_with_mixed_content" rel="noopener noreferrer"&gt;mixed-content errors&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With all that said, using &lt;a href="https://dev.to/docs/docker"&gt;Docker&lt;/a&gt; can sometimes help reduce the burden of thinking about many of the above items by making your dev environment more closely match the production environment, but using Docker also adds a bunch of additional complexity, and that's a post for another day...&lt;/p&gt;

&lt;p&gt;We've touched on a lot of areas to get your project deployed successfully, but it’s hard to cover everything in a blog post. If you find you're still having trouble deploying things on Render, we're here to help and you can reach us at &lt;a href="mailto:support@render.com"&gt;support@render.com&lt;/a&gt; or post on &lt;a href="https://community.render.com/" rel="noopener noreferrer"&gt;community.render.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Migrate a Django App from Heroku to Render (and get it ready for prod)</title>
      <dc:creator>Audrey Maldonado</dc:creator>
      <pubDate>Fri, 01 Jul 2022 19:12:51 +0000</pubDate>
      <link>https://forem.com/render/migrate-a-django-app-from-heroku-to-render-and-get-it-ready-for-prod-596f</link>
      <guid>https://forem.com/render/migrate-a-django-app-from-heroku-to-render-and-get-it-ready-for-prod-596f</guid>
      <description>&lt;p&gt;&lt;strong&gt;By Rosalind Benoit - June 27, 2022&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Picture this: you run a hefty Django app, an online store for your company. It’s a collection of services – an API, a shiny frontend, a database, an in-memory datastore, worker nodes, a GUI for admins, a bunch of scheduled jobs…the whole shebang – all tuned to perform quickly for your customers. You’re ready to kick into high gear as the busy season approaches and the team preps new product launches.&lt;/p&gt;

&lt;p&gt;Then suddenly, your cloud provider is struggling with operational challenges. You can’t deploy updates. You’re channeling #HugOps, but your apps must continue to #JustWork.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1527293265149513736-877" src="https://platform.twitter.com/embed/Tweet.html?id=1527293265149513736"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1527293265149513736-877');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1527293265149513736&amp;amp;theme=dark"
  }



&lt;/p&gt;



&lt;p&gt;While our &lt;a href="https://render.com/docs/migrate-from-heroku" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; explains the process for moving any kind of app over, I often like to have a detailed example to follow. As a Python developer and a former Drupal and Magento admin for a retail chain, I wanted to share a migration post that could help someone in a time of need like the one described above. With this guide, I will help you move your Django app to Render so that it's production-ready : ) If you’re looking to start fresh instead, check out our guide for quickly &lt;a href="https://render.com/docs/deploy-django" rel="noopener noreferrer"&gt;deploying a new Django app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1527307407466106880-856" src="https://platform.twitter.com/embed/Tweet.html?id=1527307407466106880"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1527307407466106880-856');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1527307407466106880&amp;amp;theme=dark"
  }



&lt;/p&gt;



&lt;p&gt;This post will walk through a practical example of migrating an application from Heroku to Render using &lt;a href="https://saleor.io/" rel="noopener noreferrer"&gt;Saleor&lt;/a&gt;, a popular open-source e-commerce system. Based on Django and Python, Saleor has served high-volume companies in retail sectors like publishing and apparel since 2012 and is still growing rapidly. The latest major update introduces a modular front end powered by a GraphQL API and written with React and TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Saleor, Why Render?
&lt;/h2&gt;

&lt;p&gt;Lots of Render users may encounter real-world use cases in which they need to deploy a full-featured storefront, and Saleor provides a flexible API-first solution. The framework appeals to us at Render because it’s developer-first, actively maintained, and receptive to community feedback and contribution.&lt;/p&gt;

&lt;p&gt;You may wonder why you would want to migrate your Django app from Heroku to Render. Our &lt;a href="https://render.com/render-vs-heroku-comparison" rel="noopener noreferrer"&gt;comparison page&lt;/a&gt; explains all the benefits you'll get – like private networking, HTTP/3, and DDoS protection, among many other things. Running a production-grade instance of Saleor on Heroku gets complicated really quickly. This guide explains how we addressed much of that complexity in the Render environment.&lt;/p&gt;

&lt;p&gt;There is one more reason we chose the Saleor project for this guide. We often hear questions about Docker Compose from our users interested in taking advantage of &lt;a href="https://render.com/docs/infrastructure-as-code" rel="noopener noreferrer"&gt;Render’s implementation of IaC&lt;/a&gt; (infrastructure-as-code). Within on of its repositories, the Saleor project maintains code and documentation for deploying all components of Saleor &lt;a href="https://github.com/saleor/saleor-platform/blob/main/docker-compose.yml" rel="noopener noreferrer"&gt;using Docker Compose&lt;/a&gt;. This guide will also discuss how we translated that &lt;code&gt;docker-compose.yml&lt;/code&gt; into a Render Blueprint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7lxfp95c1w8nuq0oeby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7lxfp95c1w8nuq0oeby.png" alt="A screenshot of the homepage of the Saleor Demo, an e-commerce site with Saleor-branded items for sale." width="800" height="991"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before embarking on a migration, it helps to understand how some high-level concepts map from Heroku to Render. Check out the helpful &lt;a href="https://render.com/docs/migrate-from-heroku#concept-mapping" rel="noopener noreferrer"&gt;concept mapping&lt;/a&gt; my coworker Chris put together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy Saleor on Render
&lt;/h2&gt;

&lt;p&gt;In the first part of this post, we’ll migrate a demo instance of Saleor from Heroku to Render. In the second half, we’ll productionalize Saleor on Render. If you want to follow along on our journey interactively, fork the &lt;a href="https://github.com/saleor/saleor" rel="noopener noreferrer"&gt;saleor repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ll start by creating a &lt;code&gt;render.yaml&lt;/code&gt; file at the root of our repository. If our deployment is a Bob Ross creation, this is our blank canvas – the Blueprint that defines and integrates all the components of the working production application. You can certainly use the Render Dashboard to deploy services and databases individually, but we’ll codify the architecture in a &lt;code&gt;render.yaml&lt;/code&gt; so we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce the chance of human error&lt;/li&gt;
&lt;li&gt;Reduce repetitive point-and-click configuration&lt;/li&gt;
&lt;li&gt;Define a source of truth for the architecture with version control and git blame&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Saleor’s README invitation to deploy their demo on a Heroku instance acts as our entry point for this post. We’ll first walk through each stage of migrating the demo to Render, and then illustrate steps for making the Django app production-ready. Saleor consists of three components: the Saleor Core backend server, the Saleor Dashboard GUI, and the Saleor React Storefront. Here's a preview of the architecture we'll deploy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gxk16djpkyd68tb4og8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gxk16djpkyd68tb4og8.png" alt="The Saleor architecture diagram" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll paint each happy little tree at a time 🌳, but you can peek at the final creation &lt;a href="https://github.com/render-examples/saleor" rel="noopener noreferrer"&gt;on Github&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Part One: Migrate a Django App from Heroku to Render&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a Web Service for the API&lt;/li&gt;
&lt;li&gt;Add a Database&lt;/li&gt;
&lt;li&gt;Add Redis&lt;/li&gt;
&lt;li&gt;Add Build Steps&lt;/li&gt;
&lt;li&gt;Add a Frontend React App&lt;/li&gt;
&lt;li&gt;Add a Static Dashboard&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Part Two: Productionalize a Django App on Render&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a Secret File&lt;/li&gt;
&lt;li&gt;Add a Background Worker&lt;/li&gt;
&lt;li&gt;Add a Cron Job&lt;/li&gt;
&lt;li&gt;Update Build Steps&lt;/li&gt;
&lt;li&gt;Add Runtime Steps&lt;/li&gt;
&lt;li&gt;Add a Message Broker&lt;/li&gt;
&lt;li&gt;DRY It Up&lt;/li&gt;
&lt;li&gt;Help! (a Helper File for Derived Variables)&lt;/li&gt;
&lt;li&gt;Next Steps&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;&lt;strong&gt;A note on using other application manifests to generate Blueprints:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your application has been deployed on Heroku or with an infrastructure provisioning tool, you likely have other infrastructure-as-code files, like a &lt;code&gt;Procfile&lt;/code&gt; or &lt;code&gt;docker-compose.yml&lt;/code&gt;, to refer to while setting up a deployment to Render. These files can act as useful tools for creating Blueprints. This post will reference several alternative application manifests to demonstrate how they were used.&lt;/p&gt;

&lt;p&gt;The saleor and saleor-dashboard repositories both contain &lt;code&gt;app.json&lt;/code&gt; files for Heroku deployment. The Saleor project also includes the saleor-platform repository, which combines the Saleor API, saleor-dashboard, and the react-storefront. Both saleor-platform and the Heroku deployments are intended for local development and demo purposes and do not reflect productionalized Saleor environments. Still, we’ll use &lt;code&gt;app.json&lt;/code&gt; and Docker Compose files  as guides for getting started with a Blueprint that describes the Saleor services and their attributes. These resources are particularly helpful in pointing us to the environment variables and commands necessary to build and run an app.&lt;/p&gt;



&lt;h2&gt;
  
  
  Migrate a Django App from Heroku to Render
&lt;/h2&gt;

&lt;p&gt;To begin, we will deploy the Saleor Core API server using Render’s native Python environment. We maintain &lt;a href="https://render.com/docs/native-environments" rel="noopener noreferrer"&gt;native environments&lt;/a&gt; to make deploying to production similar in complexity to running code locally. Like Buildpacks in Heroku, they provide common language runtimes and minimize the need to provision utilities used to build and deploy; native environments aim to provide more control and customization capability while requiring fewer steps to use and understand than Buildpacks. Let’s jump in to creating a &lt;code&gt;render.yaml&lt;/code&gt; to set up our demo Django app!&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Web Service
&lt;/h3&gt;

&lt;p&gt;We’ll begin by defining a Web Service for the Django app’s GraphQL API in a brand new &lt;code&gt;render.yaml&lt;/code&gt;. This service is described in an &lt;a href="https://github.com/saleor/saleor/blob/main/app.json" rel="noopener noreferrer"&gt;&lt;code&gt;app.json file&lt;/code&gt;&lt;/a&gt; and corresponds to the &lt;a href="https://github.com/saleor/saleor-platform/blob/main/docker-compose.yml#L4-L33" rel="noopener noreferrer"&gt;&lt;code&gt;api&lt;/code&gt; service&lt;/a&gt; in the saleor-platform &lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/render-examples/saleor&lt;/span&gt;
    &lt;span class="na"&gt;buildCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip3 install -r requirements.txt &amp;amp;&amp;amp; python manage.py migrate --no-input&lt;/span&gt;
    &lt;span class="na"&gt;startCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALLOWED_HOSTS=".onrender.com" gunicorn --bind :$PORT --workers 4 --worker-class uvicorn.workers.UvicornWorker saleor.asgi:application&lt;/span&gt;
    &lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor.settings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NPM_CONFIG_PRODUCTION&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;noreply@example.com&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;False&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;
        &lt;span class="na"&gt;generateValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PYTHON_VERSION&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.9.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s make sure each line is clear.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; is a name for our service to make it easy to find on the Render Dashboard. It’s interchangeable with the &lt;code&gt;name&lt;/code&gt; in &lt;code&gt;app.json&lt;/code&gt; and is also used to generate an .onrender.com URL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt; tells Render that we’d like to create a Web Service. When we’re deploying an API, the type may be either &lt;code&gt;web&lt;/code&gt; or &lt;code&gt;pserv&lt;/code&gt; (Private Service), depending on the use case.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;env&lt;/code&gt; specifies that we’d like to use Render’s native Python environment. This environment includes OS packages that common Python libraries need in addition to Python 3 specific environment variables.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repo&lt;/code&gt; specifies which repository we'll deploy from; update this if you're deploying your own fork of Saleor Core.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;buildCommand&lt;/code&gt; tells Render which commands or files to run to build the Django app. Read more about &lt;code&gt;render-build.sh&lt;/code&gt; below. Heroku buildpacks often handle build elements behind the scenes, but Render encourages being more transparent and provides more control. Use the &lt;code&gt;RUN&lt;/code&gt; commands from builds described in Dockerfiles as a guide for build commands to include in a blueprint.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startCommand&lt;/code&gt; tells Render which commands or files to run to start the Django app. This command is &lt;a href="https://github.com/saleor/saleor/blob/main/Procfile#L2" rel="noopener noreferrer"&gt;specified in the &lt;code&gt;Procfile&lt;/code&gt;&lt;/a&gt; for a Heroku build. We'll prepend the command with the &lt;a href="https://docs.djangoproject.com/en/3.0/ref/settings/#allowed-hosts" rel="noopener noreferrer"&gt;&lt;code&gt;ALLOWED_HOSTS&lt;/code&gt;&lt;/a&gt; environment variable. Referencing the standard &lt;code&gt;RENDER_EXTERNAL_HOSTNAME&lt;/code&gt; variable will add our API's hostname to the &lt;a href="https://github.com/saleor/saleor/blob/main/saleor/settings.py#L408" rel="noopener noreferrer"&gt;list of allowed hosts&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; is an environment variable that &lt;a href="https://docs.djangoproject.com/en/4.0/topics/settings/#envvar-DJANGO_SETTINGS_MODULE" rel="noopener noreferrer"&gt;Django requires&lt;/a&gt; to determine which settings to use.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEBUG&lt;/code&gt; is an environment variable that Django requires, and should never be set to &lt;code&gt;TRUE&lt;/code&gt; in &lt;a href="https://render.com/docs/deploy-django#go-production-ready" rel="noopener noreferrer"&gt;production deployments&lt;/a&gt;. We’ll use &lt;code&gt;DEBUG=True&lt;/code&gt; for our first demo deployment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEFAULT_FROM_EMAIL&lt;/code&gt; is an environment variable that Saleor uses to set a &lt;a href="https://docs.saleor.io/docs/2.11/developer/running-saleor/configuration#default_from_email" rel="noopener noreferrer"&gt;default email address for outgoing email&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL&lt;/code&gt; is an environment variable that Saleor uses to control whether new account registration should require email confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SECRET_KEY&lt;/code&gt; is an environment variable that Django requires to &lt;a href="https://docs.djangoproject.com/en/3.0/ref/settings/#secret-key" rel="noopener noreferrer"&gt;provide cryptographic signing&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PYTHON_VERSION&lt;/code&gt; is an environment variable used to &lt;a href="https://render.com/docs/python-version" rel="noopener noreferrer"&gt;customize the Python version&lt;/a&gt; for a project on Render if a version other than Render’s current default of 3.7 is required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Add a Database
&lt;/h3&gt;

&lt;p&gt;Now we need a PostgreSQL database for the app. Let's add that to our &lt;code&gt;render.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# …snip…&lt;/span&gt;
&lt;span class="na"&gt;databases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-db&lt;/span&gt;
    &lt;span class="na"&gt;ipAllowList&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# only allow connections from services in this Render account&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was simple, but how do we connect this database to the Web Service?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  - name: saleor
    type: web
    env: python
    repo: https://github.com/render-examples/saleor
    buildCommand: pip3 install -r requirements.txt &amp;amp;&amp;amp; python manage.py migrate --no-input
    startCommand: ALLOWED_HOSTS=".onrender.com" gunicorn --bind :$PORT --workers 4 --worker-class uvicorn.workers.UvicornWorker saleor.asgi:application
    envVars:
      - key: DJANGO_SETTINGS_MODULE
        value: saleor.settings
      - key: DEBUG
        value: True
      - key: NPM_CONFIG_PRODUCTION
        value: false
      - key: DEFAULT_FROM_EMAIL
        value: noreply@example.com
      - key: ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL
        value: False
      - key: SECRET_KEY
        generateValue: true
      - key: PYTHON_VERSION
        value: 3.9.0
      - key: DATABASE_URL
        fromDatabase:
          name: saleor-db
          property: connectionString

databases:
  - name: saleor-db
    ipAllowList: [] # only allow internal connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The highlighted lines create an environment variable whose value is the connection string for the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Redis
&lt;/h3&gt;

&lt;p&gt;This Django app uses Redis for &lt;a href="https://docs.djangoproject.com/en/4.0/topics/cache/#redis" rel="noopener noreferrer"&gt;caching&lt;/a&gt;, so let's add Redis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# …snip…&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-redis&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;ipAllowList&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# only allow connections from services in this Render account&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And similar to the database, we now need to tell the API how to access the Redis instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  - name: saleor
    type: web
      env: python
      repo: https://github.com/render-examples/saleor
      buildCommand: pip3 install -r requirements.txt &amp;amp;&amp;amp; python manage.py migrate --no-input
      startCommand: ALLOWED_HOSTS=".onrender.com" gunicorn --bind :$PORT --workers 4 --worker-class uvicorn.workers.UvicornWorker saleor.asgi:application
      envVars:
      - key: DJANGO_SETTINGS_MODULE
        value: saleor.settings
      - key: DEBUG
          value: True
      - key: NPM_CONFIG_PRODUCTION
          alue: false
      - key: DEFAULT_FROM_EMAIL
        value: noreply@example.com
      - key: ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL
        value: False
      - key: SECRET_KEY
        generateValue: true
      - key: DATABASE_URL
        fromDatabase:
          name: saleor-db
          property: connectionString
      - key: REDIS_URL
        fromService:
          type: redis
          name: saleor-redis
          property: connectionString
      - key: PYTHON_VERSION
        value: 3.9.0

databases:
    - name: saleor-db
      ipAllowList: [] # only allow internal connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The highlighted lines create an environment variable whose value is the connection string for the Redis instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Build Steps
&lt;/h3&gt;

&lt;p&gt;When Django apps are built and deployed, a few extra commands are commonly run to install dependencies and perform database migrations. Let’s create a &lt;code&gt;render-build.sh&lt;/code&gt; file in the root directory of our project. You can use this type of file to run any build steps your Django app requires.&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;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="c"&gt;# exit on error&lt;/span&gt;

pip3 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

python manage.py migrate &lt;span class="nt"&gt;--no-input&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value of &lt;code&gt;buildCommand&lt;/code&gt; in the &lt;code&gt;render.yaml&lt;/code&gt; should be the path to the &lt;code&gt;render-build.sh&lt;/code&gt; file. With a build script in place, we can deploy the API, database, and Redis instance to ensure everything works as expected so far. Deploy the repository containing your &lt;code&gt;render.yaml&lt;/code&gt; as a Blueprint on Render. When it's done, go to the &lt;code&gt;/graphql&lt;/code&gt;/ path of your API's .onrender.com URL to see the GraphQL API Playground.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Dashboard
&lt;/h3&gt;

&lt;p&gt;With the API for our Django app scaffolded, we’re ready to define a frontend service to consume it. The Saleor platform deployed on Heroku includes the &lt;a href="https://github.com/saleor/saleor-dashboard" rel="noopener noreferrer"&gt;saleor-dashboard&lt;/a&gt;, so we’ll define this in our Blueprint next. It is a single-page Node.js dashboard app, &lt;a href="https://github.com/saleor/saleor-dashboard/blob/main/app.json" rel="noopener noreferrer"&gt;defined in an &lt;code&gt;app.json&lt;/code&gt; file&lt;/a&gt; for Heroku deployment, and uses Heroku’s static buildpack.&lt;br&gt;
When we migrate &lt;code&gt;saleor-dashboard&lt;/code&gt; to Render, we’ll use Render’s &lt;a href="https://render.com/docs/static-sites" rel="noopener noreferrer"&gt;static environment&lt;/a&gt;. This component of our infrastructure will run for free, since Static Sites are always free on Render, and Render will serve it over a global CDN with fully managed TLS certificates. Let’s add to our &lt;code&gt;render.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# …snip…&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-dashboard&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/saleor/saleor-dashboard&lt;/span&gt;
    &lt;span class="na"&gt;buildCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install &amp;amp;&amp;amp; API_URI="$TEMP_API_URI/graphql/" npm run build&lt;/span&gt;
    &lt;span class="na"&gt;staticPublishPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./build/&lt;/span&gt;
    &lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TEMP_API_URI&lt;/span&gt;
        &lt;span class="na"&gt;fromService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
          &lt;span class="na"&gt;envVarKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RENDER_EXTERNAL_URL&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;APP_MOUNT_URI&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dashboard/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;STATIC_URL&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dashboard/&lt;/span&gt;
    &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rewrite&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dashboard/index.html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve added the commands for installing dependencies and building the app to add to our &lt;code&gt;buildCommand&lt;/code&gt;. The dashboard requires the &lt;code&gt;API_URI&lt;/code&gt; environment variable to build successfully. To avoid hard-coding, we'll derive a temporary environment variable from our API service using the &lt;code&gt;fromService&lt;/code&gt; attribute, and build our final &lt;code&gt;API_URI&lt;/code&gt; as part of our &lt;code&gt;buildCommand&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/saleor/saleor-dashboard/blob/main/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt; provides insight into the &lt;code&gt;staticPublishPath&lt;/code&gt; required for this project; we see a &lt;code&gt;COPY&lt;/code&gt; of the static contents to the /build/ directory, relative to the working directory. We also see the environment variables required to run the app in the Dockerfile, including the URI for our Saleor API and the URL where the dashboard should be mounted and served.&lt;/p&gt;

&lt;p&gt;To translate the configuration found in &lt;a href="https://github.com/saleor/saleor-dashboard/blob/main/static.json" rel="noopener noreferrer"&gt;static.json&lt;/a&gt; and the &lt;a href="https://github.com/saleor/saleor-dashboard/blob/main/nginx/default.conf" rel="noopener noreferrer"&gt;NGINX configuration file&lt;/a&gt; to Render, we add a &lt;code&gt;rewrite&lt;/code&gt; to route &lt;code&gt;/&lt;/code&gt; requests to &lt;code&gt;/dashboard/index.html&lt;/code&gt;. When we push these &lt;code&gt;render.yaml&lt;/code&gt; changes, Render will automatically create the new Static Site. &lt;/p&gt;

&lt;h3&gt;
  
  
  Add a React Storefront
&lt;/h3&gt;

&lt;p&gt;If I’m comparing this project to a painting, it's time we added something pretty. The final frontend piece of the Saleor platform, the storefront, is deployed to Heroku as part of the Saleor demo in its &lt;a href="https://github.com/saleor/saleor-storefront" rel="noopener noreferrer"&gt;original version&lt;/a&gt; of the storefront. This storefront is now deprecated because in 2021, the Saleor project added a new storefront service built with Next.js, TypeScript, and Tailwind CSS. The original &lt;code&gt;saleor-storefront&lt;/code&gt; can be migrated from Heroku similarly to the API and Dashboard projects, but Heroku deployment hasn’t been added to the new &lt;code&gt;react-storefront&lt;/code&gt; repository. The more modern react-storefront contains a &lt;a href="https://github.com/saleor/react-storefront/blob/main/Dockerfile.dev" rel="noopener noreferrer"&gt;development Dockerfile&lt;/a&gt; that provides clues to deployment requirements. In this guide, we’ll deploy the &lt;code&gt;react-storefront&lt;/code&gt; as the final component of our demo Saleor instance.&lt;/p&gt;

&lt;p&gt;Note that we can deploy the react-storefront on Render as-is using the Dockerfile. However, since Render provides an HTTP proxy for all Web Services, using nginx as defined in the Dockerfile is redundant. Instead, we can define our service more simply and use Render’s native NodeJS environment. We’ll use &lt;a href="https://github.com/saleor/saleor-platform/blob/main/docker-compose.yml#L35-L53" rel="noopener noreferrer"&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/a&gt;, the Dockerfile, and the &lt;a href="https://github.com/saleor/react-storefront/blob/main/package.json" rel="noopener noreferrer"&gt;&lt;code&gt;package.json&lt;/code&gt; file&lt;/a&gt; as starting points. Let’s add a storefront to our &lt;code&gt;render.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# …snip…&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-storefront&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/saleor/react-storefront&lt;/span&gt;
    &lt;span class="na"&gt;buildCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;pnpm install&lt;/span&gt;
      &lt;span class="s"&gt;&amp;amp;&amp;amp; NEXT_PUBLIC_API_URI=$TEMP_NEXT_PUBLIC_API_URL/graphql/ pnpm run build&lt;/span&gt;
      &lt;span class="s"&gt;&amp;amp;&amp;amp; pnpm run postbuild&lt;/span&gt;
    &lt;span class="na"&gt;startCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NEXT_PUBLIC_API_URI=$TEMP_NEXT_PUBLIC_API_URL/graphql/ pnpm start&lt;/span&gt;
    &lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TEMP_NEXT_PUBLIC_API_URL&lt;/span&gt;
        &lt;span class="na"&gt;fromService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
          &lt;span class="na"&gt;envVarKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RENDER_EXTERNAL_URL&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NEXT_PUBLIC_IMAGE_CONVERSION_FORMATS&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;image/avif,image/webp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build command is &lt;a href="https://github.com/saleor/react-storefront/blob/main/Dockerfile.dev#L13" rel="noopener noreferrer"&gt;defined in the Dockerfile&lt;/a&gt;. The &lt;code&gt;docker-compose.yml&lt;/code&gt; and the Dockerfile only define &lt;code&gt;RUN&lt;/code&gt; commands for a development instance, but we’ll run the &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt;, and &lt;code&gt;postbuild&lt;/code&gt; scripts defined in the &lt;a href="https://github.com/saleor/react-storefront/blob/main/package.json#L8-L19" rel="noopener noreferrer"&gt;package.json&lt;/a&gt; using &lt;a href="https://pnpm.io/cli/run" rel="noopener noreferrer"&gt;pnpm run&lt;/a&gt; commands.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;environment:&lt;/code&gt; clause of the &lt;a href="https://github.com/saleor/saleor-platform/blob/main/docker-compose.yml#L51-L53" rel="noopener noreferrer"&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/a&gt; provides clues to the minimum environment variables required for react-storefront to run. &lt;code&gt;NEXT_PUBLIC_API_URI&lt;/code&gt; is required to define the location of the GraphQL API, and we’ve set this to our Saleor API instance’s public URI with &lt;code&gt;/graphql/&lt;/code&gt; added per &lt;a href="https://github.com/saleor/react-storefront/blob/main/docs/configuration.md" rel="noopener noreferrer"&gt;react-storefront docs&lt;/a&gt;. In our testing, the only other environment variable required for successful deployment was &lt;code&gt;NEXT_PUBLIC_IMAGE_CONVERSION_FORMATS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;🎇 Get excited! 🎆 With a Dashboard and Storefront added to our Blueprint, we are ready to deploy our demo instance of Saleor. Pushing these changes will automatically deploy the storefront. Try it out if you're following along in your own repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Productionalizing a Django App on Render
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, while the deploy-to-Heroku resources that Saleor provides are suitable for a demo application, we would need to modify them to deploy a production-grade instance of the Saleor platform to Heroku. Instead, we’ll walk through productionalizing our Django e-commerce app on Render. We’ll now frame and hang our painting – or maybe you prefer imagining the addition of a majestic mountain or waterfall. Either way, now that we’re beyond Heroku, keep reading to tap some handy Render features, add power, and DRY it up. We'll finish with a hardened Saleor instance impervious to "happy accidents."&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Secret File
&lt;/h3&gt;

&lt;p&gt;A production-grade instance of Saleor needs an &lt;a href="https://docs.saleor.io/docs/3.x/developer/running-saleor/configuration#rsa_private_key" rel="noopener noreferrer"&gt;RSA private key&lt;/a&gt;. Render’s Secret Files can help us securely store this key. To begin, we need to generate a key in the specified PEM format. We can do this locally: &lt;code&gt;ssh-keygen -t rsa -b 4096 -m PEM&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When generating the key, we should give it a unique name like &lt;code&gt;saleor-key&lt;/code&gt; to avoid overwriting any existing SSH keys. No passphrase is required. With the newly-generated private key copied to the clipboard, we can navigate to our Saleor Core (API) Web Service in the Render Dashboard and create a Secret File. In &lt;strong&gt;Environment&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secret Files&lt;/strong&gt;, we can add a Secret File by providing the filename as the key and pasting the private key into Contents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdb6u1lynf8hh9yke00zg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdb6u1lynf8hh9yke00zg.png" alt="A screenshot of the secret file dialog in The Render Dashboard, found in Environment &amp;gt; Secret Files" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our private key is accessible at the absolute path &lt;code&gt;/etc/secrets/saleor-key&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Background Worker
&lt;/h3&gt;

&lt;p&gt;In processing web requests to your Django app, it’s often useful to offload tasks to an asynchronous background worker. Our Heroku instance of Saleor used a &lt;code&gt;celeryworker&lt;/code&gt; dyno, and we can provision a similar celery worker on Render. Let’s add a celery worker to our &lt;code&gt;render.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# …snip…&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-worker&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
  &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/render-examples/saleor&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;
  &lt;span class="na"&gt;buildCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./render-build.sh&lt;/span&gt;
  &lt;span class="na"&gt;startCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;celery -A saleor --app=saleor.celeryconf:app worker --loglevel=info -E&lt;/span&gt;
  &lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL&lt;/span&gt;
      &lt;span class="na"&gt;fromDatabase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-db&lt;/span&gt;
        &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connectionString&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL&lt;/span&gt;
      &lt;span class="na"&gt;fromService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-redis&lt;/span&gt;
        &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connectionString&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PYTHON_VERSION&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.9.0&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor.settings&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NPM_CONFIG_PRODUCTION&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;noreply@example.com&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;False&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;
      &lt;span class="na"&gt;generateValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;plan: standard&lt;/code&gt; attribute is set because the celery worker requires more memory than what's availabe in free plans. Also, adding the same environment variables that our API process uses creates some duplication in our &lt;code&gt;render.yaml&lt;/code&gt;, which we’ll also address in a later section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Cron Job
&lt;/h3&gt;

&lt;p&gt;Saleor runs a command periodically to update currency exchange rates for sellers doing business internationally. Saleor’s demo app uses the Heroku Scheduler to run this daily. We can use a Cron Job to keep the exchange rates up to date. Let’s create a Render Cron Job to run the &lt;code&gt;python manage.py update_exchange_rates --all&lt;/code&gt; command at 1 AM every day.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# …snip…&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cron&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exchange-rates&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
  &lt;span class="na"&gt;startCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python manage.py update_exchange_rates --all&lt;/span&gt;
  &lt;span class="na"&gt;buildCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./render-build.sh&lt;/span&gt;
  &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/render-examples/saleor&lt;/span&gt;
  &lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor.settings&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NPM_CONFIG_PRODUCTION&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;noreply@example.com&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;False&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;
      &lt;span class="na"&gt;generateValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PYTHON_VERSION&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.9.0&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL&lt;/span&gt;
      &lt;span class="na"&gt;fromDatabase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-db&lt;/span&gt;
        &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connectionString&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL&lt;/span&gt;
      &lt;span class="na"&gt;fromService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-redis&lt;/span&gt;
        &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connectionString&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OPENEXCHANGERATES_API_KEY&lt;/span&gt;
      &lt;span class="na"&gt;sync&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may notice that the definition of the Cron Job looks very similar to that of the Web Service for our API. A Render Cron Job is not tied to a parent app or Web Service like Heroku’s Scheduler add-on. It is defined on its own. A Cron Job builds and runs code from any repository on a specified schedule, whereas the Heroku Scheduler can only execute a command using an existing deployed app. In this case, similarly to the background worker, the Cron Job needs to use many of the same environment variables as its fellow services, and we’ll address that duplication below.&lt;/p&gt;

&lt;p&gt;A few other things to note about this Cron Job definition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;schedule&lt;/code&gt; property; &lt;code&gt;schedule&lt;/code&gt; is a cron expression that defines when to run the job – in this case, we’re running it every day at 1 am UTC.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;OPENEXCHANGERATES_API_KEY&lt;/code&gt; is required to get exchange rates from the Open Exchange Rates API. You’ll need to sign up for a free Open Exchange Rates account to enable the integration. The value for your &lt;code&gt;OPENEXCHANGERATES_API_KEY&lt;/code&gt; should be your Open Exchange Rates app ID. Read more in the &lt;a href="https://docs.saleor.io/docs/2.11/developer/running-saleor/background-tasks#fetching-currency-conversion-rates" rel="noopener noreferrer"&gt;Saleor documentation&lt;/a&gt;. Because this value should probably be kept secret, you should define it with the placeholder &lt;code&gt;sync: false&lt;/code&gt; so that you can add teh value securely in the dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that, our Cron Job is in place to keep prices in local currencies updated, and we are fleshing out a promising landscape.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update Build Script
&lt;/h3&gt;

&lt;p&gt;Now, let’s update our build script to support the secret key and services we’ve added to get our instance of Saleor ready for 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="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="c"&gt;# exit on error&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RSA_PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/secrets/saleor-key&lt;span class="si"&gt;)&lt;/span&gt;

pip3 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RENDER_SERVICE_TYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;python manage.py migrate &lt;span class="nt"&gt;--no-input&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we set the &lt;code&gt;RSA_PRIVATE_KEY&lt;/code&gt; environment variable to make it available in the build context.&lt;/p&gt;

&lt;p&gt;We also add logic that checks the &lt;a href="https://render.com/docs/environment-variables" rel="noopener noreferrer"&gt;standard Render environment variable&lt;/a&gt; &lt;code&gt;RENDER_SERVICE_TYPE&lt;/code&gt; to ensure that database migration only runs for the Web Service. This change helps us to avoid a race condition which would prevent one of the services from deploying successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Runtime Script
&lt;/h3&gt;

&lt;p&gt;Next, we’ll replace our original &lt;code&gt;startCommand:&lt;/code&gt; in the Python services with a start script to accommodate our more complex deployment. We’ll use a subcommand to address the cases of starting the celery worker and cron job.&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;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="c"&gt;# exit on error&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RSA_PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/secrets/saleor-key&lt;span class="si"&gt;)&lt;/span&gt;


&lt;span class="nv"&gt;subcommand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;$subcommand&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;server&lt;span class="p"&gt;)&lt;/span&gt;
    gunicorn &lt;span class="nt"&gt;--bind&lt;/span&gt; :&lt;span class="nv"&gt;$PORT&lt;/span&gt; &lt;span class="nt"&gt;--workers&lt;/span&gt; 4 &lt;span class="nt"&gt;--worker-class&lt;/span&gt; uvicorn.workers.UvicornWorker saleor.asgi:application
    &lt;span class="p"&gt;;;&lt;/span&gt;
  worker&lt;span class="p"&gt;)&lt;/span&gt;
    celery &lt;span class="nt"&gt;-A&lt;/span&gt; saleor &lt;span class="nt"&gt;--app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;saleor.celeryconf:app worker &lt;span class="nt"&gt;--loglevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nt"&gt;-E&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
  cron&lt;span class="p"&gt;)&lt;/span&gt;
    python3 manage.py update_exchange_rates &lt;span class="nt"&gt;--all&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Unknown subcommand"&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We explicitly set the &lt;code&gt;RSA_PRIVATE_KEY&lt;/code&gt; environment variable since it is also required in Saleor’s run context. We also need a subcommand to tell the script to start either the web server or the celery worker. To finish this step, we’ll update the &lt;code&gt;startCommand&lt;/code&gt; value for our Python services in &lt;code&gt;render.yaml&lt;/code&gt; to reference the new start script; for our API, we’ll use &lt;code&gt;./render-start.sh server&lt;/code&gt;, for our worker, &lt;code&gt;./render-start.sh worker&lt;/code&gt;, and for our cron job, &lt;code&gt;./render-start.sh cron&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add CloudAMQP
&lt;/h3&gt;

&lt;p&gt;The Heroku deployment of Saleor uses CloudAMQP as an add-on for message brokering. To use CloudAMQP with our Render deployment, we first create a free &lt;a href="https://www.cloudamqp.com/" rel="noopener noreferrer"&gt;CloudAMQP&lt;/a&gt; instance and note the URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuw5g8vkgypzgofqjf69j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuw5g8vkgypzgofqjf69j.png" alt="A screenshot of the CloudAMQP dashboard, indicating the URL that should be used as an environment variable." width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we set the value of the &lt;code&gt;CLOUDAMQP_URL&lt;/code&gt; environment variable to the URL provided by the CloudAMQP dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# …snip…&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;celery-worker&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
  &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/render-examples/saleor&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;
  &lt;span class="na"&gt;buildCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./render-build.sh&lt;/span&gt;
  &lt;span class="na"&gt;startCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./render-start.sh worker&lt;/span&gt;
  &lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# …snip…&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CLOUDAMQP_URL&lt;/span&gt;
      &lt;span class="na"&gt;sync&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DRY It Up
&lt;/h3&gt;

&lt;p&gt;As I mentioned previously, there’s some duplication in the &lt;code&gt;render.yaml&lt;/code&gt; between the Python services. On Render, we can add a happy little cloud to our environment – an &lt;a href="https://render.com/docs/configure-environment-variables#2-environment-groups" rel="noopener noreferrer"&gt;Environment Group&lt;/a&gt; – to reduce some of this duplication. An Environment Group is a set of environment variables maintained in one place and shared with multiple services. Let’s move the &lt;code&gt;SECRET_KEY&lt;/code&gt;, &lt;code&gt;PYTHON_VERSION&lt;/code&gt;, &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt;, &lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;NPM_CONFIG_PRODUCTION&lt;/code&gt;, &lt;code&gt;DEFAULT_FROM_EMAIL&lt;/code&gt;, and &lt;code&gt;ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL&lt;/code&gt; environment variables to an Environment Group. We're also going to add &lt;code&gt;ALLOWED_CLIENT_HOSTS&lt;/code&gt;, which is required when we flip &lt;code&gt;DEBUG&lt;/code&gt; from &lt;code&gt;True&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt; in the next section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# …snip…&lt;/span&gt;
&lt;span class="na"&gt;envVarGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor-settings&lt;/span&gt;
    &lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PYTHON_VERSION&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.9.0&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;saleor.settings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NPM_CONFIG_PRODUCTION&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;noreply@example.com&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;False&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;
        &lt;span class="na"&gt;generateValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALLOWED_CLIENT_HOSTS&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.onrender.com&lt;/span&gt; &lt;span class="c1"&gt;# If using custom domains, change this to a comma-separated list of your storefront and dashboard hostnames&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can remove the definition of those environment variables from the Python services and add the following property to their envVars objects:&lt;br&gt;
&lt;code&gt;- fromGroup: saleor-settings&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To make the most of our environment group, note that we can also add our &lt;code&gt;saleor-key&lt;/code&gt; secret file to our environment group so it’s accessible from each service’s environment. We need to do this in the Dashboard after the Environment Group creation is complete.&lt;/p&gt;
&lt;h3&gt;
  
  
  Help! (a Helper File for Derived Variables)
&lt;/h3&gt;

&lt;p&gt;Our repetition of the &lt;code&gt;RSA_PRIVATE_KEY&lt;/code&gt; export is another source of duplication in our deployment code. In addition to &lt;code&gt;RSA_PRIVATE_KEY&lt;/code&gt;, Django apps and Saleor in particular also require a few other security-focused environment variables to run in production: the aforementioned &lt;a href="https://docs.djangoproject.com/en/3.0/ref/settings/#allowed-hosts" rel="noopener noreferrer"&gt;&lt;code&gt;ALLOWED_HOSTS&lt;/code&gt;&lt;/a&gt;, used to thwart HTTP Host header attacks, and &lt;a href="https://docs.saleor.io/docs/3.x/developer/running-saleor/configuration#allowed_client_hosts" rel="noopener noreferrer"&gt;&lt;code&gt;ALLOWED_CLIENT_HOSTS&lt;/code&gt;&lt;/a&gt;, used to restrict API access to the target clients you define. To continue our DRYing kick and make these parameters centrally available, we can use a helper script and source it in both our &lt;code&gt;render-build.sh&lt;/code&gt; and &lt;code&gt;render-start.sh&lt;/code&gt; scripts. Let’s create a helper script.&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;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="c"&gt;# exit on error&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RSA_PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/secrets/saleor-key&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$RENDER_EXTERNAL_HOSTNAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can replace the &lt;code&gt;export&lt;/code&gt; lines in both &lt;code&gt;render-build.sh&lt;/code&gt; and &lt;code&gt;render-start.sh&lt;/code&gt; with &lt;code&gt;source helpers/variables.sh&lt;/code&gt; and add any other programmatically derived environment variables to &lt;code&gt;helpers/variables.sh&lt;/code&gt; in future. &lt;/p&gt;

&lt;p&gt;To complete our de-duplication effort, we should add our &lt;code&gt;RSA_PRIVATE_KEY&lt;/code&gt; to the Environment Group we created so it's shared among our Environment Group. We can do this in the Dashboard in &lt;strong&gt;Env Groups&lt;/strong&gt; &amp;gt; &lt;strong&gt;saleor&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secret Files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Drumroll, please: our happy little services, scripts, and tools now depict a complete landscape. With our production-ready environment variables now available in both the build and start contexts for all of our services, we are ready to flip the switch on our Saleor services in &lt;code&gt;render.yaml&lt;/code&gt; to &lt;code&gt;DEBUG=False&lt;/code&gt;, sync our Blueprint, and deploy!!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Freaqiy2j6dv1jqwvw992.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Freaqiy2j6dv1jqwvw992.png" alt="A screenshot of an item detail page in the Saleor Demo, with a Saleor-branded shirt for sale." width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Bob Ross warned us, “If you do too much, it’s going to lose its effectiveness." At Render, we do a lot so that &lt;em&gt;you&lt;/em&gt; can do just enough to build great things. If you're thinking about migrating your Django project to Render, you’re considering how to translate each component to the new environment, and the amount of work required. This post aims to provide good coverage of those tasks, but there is always room for growth : ) We’ll keep building, and in the meantime, we (and your fellow developers) would love to see &lt;em&gt;your&lt;/em&gt; solutions for the following next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You may &lt;a href="https://render.com/docs/deploy-rabbitmq" rel="noopener noreferrer"&gt;deploy RabbitMQ&lt;/a&gt; as a Private Service on Render instead of using an external RabbitMQ-as-a-service provider. A version of this project with a RabbitMQ instance added to the Blueprint would remove the need to sign up and pay the bill for another service. If you extend the project as such, please post in &lt;a href="https://community.render.com/c/render-examples/12" rel="noopener noreferrer"&gt;Render-Examples on the Render Community Site&lt;/a&gt; to share your work.&lt;/li&gt;
&lt;li&gt;At Render, when we think about what to paint in our next masterpiece, we always start with user feedback. Unsurprisingly, object storage is one of our most common &lt;a href="https://feedback.render.com/features/p/cloud-object-storage" rel="noopener noreferrer"&gt;product requests&lt;/a&gt; (upvote plz!) While we work on that, you can &lt;a href="https://docs.saleor.io/docs/3.x/developer/running-saleor/s3" rel="noopener noreferrer"&gt;use S3&lt;/a&gt; to store and serve static files for your Saleor instance.&lt;/li&gt;
&lt;li&gt;If you’re migrating an existing Django application from Heroku to Render, you likely have data in a database that you’ll need to export and import. We heard from readers and Render users that a detailed guide for these steps would provide critical context to support real migrations. Given their importance, we will dedicate a full blog post to those steps. Look out for a language and framework-agnostic guide to the data export and migration process in an upcoming post. Feel free to share about your own experience with data migration.&lt;/li&gt;
&lt;li&gt;This post touches on translating &lt;code&gt;app.json&lt;/code&gt; and &lt;code&gt;docker-compose.yml&lt;/code&gt; files to &lt;code&gt;render.yaml&lt;/code&gt;, but there’s more to say on the subject. We dream of &lt;a href="https://render.canny.io/features/p/support-docker-compose" rel="noopener noreferrer"&gt;automating this&lt;/a&gt; for our users. In the meantime, our community would love to read about your translation tricks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can’t wait to hear from you about whether this guide helped you migrate your application. If you’ve considered moving a Django app over from Heroku, or if you use Saleor or another open source e-commerce platform on Render, we’d love to see what you’ve built out. Post on your channel of choice, reach out on our &lt;a href="https://community.render.com/" rel="noopener noreferrer"&gt;community site&lt;/a&gt;, or &lt;a href="https://twitter.com/dnilas0r" rel="noopener noreferrer"&gt;hop into my DMs&lt;/a&gt; with your big ideas, or the unvarnished truth. We’re joined at the hip with our developer community and trust me, we want to hear from you!&lt;/p&gt;




&lt;p&gt;Rosalind Benoit leads Developer Marketing at Render. She enjoys cycling, rock climbing, and Haruki Murakami novels. You can contact Rosalind at &lt;a href="https://twitter.com/dnilas0r" rel="noopener noreferrer"&gt;@dnilas0r&lt;/a&gt; or &lt;a href="mailto:rosalind@render.com"&gt;rosalind@render.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>django</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Render, Remix, and Strapi: Let's Build a Productivity Tips App</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Mon, 04 Apr 2022 19:55:43 +0000</pubDate>
      <link>https://forem.com/render/render-remix-and-strapi-lets-build-a-productivity-tips-app-2i3m</link>
      <guid>https://forem.com/render/render-remix-and-strapi-lets-build-a-productivity-tips-app-2i3m</guid>
      <description>&lt;h2&gt;
  
  
  What are Strapi and Remix?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://strapi.io/" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt; is "the leading open-source headless CMS. It's 100% JavaScript, fully customizable, and developer-first." Think of it as an admin GUI to manage your content. That content could be blog posts or product images or videos. And Strapi automatically generates both REST and GraphQL APIs for the content. Render has been an especially popular place to deploy Strapi because it provides one of the quickest paths to get Strapi running in a production environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://remix.run" rel="noopener noreferrer"&gt;Remix&lt;/a&gt; is "a full stack web framework that lets you focus on the user interface...to deliver a fast, slick, and resilient user experience." It comes from some well-known developers from the React ecosystem and has been getting more and more attention since its open source debut at the end of 2021.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Strapi and Remix Together?
&lt;/h2&gt;

&lt;p&gt;Strapi and Remix are both popular and growing open source JavaScript projects. In fact, they topped the list of &lt;a href="https://risingstars.js.org/2021/en#section-nodejs-framework" rel="noopener noreferrer"&gt;Node.js Framework Rising Stars in 2021&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze99c2hkobstuch1wtyf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze99c2hkobstuch1wtyf.png" alt="A screenshot showing Strapi and Remix (among others) as 2021 Node.js Rising Stars." width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But more importantly, these two tools complement each other well. Strapi serves as an ideal back end for a Remix front-end. Strapi gives us many things we, as developers, would otherwise have to build ourselves: a clean abstraction on top of a database, a data admin GUI, and an API for the front-end to use. Remix builds upon the vast React ecosystem, letting you quickly create a front-end and scale it up as needed. It addresses a lot of the time-consuming complexity that has become a regular part of a React developer’s work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Productivity Tips App
&lt;/h2&gt;

&lt;p&gt;The inspiration for this app came from Render Engineer &lt;a href="https://twitter.com/joshishantanu4" rel="noopener noreferrer"&gt;Shantanu Joshi&lt;/a&gt;: "Over time, everyone develops a Swiss army knife of tips, tricks, and hacks to boost productivity. At Render, we created a #productivity-tips Slack channel for anyone to share their best productivity boosters with everyone on the team."&lt;/p&gt;

&lt;p&gt;But Slack doesn't provide a great way to discover tips that others have shared in the channel. You can scroll back through the conversation history, but wouldn't it be nice if there was a web page with a simple list? Using Strapi and Remix, we're going to make a web app to catalog all of these tips and share them with others.&lt;/p&gt;

&lt;p&gt;Here's a visual overview of what we'll build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1p82lce3d2gm48wjgpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1p82lce3d2gm48wjgpr.png" alt="A diagram showing the high-level architecture of our Productivity Tips app." width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here are the steps we’ll take to get there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up your development machine&lt;/li&gt;
&lt;li&gt;Configure and get familiar with Strapi&lt;/li&gt;
&lt;li&gt;Build the front-end with Remix&lt;/li&gt;
&lt;li&gt;Deploy everything to Render&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set Up Your Development Machine
&lt;/h3&gt;

&lt;p&gt;You’ll need &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;, &lt;a href="https://classic.yarnpkg.com/lang/en/docs/install/" rel="noopener noreferrer"&gt;Yarn&lt;/a&gt;, &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;git&lt;/a&gt;, a &lt;a href="https://render.com/register" rel="noopener noreferrer"&gt;Render account&lt;/a&gt;, and a &lt;a href="https://cloudinary.com/users/register/free" rel="noopener noreferrer"&gt;Cloudinary account&lt;/a&gt;. If you don't have them already, install the tools and create the accounts linked above (everything's free).&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure and Get Familiar with Strapi
&lt;/h3&gt;

&lt;p&gt;We'll start with an &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi" rel="noopener noreferrer"&gt;example repository&lt;/a&gt; that contains only a few changes from the &lt;a href="https://docs.strapi.io/developer-docs/latest/getting-started/quick-start.html" rel="noopener noreferrer"&gt;default Strapi project scaffolding&lt;/a&gt;. Following the instructions in the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi#set-up-strapi" rel="noopener noreferrer"&gt;Set up Strapi section&lt;/a&gt; of the example repository's README, run Strapi locally to get comfortable with Strapi and understand the minor code changes below.&lt;/p&gt;

&lt;p&gt;Here are the changes I made to the Strapi scaffolding. The links will take you to the code for each change.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/config/env/production/database.js" rel="noopener noreferrer"&gt;PostgreSQL in production&lt;/a&gt; and &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/config/database.js" rel="noopener noreferrer"&gt;SQLite for local development&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/config/env/production/plugins.js" rel="noopener noreferrer"&gt;Cloudinary for media storage, resizing, and serving in production&lt;/a&gt;, and use the filesystem during local development. No code changes were necessary to use the local filesystem because that is the default built into Strapi.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/config/env/production/server.js" rel="noopener noreferrer"&gt;Tell Strapi what its public URL is&lt;/a&gt; when deployed in production.&lt;/li&gt;
&lt;li&gt;Create the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/tree/main/src/api" rel="noopener noreferrer"&gt;Tip content type&lt;/a&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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feq7z5k1ukxezbzw06g8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feq7z5k1ukxezbzw06g8d.png" alt="A screenshot of the Strapi Admin GUI Content Type builder." width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;em&gt;Strapi requires you to define Content Types–effectively, the data schema–in code. When you deploy your Strapi service to production, those Content Types are deployed along with the Strapi application code. However, content for each Content Type only lives within the environment in which it's created. You can read more about this in the &lt;a href="https://docs.strapi.io/user-docs/latest/content-types-builder/creating-new-content-type.html" rel="noopener noreferrer"&gt;Strapi documentation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In our example, we've created a &lt;code&gt;Tip&lt;/code&gt; Content Type in development, and we will deploy that to production. However, any content we create in development won't be deployed to production, and any content we create in production won't be available in development.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you've completed the instructions to set up Strapi, including adding some sample Tip content in the Strapi Admin GUI, we'll move on to building the Remix service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Front-End with Remix
&lt;/h3&gt;

&lt;p&gt;Strapi is mostly plug-and-play, requiring you to write code only when you want to customize its default functionality. On the other hand, building a Remix app requires us to write more code. We can start with a Remix scaffolding, but we still need to create the web page views and API requests to to show our productivity tip data. I've broken the process into five steps to guide you through this code. Each step is a separate git branch in the example repository, and the README for each branch explains the changes in that branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4564n5pe48letk8jkj4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4564n5pe48letk8jkj4l.png" alt="A screenshot of the branches labeled step one through five in the example Remix repository." width="638" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can either start with the &lt;code&gt;step-1&lt;/code&gt; branch and work your way through the changes in each branch or skip to &lt;code&gt;step-5&lt;/code&gt;, which contains the final example code that we'll deploy to Render. To start at &lt;code&gt;step-1&lt;/code&gt; and work through the branches, follow the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-remix#readme" rel="noopener noreferrer"&gt;README instructions in each branch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To skip to &lt;code&gt;step-5&lt;/code&gt;, fork the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-remix" rel="noopener noreferrer"&gt;example repository&lt;/a&gt;, &lt;code&gt;git clone&lt;/code&gt; it, switch to the &lt;code&gt;step-5&lt;/code&gt; branch with &lt;code&gt;git checkout step-5&lt;/code&gt;, and then install the necessary dependencies with &lt;code&gt;npm install&lt;/code&gt;. Now we can connect the Remix app to Strapi's REST API. To do that,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the &lt;code&gt;.env.example&lt;/code&gt; file to &lt;code&gt;.env&lt;/code&gt;. This file provides our Remix app with two variables it needs to access the Strapi API: &lt;code&gt;STRAPI_URL_BASE&lt;/code&gt; and &lt;code&gt;STRAPI_API_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We need to ask Strapi to generate an API token for use in development. In the Strapi Admin GUI, go to Settings --&amp;gt; API Tokens --&amp;gt; Create new API Token. Name it something like &lt;code&gt;Remix Dev&lt;/code&gt; and leave it as &lt;code&gt;Read-only&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copy the token and paste it into &lt;code&gt;.env&lt;/code&gt; as the value for &lt;code&gt;STRAPI_API_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Start or restart the Remix dev server by running &lt;code&gt;npm run dev&lt;/code&gt; in the Remix app directory, and also make sure the Strapi server is running by running &lt;code&gt;yarn develop&lt;/code&gt; in the Strapi app directory.&lt;/li&gt;
&lt;li&gt;If you open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in a browser, you should see your Productivity Tips app containing any Tip data you've created in the Strapi Admin GUI!&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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnxhag6te394017najeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnxhag6te394017najeu.png" alt="A screenshot of the Remix Productivity Tips app." width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice job! Local development is done! Time to deploy our code to Render.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Everything to Render
&lt;/h3&gt;

&lt;p&gt;We will use &lt;a href="https://render.com/docs/infrastructure-as-code" rel="noopener noreferrer"&gt;Render Blueprints&lt;/a&gt; to make deploying and connecting these two services faster and less error-prone. A Render Blueprint is defined using a single &lt;code&gt;render.yaml&lt;/code&gt; file. &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/render.yaml" rel="noopener noreferrer"&gt;That file&lt;/a&gt; lives in the Strapi repository for these two applications.&lt;/p&gt;

&lt;p&gt;Follow the instructions in the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi#deploy-to-production-on-render" rel="noopener noreferrer"&gt;Deploy to Production on Render&lt;/a&gt; section of the Strapi repository's README. You'll make minor changes to the &lt;code&gt;render.yaml&lt;/code&gt; file to make sure it deploys the code from your copies of the example repositories. Then you’ll perform the initial deploy, generate a production Strapi API token, and update the deployed production Remix service with that API token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpo0oj4vfguyu2mm3iede.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpo0oj4vfguyu2mm3iede.png" alt="A screenshot showing a successfully deployed Blueprint for the Productivity Tips app." width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have your very own Productivity Tips app with which you and your coworkers can collect and share all your best productivity tips! Start adding tips using the Strapi Admin GUI, and then share the Remix service's URL with your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customize
&lt;/h2&gt;

&lt;p&gt;I encourage you to extend the Remix and Strapi applications to customize them for your needs! Here are some ideas for inspiration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a search bar so your coworkers can search for tips about a specific topic or tool. Algolia’s &lt;a href="https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/react/" rel="noopener noreferrer"&gt;React InstantSearch&lt;/a&gt; library might be useful for this.&lt;/li&gt;
&lt;li&gt;Add a login page to restrict use of the Remix app to only approved users. Use OAuth with Google or GitHub login so you don’t have to write code to manage user accounts and password resets. Okta has guides for adding &lt;a href="https://developer.okta.com/docs/guides/add-an-external-idp/google/main/" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, &lt;a href="https://developer.okta.com/docs/guides/social-login/github/main/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and many other login providers to a web application. If you don’t want to use a 3rd party login provider, you could &lt;a href="https://strapi.io/blog/a-beginners-guide-to-authentication-and-authorization-in-strapi" rel="noopener noreferrer"&gt;store and manage users directly in Strapi&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Make the screenshot thumbnails for each productivity tip clickable and zoomable. Strapi’s Cloudinary plugin that we're using automatically creates small, medium, and large versions of images you attach to a Tip, but I didn’t have time to add more than the thumbnail view to the Remix app.&lt;/li&gt;
&lt;li&gt;Replace &lt;a href="https://picocss.com" rel="noopener noreferrer"&gt;Pico.css&lt;/a&gt; with a different &lt;a href="https://github.com/troxler/awesome-css-frameworks#general-purpose" rel="noopener noreferrer"&gt;minimal CSS theme&lt;/a&gt;, or even add your own styling to match your company or team’s brand colors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://twitter.com/crc" rel="noopener noreferrer"&gt;Let me know&lt;/a&gt; if you implement any of these! If they would be useful to others, I’ll consider merging them into the example repositories!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Render Redis</title>
      <dc:creator>Kelley Rivoire</dc:creator>
      <pubDate>Wed, 23 Mar 2022 20:50:58 +0000</pubDate>
      <link>https://forem.com/render/render-redis-4f5o</link>
      <guid>https://forem.com/render/render-redis-4f5o</guid>
      <description>&lt;p&gt;Today, we're announcing the General Availability of Render Redis. You can now set up a Redis instance in just a few clicks and let Render handle the heavy lifting to operate it reliably and securely. Redis, a widely-used, open source, in-memory key-value store, supports high throughput and low latency reads and writes. This makes it a great fit for uses like caching and queueing.&lt;/p&gt;

&lt;p&gt;To get started, &lt;a href="https://dashboard.render.com/new/redis" rel="noopener noreferrer"&gt;create a Redis instance&lt;/a&gt; directly from your Render account, or read more in our &lt;a href="https://render.com/docs/redis" rel="noopener noreferrer"&gt;docs&lt;/a&gt;. Try our Redis for free, and when you're ready, select a paid plan starting at $10/month to get more RAM and data persistence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fktv055j2zmyyndoigx04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fktv055j2zmyyndoigx04.png" alt="A screenshot of the Render Dashboard screen used to create a new managed Redis instance" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we're offering Redis
&lt;/h2&gt;

&lt;p&gt;Prior to this release, if you wanted to run Redis, you needed to set up, configure, and operate Redis yourself using a Render Private Service, taking engineering time away from your core business. Because of this, Redis has been the &lt;a href="https://feedback.render.com/features/p/managed-redis" rel="noopener noreferrer"&gt;number one upvoted product requested&lt;/a&gt; by Render users. With our Redis service, getting a running instance is simple, and from there, Render takes care of reliability, security, and operations. Render’s Redis also offers our most-requested features out-of-the-box, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A URL for connecting to Redis from outside Render&lt;/li&gt;
&lt;li&gt;Access to the Redis CLI&lt;/li&gt;
&lt;li&gt;Blueprint support so Redis works seamlessly with Render’s &lt;a href="https://render.com/docs/infrastructure-as-code" rel="noopener noreferrer"&gt;infrastructure-as-code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A Redis metrics dashboard showing memory usage, CPU load, and active connections&lt;/li&gt;
&lt;li&gt;Configurable eviction policy to support caching, queueing, and other use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is just the beginning - we'll continue to add features and make improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Redis enables for Render users
&lt;/h2&gt;

&lt;p&gt;Already, through our Early Access period, hundreds of Render users have found Redis a convenient way to take on a variety of tasks, from caching user sessions, full pages, and template fragments to running background jobs like parsing large data files or sending e-mails. Early Access Redis users have particularly appreciated the easy setup — a new Redis instance can be created in a couple of clicks, and is instantly ready for internal and external connections — while maintaining the flexibility they needed.&lt;/p&gt;

&lt;p&gt;"I was very pleasantly surprised by how easy the process was. I followed the instructions to deploy a Redis instance and connect it to Rails and everything worked right away," said &lt;a href="https://twitter.com/benjaminhouy" rel="noopener noreferrer"&gt;Benjamin Houy&lt;/a&gt;, Founder of &lt;a href="https://frenchtogether.com/" rel="noopener noreferrer"&gt;French Together&lt;/a&gt;, who is using Render’s Redis with the &lt;a href="https://github.com/mperham/sidekiq" rel="noopener noreferrer"&gt;Sidekiq&lt;/a&gt; framework for Ruby. Another Render user, prolific indie builder &lt;a href="https://twitter.com/marckohlbrugge" rel="noopener noreferrer"&gt;Marc Köhlbrugge&lt;/a&gt;, shared, "Having hosted Redis instances on Render is yet another thing I no longer need to worry about. Having dedicated configs for different use cases like caching takes out the guess work and lets me focus on building my app rather than having to learn another infrastructure service."&lt;/p&gt;

&lt;p&gt;Early Access users, like &lt;a href="https://twitter.com/damianlegawiec" rel="noopener noreferrer"&gt;Damian Legawiec&lt;/a&gt;, CTO of &lt;a href="https://www.getvendo.com/" rel="noopener noreferrer"&gt;vendo&lt;/a&gt;, have also appreciated the hands-off operation so they can focus on growing their businesses:&lt;/p&gt;

&lt;p&gt;"Our product allows businesses to launch an eCommerce marketplace quickly. A key feature of vendo is supplier onboarding where we make extensive use of background queues on Redis to pull product data from various sources and normalize it. We then use another queue to push that data to other tools to make it easily searchable and downloadable. Building on Render’s managed Redis has allowed us to focus on our product roadmap. Setting it up was a breeze, and it has a big impact on scaling of our product."&lt;/p&gt;

&lt;p&gt;Other Early Access users created Render Redis instances to manage rate limits for third-party API requests, queue expensive tasks in the background and remove them from the request flow, and cache data from their CMS to make their sites faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;For users who want to use Redis for queueing, check out our &lt;a href="http://render.com/docs/background-workers" rel="noopener noreferrer"&gt;guides&lt;/a&gt; for processing requests using the Ruby Sidekiq framework and using the Python Celery library. For other use cases, you can learn more in our &lt;a href="http://render.com/docs/redis" rel="noopener noreferrer"&gt;docs&lt;/a&gt; and &lt;a href="https://dashboard.render.com/new/redis" rel="noopener noreferrer"&gt;create a Redis instance&lt;/a&gt; to get started.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>news</category>
    </item>
    <item>
      <title>Remote Debugging with SSH and VS Code</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Mon, 28 Feb 2022 19:48:15 +0000</pubDate>
      <link>https://forem.com/render/remote-debugging-with-ssh-and-vs-code-3ol8</link>
      <guid>https://forem.com/render/remote-debugging-with-ssh-and-vs-code-3ol8</guid>
      <description>&lt;p&gt;I was pretty excited when I discovered &lt;a href="https://en.wikipedia.org/wiki/Telnet" rel="noopener noreferrer"&gt;Telnet&lt;/a&gt; back in the 90s. I could access a command line on servers all over the college campus from my tiny dorm room! I no longer had to trudge through feet of snow and frigid temperatures (I went to college in the U.S. state of Maine) only to find all the computers in the lab occupied. I could connect to them from my room -- even if someone else was already using them!&lt;/p&gt;

&lt;p&gt;A more advanced version of me is now shocked at how pervasive the unencrypted Telnet protocol was in the 90s. Back then, I didn't even consider encrypting terminal traffic. Fortunately, others did. After discovering a password sniffer had been used on his university’s network, Finnish student &lt;a href="https://en.wikipedia.org/wiki/Secure_Shell#Version_1" rel="noopener noreferrer"&gt;Tatu Ylönen created the first version of the Secure Shell Protocol (or SSH)&lt;/a&gt; in 1995, and its use has grown continuously since.&lt;/p&gt;

&lt;p&gt;SSH is powerful because it can be used not only to get a command line on a remote computer but also to securely encapsulate almost any kind of data flowing between two machines. It can tunnel pixels from a remote machine's &lt;a href="https://en.wikipedia.org/wiki/X_Window_System" rel="noopener noreferrer"&gt;X11 server&lt;/a&gt;, sync files with &lt;a href="https://en.wikipedia.org/wiki/Rsync" rel="noopener noreferrer"&gt;rsync&lt;/a&gt;, and even mount a filesystem from a remote machine using &lt;a href="https://en.wikipedia.org/wiki/SSHFS" rel="noopener noreferrer"&gt;SSHFS&lt;/a&gt;. SSH has understandably become an essential tool in the software developer's toolkit.&lt;/p&gt;

&lt;p&gt;We recently &lt;a href="https://render.com/blog/winter-release-new-features#native-ssh-connectivity-for-remote-access" rel="noopener noreferrer"&gt;announced&lt;/a&gt; the ability to SSH into your Render services, so I was excited to see how I could use it to improve my development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SSH?
&lt;/h2&gt;

&lt;p&gt;We primarily built SSH on Render to allow you to get a command line in containers running a service you’ve deployed. We don’t recommend using it in production regularly, but it can be helpful in a pinch, to run a database migration after a deployment or to check the contents of a file being written to disk.&lt;/p&gt;

&lt;p&gt;But there’s a lot more you can do with SSH. For example, you can &lt;a href="https://render.com/docs/disks#scp" rel="noopener noreferrer"&gt;use &lt;code&gt;scp&lt;/code&gt; to copy files&lt;/a&gt; to or from your service’s &lt;a href="https://render.com/docs/disks" rel="noopener noreferrer"&gt;persistent disk&lt;/a&gt;. For my own needs, I was curious if I could use Render SSH for port forwarding and eventually remote debugging.&lt;/p&gt;

&lt;p&gt;Some of the most frustrating bugs I’ve encountered are those I can’t reproduce locally. I get a bug report from a user along with instructions to reproduce it. I can reproduce the bug in production (running Linux) but surprisingly can’t reproduce it on my Mac. How do I investigate and fix a bug if I can’t reproduce it on my development machine?&lt;/p&gt;

&lt;h2&gt;
  
  
  Debug Node.js Remotely
&lt;/h2&gt;

&lt;p&gt;Remote debugging to the rescue! Here’s the code for a simplified Node.js HTTP server I will debug remotely.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&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;port&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;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;if &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;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NODE_ENV is set to "production"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&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;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &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;url&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="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;writeHead&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="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&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;text/html&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="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, World!&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;utf-8&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;else&lt;/span&gt; &lt;span class="k"&gt;if &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;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/exception&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;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Some code that causes an exception&lt;/span&gt;
      &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;e&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;writeHead&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="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&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;text/html&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="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`An error occurred: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&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="s2"&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;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="nx"&gt;host&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="s2"&gt;`Server running at http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a refresher, to attach a debugger to the &lt;code&gt;node&lt;/code&gt; process if it were running locally, I’d start &lt;code&gt;node&lt;/code&gt; with the &lt;code&gt;--inspect&lt;/code&gt; flag and then attach VS Code (or one of several other &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#inspector-clients" rel="noopener noreferrer"&gt;debugger clients&lt;/a&gt;) to the process.&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;node &lt;span class="nt"&gt;--inspect&lt;/span&gt; server.js
Debugger listening on ws://127.0.0.1:9229/a9ca58f7-e847-4c31-9e49-82f10ee3a5f5
For &lt;span class="nb"&gt;help&lt;/span&gt;, see: https://nodejs.org/en/docs/inspector
Server running at http://127.0.0.1:10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now VS Code would show me variable values and the call stack at any point during the program’s execution. &lt;/p&gt;

&lt;p&gt;But getting this working when the &lt;code&gt;node&lt;/code&gt; process is running on Render is not as straightforward. Render accepts traffic from the public internet for only one port for a Web Service. My Web Service is already listening on port 10000 for HTTP traffic, so I can’t expose the debug port publicly. Moreover, even when Render starts supporting traffic on multiple ports, &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#exposing-the-debug-port-publicly-is-unsafe" rel="noopener noreferrer"&gt;exposing the debug port publicly is unsafe&lt;/a&gt;! Fortunately, the Node.js documentation provides &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#enabling-remote-debugging-scenarios" rel="noopener noreferrer"&gt;guidance on enabling remote debugging safely&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Render
&lt;/h3&gt;

&lt;p&gt;The first step is to edit the &lt;strong&gt;Start Command&lt;/strong&gt; for the service from the Render Dashboard. Instead of running &lt;code&gt;node server.js&lt;/code&gt;, I have to run &lt;code&gt;node --inspect=9229 server.js&lt;/code&gt;. &lt;code&gt;9229&lt;/code&gt; is the default port the Node.js debugger will listen on, but I want to be explicit to avoid confusion. Then I &lt;a href="https://render.com/docs/ssh-keys" rel="noopener noreferrer"&gt;add an SSH key to my Render account&lt;/a&gt;. This only needs to be done once for each Render account.&lt;/p&gt;

&lt;p&gt;While the service restarts with the new start command, I’m going to configure VS Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure VS Code
&lt;/h3&gt;

&lt;p&gt;VS Code requires a &lt;code&gt;launch.json&lt;/code&gt; configuration file to connect to the &lt;code&gt;node&lt;/code&gt; process as a debug client. To create this, open &lt;code&gt;.vscode/launch.json&lt;/code&gt; (create this folder and file if needed) and click &lt;strong&gt;Add Configuration…&lt;/strong&gt; in the bottom right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felglp752eb7wbavqasmr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felglp752eb7wbavqasmr.png" alt="A screenshot of configuring VS Code to remotely debug a Node.js process." width="800" height="494"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Configuring VS Code to remotely debug a Node.js process&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;Node.js: Attach to Remote Program&lt;/strong&gt;, and VS Code will generate a template with some values that need to be filled in. The &lt;code&gt;address&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt; are where VS Code will try to connect to the &lt;code&gt;node&lt;/code&gt; process, and the &lt;code&gt;remoteRoot&lt;/code&gt; is the directory to which code is deployed on Render. This path won’t change if you’re using the native Render Node environment instead of Docker. Here is the &lt;code&gt;launch.json&lt;/code&gt; I used.&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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"localRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Attach to Remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"remoteRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/opt/render/project/src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"skipFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;node_internals&amp;gt;/**"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pwa-node"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create the SSH Tunnel
&lt;/h3&gt;

&lt;p&gt;Now that the Web Service is using the new &lt;strong&gt;Start Command&lt;/strong&gt;, I can create an SSH tunnel between the &lt;code&gt;node&lt;/code&gt; process running on Render and my local development machine. First I grab the &lt;code&gt;ssh&lt;/code&gt; command for my Web Service from the Render Dahsboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpusyet830hhefvi3ypar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpusyet830hhefvi3ypar.png" alt="A screenshot of the Render Dashboard showing the SSH connection command for a Web Service." width="800" height="469"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    The &lt;code&gt;ssh&lt;/code&gt; command for a Web Service&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;I need to modify it so that &lt;code&gt;ssh&lt;/code&gt; creates a tunnel between two ports instead of giving me a command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; 9229:localhost:9229 srv-c804oa46fj3d2u40bpq0@ssh.oregon.render.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;-N&lt;/code&gt; flag tells &lt;code&gt;ssh&lt;/code&gt; to not execute a remote command after establishing the connection. Normally when you &lt;code&gt;ssh&lt;/code&gt; into a machine, a command like &lt;code&gt;bash&lt;/code&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt; is run. I don’t need that because I only want &lt;code&gt;ssh&lt;/code&gt; to forward TCP traffic back and forth. &lt;/li&gt;
&lt;li&gt;The &lt;code&gt;-L&lt;/code&gt; flag instructs &lt;code&gt;ssh&lt;/code&gt; to create a tunnel for TCP traffic such that connections to port &lt;code&gt;9229&lt;/code&gt; on my local machine are forwarded to &lt;code&gt;localhost:9229&lt;/code&gt; on the remote machine.&lt;/li&gt;
&lt;li&gt;Finally, &lt;code&gt;srv-c804oa46fj3d2u40bpq0@ssh.oregon.render.com&lt;/code&gt; is the username and domain name &lt;code&gt;ssh&lt;/code&gt; will use to create the connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Start Debugging
&lt;/h3&gt;

&lt;p&gt;Now I can start the debugging session! In VS Code I’ve opened the project deployed to Render, switched the left side panel to &lt;strong&gt;Run and Debug&lt;/strong&gt;, and then clicked the play button at the top to start debugging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foyjtn06lkvkzbwm581tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foyjtn06lkvkzbwm581tm.png" alt="A screenshot showing VS Code's initial state when it connects as a debugging client." width="800" height="484"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    VS Code connected as a debug client to a remote &lt;code&gt;node&lt;/code&gt; process&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;Let’s walk through everything I can see and do now. On the bottom, a &lt;strong&gt;Debug Console&lt;/strong&gt; appeared, which now shows me the logs from my service (stdout and stderr). On the left within the &lt;strong&gt;Loaded Scripts&lt;/strong&gt; panel, I select the &lt;code&gt;server.js&lt;/code&gt; file so that I can add breakpoints and see which line program execution is on.&lt;/p&gt;

&lt;p&gt;To confirm that remote debugging is working the same way local debugging does in VS Code, I add a breakpoint on line 15.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zlkcavfubxbxan8r1j9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zlkcavfubxbxan8r1j9.png" alt="A screenshot of VS Code showing a breakpoint on line 15." width="800" height="89"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Add a breakpoint to line 15&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;This is part of the code that is executed when a request to &lt;code&gt;/&lt;/code&gt; is made. I make a request to that path in a new browser tab, and it looks like everything is working as expected! I can see that execution paused on line 15, and VS Code shows me the state of the &lt;code&gt;req&lt;/code&gt; and &lt;code&gt;res&lt;/code&gt; local variables and the call stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvwa91f62zdytg7g3vb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvwa91f62zdytg7g3vb6.png" alt="A screenshot of VS Code pausing program execution at a breakpoint." width="800" height="484"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Program execution paused at the breakpoint&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;Earlier in this post, I said that remote debugging is helpful to investigate an issue observed in production but not on my local development machine. Let’s say the bug causes an exception, but I don’t know which line of code is generating the exception. I can tell VS Code to automatically pause program execution when it hits both caught and uncaught exceptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sve6pmdyx0m4f1tr0ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sve6pmdyx0m4f1tr0ux.png" alt="A screenshot of VS Code configured to pause execution on all exceptions." width="800" height="473"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Break on caught and uncaught exceptions&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;To test this out, I make a request to &lt;code&gt;/exception&lt;/code&gt;. VS Code automatically pauses execution on the line that generates the exception. This example is contrived because I purposely wrote this line to generate an exception. Still, it’s a simple demonstration of how you can remotely debug exceptions before you have to handle a real-life (and more complex) scenario.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkdzyvp5dp6cvbyflxym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkdzyvp5dp6cvbyflxym.png" alt="A screenshot of VS Code pausing program execution on a caught exception." width="800" height="484"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    VS Code pausing program execution on a caught exception&lt;br&gt;
  
  &lt;/p&gt;

&lt;h2&gt;
  
  
  More to Explore
&lt;/h2&gt;

&lt;p&gt;VS Code’s remote debugging feature can also help if you need to investigate CPU or memory use. The &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-js-profile-flame" rel="noopener noreferrer"&gt;Flame Chart Visualizer for JavaScript extension&lt;/a&gt; provides a real-time chart of CPU and memory use of the &lt;code&gt;node&lt;/code&gt; process and a flame chart to help you identify CPU hotspots in your code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpv728imei4qotzis5bax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpv728imei4qotzis5bax.png" alt="An example flame chart showing CPU use chronologically." width="800" height="281"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Example flame chart&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;And you can use VS Code to debug more than just Node.js remotely: use it to connect to an Elixir cluster, a Go process, a Ruby process, and much more. There are VS Code extensions available for all popular languages that enable remote debugging for that language.&lt;/p&gt;

&lt;p&gt;What are you going to use Render SSH for? Let me know in a comment below!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;The shell that’s run is configured per user on the remote machine. You can also override the command by appending a command to the end of the &lt;code&gt;ssh&lt;/code&gt; command. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>vscode</category>
      <category>tooling</category>
      <category>debugging</category>
      <category>ssh</category>
    </item>
    <item>
      <title>Add Password Protection to Any Site with OAuth2 Proxy - Plus Social Logins</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Mon, 14 Feb 2022 19:51:52 +0000</pubDate>
      <link>https://forem.com/render/add-password-protection-to-any-site-with-oauth2-proxy-plus-social-logins-223c</link>
      <guid>https://forem.com/render/add-password-protection-to-any-site-with-oauth2-proxy-plus-social-logins-223c</guid>
      <description>&lt;p&gt;Continuing on the topic of adding password protection to your site, let’s look at using a reverse proxy server to password protect a service you have deployed to Render. The &lt;a href="https://dev.to/render/password-protect-static-sites-with-pagecrypt-4dia"&gt;previous post&lt;/a&gt; on this topic covered adding password protection to a static site using PageCrypt. Now we’re expanding beyond static sites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s the problem we’re trying to solve?&lt;/strong&gt; Say you have an existing Ruby, Node.js, or Python web service (it could be one of our native environments or a Dockerized app), and you want to protect it with password authentication. Maybe you don’t want to touch the existing service’s code because it works well, and you’re concerned about inadvertently introducing a bug. Or perhaps you have multiple services to protect, and you don’t have time to implement password protection for each of them.&lt;/p&gt;

&lt;p&gt;An interesting way to solve this problem is to put a reverse proxy in front of your existing web service(s).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s a reverse proxy server?&lt;/strong&gt; It’s a server that acts as a gate and traffic cop guiding incoming traffic to the service that can respond to it. Typically that service is not exposed to the public internet. The scenario we’ll look at below shuttles web traffic between the public internet and a Render &lt;a href="https://render.com/docs/private-services" rel="noopener noreferrer"&gt;Private Service&lt;/a&gt;, which is protected from the public internet and only accessible to applications you own. NGINX and Apache are examples of general-purpose web servers that can also be used as reverse proxies, but there are many purpose-built ones like &lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;Træfɪk&lt;/a&gt; and &lt;a href="https://caddyserver.com/" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Reverse proxies are commonly used for load balancing or TLS termination but Render already provides these features to you out of the box. In our scenario, we will deploy a reverse proxy specifically for authentication and authorization.&lt;/p&gt;

&lt;p&gt;The reverse proxy we’re going to focus on in this post is called &lt;a href="https://github.com/oauth2-proxy/oauth2-proxy" rel="noopener noreferrer"&gt;oauth2-proxy&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is it good for?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Adding social login to your site – i.e., login managed by another service like GitHub, Google, or one of the other fourteen supported OAuth providers&lt;/li&gt;
&lt;li&gt;Cases in which you can’t or don’t want to change the code of the web service you want to protect&lt;/li&gt;
&lt;li&gt;Maintaining clear separation between auth code and your web service code&lt;/li&gt;
&lt;li&gt;Language flexibility: your web service doesn’t need to be written in the same language as oauth2-proxy (which is written in Go).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What are the drawbacks?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deployed as an extra service, which has a cost (but you can use Render’s free plan)&lt;/li&gt;
&lt;li&gt;Configuration of OAuth can be confusing, but the oauth2-proxy documentation explains it well&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is it, and how does it work?
&lt;/h3&gt;

&lt;p&gt;A coworker shared oauth2-proxy as part of a &lt;a href="https://dev.to/render/use-one-click-deploys-for-secure-online-vs-code-3mdh"&gt;previous Render blog post&lt;/a&gt;, and I think it’s pretty cool. It does two things. First, it authenticates a user with an OAuth 2.0 flow. Then, after successful authentication, it acts as a reverse proxy, forwarding web requests to a Private Service behind it. The Private Service shouldn’t be exposed to the public internet, otherwise, the user will be able to access it without authenticating.&lt;/p&gt;

&lt;p&gt;Are you wondering what OAuth 2.0 is, or do you need a refresher on it? (If not, you can skip to the diagram.) &lt;a href="https://oauth.net/2/" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt; is a standard defined by the IETF OAuth Working Group. It’s a ubiquitous authorization method that lets you delegate authentication and often authorization to another trusted service. For example, you can use Google to log in to the Render Dashboard.​​&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcd2lukbh3wdni6c3be4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcd2lukbh3wdni6c3be4.png" alt="Render's login page" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OAuth 2.0 specifies a multi-step, production-hardened flow we can follow to allow Google to authenticate a user’s credentials and securely tell us that the user is, in fact, the owner of a specific email address, say, &lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt;.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; Armed with the fact that we confidently know this is &lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt; (and not &lt;a href="mailto:sneakyblackhat@example.com"&gt;sneakyblackhat@example.com&lt;/a&gt; pretending to be &lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt;), we can then show the user the resources they have access to — e.g., a web page, some data, or their daily New York Times crossword puzzle.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick detour&lt;/strong&gt;: You may have noticed I’ve used two different words that begin with &lt;em&gt;auth&lt;/em&gt;: authentication and authorization. People often confuse them or mean both when they say one. So what’s the difference? &lt;strong&gt;Authentication&lt;/strong&gt; verifies that a user is who they say they are. &lt;strong&gt;Authorization&lt;/strong&gt; determines the user’s permissions within your app. In the example above, if a user logs in to Render with Google credentials, Google handles the authentication, assuring us the user is who they say they are. After the user is authenticated, Render needs to decide what services and teams to show them. We look up the services and teams they are authorized to manage — those created by them or shared with them by another Render user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Traffic Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps6em6nzd2oy0wu4kog9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps6em6nzd2oy0wu4kog9.png" alt="oauth2-proxy traffic flow" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So how does oauth2-proxy work? Let’s look at the flow from the perspective of a user trying to access your app. Note that this flow is specific to oauth2-proxy, not a generic OAuth 2.0 flow.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser makes an HTTP request to your site, which is directed to the oauth2-proxy service.&lt;/li&gt;
&lt;li&gt;oauth2-proxy responds, prompting the user to authenticate themselves with an OAuth 2.0 provider you’ve configured. In this example, we’ll assume it’s Google.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;The browser is redirected to a Google login page.&lt;/li&gt;
&lt;li&gt;The user enters their credentials for Google to verify.&lt;/li&gt;
&lt;li&gt;Upon successful authentication, Google redirects the user back to your oauth2-proxy service along with a unique token — a query string parameter that Google refers to as an ID token.&lt;/li&gt;
&lt;li&gt;oauth2-proxy makes an HTTP request to Google containing the ID token along with a client ID and client secret that Google has uniquely assigned to your instance of oauth2-proxy. This step is a necessary part of the OAuth flow. A malicious user could spoof the request with a made-up ID token. This out-of-band request between oauth2-proxy and Google verifies the ID token was generated by Google from a recent login for this user.&lt;/li&gt;
&lt;li&gt;Google responds, verifying the validity of those three values and finally confirming to oauth2-proxy the user is who they say they are!&lt;/li&gt;
&lt;li&gt;By default, oauth2-proxy now authorizes all traffic from this user passing it to your Private Service. However, you can &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#config-file" rel="noopener noreferrer"&gt;configure&lt;/a&gt; oauth2-proxy’s authorization rules in several ways. You can restrict access based on membership in a Google Group, GitHub org, or GitLab project. oauth2-proxy can also pass the username to the Private Service which can implement its own authorization logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;I forked the oauth2-proxy repository and made a few &lt;a href="https://github.com/render-examples/oauth2-proxy-blog/compare/4f5efd4..e048670" rel="noopener noreferrer"&gt;changes&lt;/a&gt; so that you can deploy a working example to Render for free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frender.com%2Fimages%2Fdeploy-to-render-button.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frender.com%2Fimages%2Fdeploy-to-render-button.svg" alt="Click to deploy an example oauth2-proxy service to Render for free" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before the deployment starts, you’ll be asked for three values: an OAuth provider, client ID, and secret. The &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/" rel="noopener noreferrer"&gt;oauth2-proxy documentation&lt;/a&gt; explains how to generate a client ID and client secret using any one of fourteen supported OAuth providers.&lt;/p&gt;

&lt;p&gt;I’ve chosen Google as the OAuth provider for an example deployment: &lt;a href="https://oauth2-proxy-yuao.onrender.com/oauth2/sign_in" rel="noopener noreferrer"&gt;try it out&lt;/a&gt;. I'm using oauth2-proxy's default login screen, but you can customize its design. You’ll be prompted to &lt;strong&gt;Sign in with Google&lt;/strong&gt;. After Google authenticates your credentials (which aren’t shared with me or Render), your requests will be proxied to a &lt;a href="https://github.com/render-examples/express-hello-world" rel="noopener noreferrer"&gt;Node.js service&lt;/a&gt; deployed to Render as a &lt;a href="https://render.com/docs/private-services" rel="noopener noreferrer"&gt;Private Service&lt;/a&gt;. Private Services on Render are protected from the public internet and only accessible to applications you own. The oauth2-proxy service receives a request from your browser, passes it to the Node.js service, and then passes the Node.js service’s response back to your browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  More to Explore
&lt;/h3&gt;

&lt;p&gt;If oauth2-proxy doesn't suit your needs, there are some projects that have spun-off from oauth2-proxy like &lt;a href="https://github.com/pomerium/pomerium" rel="noopener noreferrer"&gt;pomerium&lt;/a&gt; and BuzzFeed's &lt;a href="https://github.com/buzzfeed/sso" rel="noopener noreferrer"&gt;sso&lt;/a&gt;. In addition to the open source library, Pomerium offers a paid service with a GUI to help IT staff more easily manage user permissions. BuzzFeed's sso builds upon oauth2-proxy by separating the domain used for auth from the domain used for the proxy (among several other changes).&lt;/p&gt;

&lt;p&gt;If you're interestd in learning more about OAuth, there is &lt;a href="https://oauth.net/3/" rel="noopener noreferrer"&gt;ongoing work to evolve the OAuth 2.0 standard&lt;/a&gt;. &lt;a href="https://oauth.net/2.1/" rel="noopener noreferrer"&gt;OAuth 2.1&lt;/a&gt; exists in a draft state, and &lt;a href="https://oauth.net/gnap/" rel="noopener noreferrer"&gt;GNAP&lt;/a&gt; is a new IETF Working Group developing "a next-generation protocol that encompasses many use cases that are challenging to implement with OAuth 2.0."&lt;/p&gt;

&lt;p&gt;Are you using oauth2-proxy or another reverse proxy with password protection? &lt;a href="https://twitter.com/crc" rel="noopener noreferrer"&gt;Let me know&lt;/a&gt; what worked and didn't work for you! I'm curious to learn more, and I'm sure others will find it helpful to hear about your experience.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;It doesn't necessarily have to be an email. It could be a username or a user ID or any other identifier. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;You could even &lt;a href="https://github.com/oauthjs/node-oauth2-server" rel="noopener noreferrer"&gt;deploy your own&lt;/a&gt;! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Password Protect Static Sites with PageCrypt</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Wed, 02 Feb 2022 20:25:53 +0000</pubDate>
      <link>https://forem.com/render/password-protect-static-sites-with-pagecrypt-4dia</link>
      <guid>https://forem.com/render/password-protect-static-sites-with-pagecrypt-4dia</guid>
      <description>&lt;p&gt;Password protecting static sites is tricky. You could use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt" rel="noopener noreferrer"&gt;&lt;code&gt;window.prompt()&lt;/code&gt;&lt;/a&gt; to ask a site visitor to enter a password before the site content is revealed, but a resourceful visitor will quickly get around this with right-click, view source (or &lt;code&gt;curl&lt;/code&gt; or numerous other ways). All of the site's HTML, CSS, and JavaScript are served to the web browser, and they constitute the entirety of the site.&lt;/p&gt;

&lt;p&gt;If you are using a backend API that the static site pulls data from (like many single-page apps do these days), you could require authentication for API requests, but this doesn't protect the HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;p&gt;In trying to find a good solution for this for Render users, we discovered PageCrypt. It's a free, open source library that allows you to password protect these static assets securely. Let's investigate how PageCrypt works and test it out!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's it good for?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Protecting a &lt;a href="https://render.com/docs/static-sites" rel="noopener noreferrer"&gt;static site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cases where you don't need (or can't run) backend code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are the drawbacks?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Only encrypts a single HTML file by default&lt;/li&gt;
&lt;li&gt;Only supports a single shared password (no per-user passwords)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is it, and how does it work?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Greenheart/pagecrypt" rel="noopener noreferrer"&gt;PageCrypt&lt;/a&gt; is a novel solution to password protecting HTML without a backend. It’s a library you can use as part of your site’s build step or as a command line tool. It uses the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API" rel="noopener noreferrer"&gt;Web Crypto API&lt;/a&gt; -- currently &lt;a href="https://caniuse.com/cryptography" rel="noopener noreferrer"&gt;supported by all major browsers&lt;/a&gt; -- and a password to encrypt an HTML page, which you can then host on any static hosting platform, including Render! An HTML page encrypted with PageCrypt prompts the viewer for a password. Upon entering the correct password, the page is decrypted and its content replaces the password prompt.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
    &lt;br&gt;
  &lt;br&gt;Example static site password protected with PageCrypt
  &lt;/p&gt;

&lt;p&gt;One potential concern with PageCrypt is that it only encrypts an HTML file by default. If you want to encrypt your CSS and JavaScript files, you’ll have to inline them in the HTML file. The same applies to images and any other binary assets; you’ll have to inline them as &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs" rel="noopener noreferrer"&gt;Data URIs&lt;/a&gt;. As with any authentication and authorization solution, you’ll want to determine what’s acceptable for your security requirements. Maybe you’re comfortable with the risk of images leaking but have higher security requirements for your JavaScript. In that case, the HTML page can link to the image files but should contain all your JavaScript. You can use many static site build tools to automate inlining assets in HTML. &lt;a href="https://webpack.js.org" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;, &lt;a href="https://gulpjs.com" rel="noopener noreferrer"&gt;Gulp&lt;/a&gt;, or &lt;a href="https://gruntjs.com" rel="noopener noreferrer"&gt;Grunt&lt;/a&gt; are just a few that might be useful.&lt;/p&gt;

&lt;p&gt;PageCrypt also doesn't allow users to have individual usernames and passwords. It only works with a single, shared password. If you need the more fine-grained control provided by user accounts, check out a service like &lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;Adding PageCrypt to the build step of a &lt;em&gt;Hello World&lt;/em&gt; static site was straightforward. The instructions in the &lt;a href="https://github.com/Greenheart/pagecrypt#readme" rel="noopener noreferrer"&gt;project’s README&lt;/a&gt; provide clear guidance on how to use PageCrypt as a library with browser-executed JavaScript, Node.js, or Deno, and how to use it as a CLI tool. I used the CLI in the build step of my example static site.&lt;/p&gt;

&lt;p&gt;Add PageCrypt to your project as a dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn add pagecrypt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the build step in &lt;code&gt;package.json&lt;/code&gt; to use the CLI:&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="err"&gt;...&amp;lt;snip&amp;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;"pagecrypt src/index.html dist/index.html $PASSWORD &amp;amp;&amp;amp; cp -R src/css dist/"&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="err"&gt;...&amp;lt;snip&amp;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 password is set using the &lt;code&gt;$PASSWORD&lt;/code&gt; environment variable since we don’t want to store that in the code. Using an environment variable also allows you to change the password and rebuild the site quickly.&lt;/p&gt;

&lt;p&gt;Here’s an &lt;a href="https://pagecrypt.onrender.com/" rel="noopener noreferrer"&gt;example deployment&lt;/a&gt; of the site. The password is &lt;code&gt;s3cr3t&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To get a deeper understanding of how PageCrypt works, try entering an incorrect password. Then right-click and view the source of the page. PageCrypt generated the contents of this page during the build step. Your original page content is encrypted inside a hidden &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element at the bottom of the HTML document.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mj0dz6200xsf5g5fy4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mj0dz6200xsf5g5fy4v.png" alt="A screenshot of a PageCrypt-encrypted HTML page in Google Chrome's elements inspector." width="800" height="178"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Encrypted HTML page inside the &amp;lt;pre&amp;gt; element.&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;After the correct password is entered, your page content is decrypted and shown.&lt;/p&gt;

&lt;p&gt;To make it easier for users to access password protected pages, PageCrypt also allows you to create a "magic link" that decrypts the page without prompting the user for a password. The format for the magic link is &lt;code&gt;https://&amp;lt;link-to-your-page&amp;gt;#&amp;lt;password&amp;gt;&lt;/code&gt;, placing the password in a &lt;a href="https://en.wikipedia.org/wiki/URI_fragment" rel="noopener noreferrer"&gt;URI fragment&lt;/a&gt;. Check out the &lt;a href="https://github.com/Greenheart/pagecrypt#share-a-magic-link-to-let-users-open-protected-pages-with-a-single-click" rel="noopener noreferrer"&gt;project's README section about magic links&lt;/a&gt; to better understand the security implications. When your browser goes to a URL containing a URI fragment, the fragment isn't sent across the internet, but it does remain in the browser's history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extend
&lt;/h2&gt;

&lt;p&gt;It would be interesting to extend PageCrypt to do a number of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate the inlining of CSS, JavaScript, and image files&lt;/li&gt;
&lt;li&gt;Encrypt multiple HTML files&lt;/li&gt;
&lt;li&gt;Encrypt multiple files, including CSS, JavaScript, and image files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you do end up extending it in your build process, &lt;a href="https://twitter.com/crc" rel="noopener noreferrer"&gt;let me know&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore
&lt;/h2&gt;

&lt;p&gt;This version of PageCrypt is a rewrite of an &lt;a href="https://github.com/MaxLaumeister/PageCrypt" rel="noopener noreferrer"&gt;older version of PageCrypt&lt;/a&gt;. That older version also inspired a few spin-offs that you might find useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/MaxLaumeister/pagecrypt/tree/master/python" rel="noopener noreferrer"&gt;Python CLI&lt;/a&gt; for PageCrypt&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/MaxLaumeister/pagecrypt/tree/master/PowerShell" rel="noopener noreferrer"&gt;PowerShell CLI&lt;/a&gt; for PageCrypt&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/brentscott93/pagecryptr" rel="noopener noreferrer"&gt;R-wrapper&lt;/a&gt; for PageCrypt&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/robinmoisson/staticrypt" rel="noopener noreferrer"&gt;StatiCrypt&lt;/a&gt; - A separate but similar project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now try it out yourself! You can deploy the &lt;a href="https://github.com/render-examples/pagecrypt" rel="noopener noreferrer"&gt;code&lt;/a&gt; to Render for free. The README provides step-by-step instructions.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>security</category>
    </item>
    <item>
      <title>Git Organized: A Better Git Flow</title>
      <dc:creator>Annie Sexton</dc:creator>
      <pubDate>Thu, 13 Jan 2022 18:58:36 +0000</pubDate>
      <link>https://forem.com/render/git-organized-a-better-git-flow-56go</link>
      <guid>https://forem.com/render/git-organized-a-better-git-flow-56go</guid>
      <description>&lt;p&gt;Imagine this: you've been paged to investigate a production incident, and after some digging, you identify the commit with the breaking code. You decide to revert the change:&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 revert 1a2b3c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, in doing so, a new bug is introduced! As it turns out, hidden in that old "broken" commit was some code that another part of the app depended upon, and when you reverted those lines, it left the site once again in a broken state. 🙃 Oh dear.&lt;/p&gt;

&lt;p&gt;How can situations like this be avoided? To answer this question, we first need to examine how these types of commits come to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Common Git Flow
&lt;/h2&gt;

&lt;p&gt;Let's take a look at a common git flow when building a new feature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new branch off of &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create commits as you go to save your work and fix bugs you find along the way.&lt;/li&gt;
&lt;li&gt;When the feature is complete, make a pull request.&lt;/li&gt;
&lt;li&gt;Merge branch into &lt;code&gt;main&lt;/code&gt; once PR is approved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This flow might feel quite familiar to you, and that's fine. It's how many of us were taught to work with git. However, &lt;strong&gt;there are two problems with this approach.&lt;/strong&gt; The first problem we've already discussed is that some commits may contain incomplete work when you simply commit as you go. This makes reverting quite risky.&lt;/p&gt;

&lt;p&gt;The second problem is that &lt;strong&gt;it can make reviewing pull requests very tedious.&lt;/strong&gt; For example, what if you've been asked to review a recent PR in which the author states that, on top of adding a new feature, they fixed an unrelated bug as well. The PR consists of changes across &lt;em&gt;dozens&lt;/em&gt; of files. Looking at each commit individually does not illuminate which changes pertain to the bug fix and which are for the new feature. Additionally, you notice some changes that seem &lt;em&gt;unrelated to anything in the description of the PR&lt;/em&gt;. Clearly, this will not be a quick review.&lt;/p&gt;

&lt;p&gt;Now, as lovely as it would be for each commit to neatly relate to only &lt;em&gt;one&lt;/em&gt; change, that's a tall order to fill when you're in the midst of development. Tangents and rewrites are just part of the process. Our work is rarely so linear, and our git commits tend to reflect this.&lt;/p&gt;

&lt;p&gt;So how can we guarantee that our git history is tidy and easily reviewable while also accepting the somewhat tangential nature of development? By modifying this basic git flow just slightly, we can create a better process that accomplishes just this.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Improved Git Flow
&lt;/h2&gt;

&lt;p&gt;The following approach was inspired by my coworker, Dan Wendorf, whose git flow tends to revolve around one core principle: &lt;strong&gt;do the work first, clean up the commits later.&lt;/strong&gt; The benefit of this flow is that it separates the &lt;em&gt;engineering&lt;/em&gt;  &lt;em&gt;work&lt;/em&gt; from the &lt;em&gt;commit writing&lt;/em&gt;. In the end, we'll be left with a sequence of commits that are &lt;em&gt;logically grouped&lt;/em&gt;, each relating to one main change in the code, thus cleaning up our git history and paving the way for a quicker PR review.&lt;/p&gt;

&lt;p&gt;We can break it down into three steps, as follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Make your changes
&lt;/h3&gt;

&lt;p&gt;The first step isn't too different than before. Start by creating a new branch and getting to work on making your changes. Don't worry too much about writing descriptive commit messages just yet, as these won't be included in your final PR. For now a simple, "work in progress" or "WIP" message will do, or something that will help you remember what was in that commit like "WIP: Started building new model". The purpose of these commits are to make sure you don't lose work and provide some general guideposts along the path of that work.&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; my-feature-branch

...make changes...

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s2"&gt;"WIP"&lt;/span&gt;

...make more changes...

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s2"&gt;"WIP"&lt;/span&gt;

...make even more changes...

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s2"&gt;"WIP"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this step, it's &lt;em&gt;okay&lt;/em&gt; to leave the codebase in a broken state or to commit half-baked features. This will all get cleaned up later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Reset
&lt;/h3&gt;

&lt;p&gt;Once you've finished making your changes, it's time to prepare your work for some "git clean up." To do this, we'll run the following command:&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 reset origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without any extra arguments, &lt;code&gt;git reset&lt;/code&gt; won't change the working tree, so your code won't change — all the work you've done will still be there. But because you've reset to an &lt;em&gt;older&lt;/em&gt; commit, &lt;code&gt;git status&lt;/code&gt; will show all the changes you've made since you started building your feature. It will look like you did all the work but never made any of those "WIP" commits earlier.&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 reset origin/main
Unstaged changes after reset:
M       src/components/Footer/Footer.tsx
M       src/components/Nav/Nav.css
M       src/components/Nav/Nav.tsx
M       src/components/Posts/Post.tsx
M       src/components/Posts/PostList.tsx

&lt;span class="nv"&gt;$ &lt;/span&gt;git status
On branch feature-branch
Your branch is behind &lt;span class="s1"&gt;'origin/feature-branch'&lt;/span&gt; by 3 commits, and can be fast-forwarded.
  &lt;span class="o"&gt;(&lt;/span&gt;use &lt;span class="s2"&gt;"git pull"&lt;/span&gt; to update your &lt;span class="nb"&gt;local &lt;/span&gt;branch&lt;span class="o"&gt;)&lt;/span&gt;

Changes not staged &lt;span class="k"&gt;for &lt;/span&gt;commit:
  &lt;span class="o"&gt;(&lt;/span&gt;use &lt;span class="s2"&gt;"git add &amp;lt;file&amp;gt;..."&lt;/span&gt; to update what will be committed&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;(&lt;/span&gt;use &lt;span class="s2"&gt;"git restore &amp;lt;file&amp;gt;..."&lt;/span&gt; to discard changes &lt;span class="k"&gt;in &lt;/span&gt;working directory&lt;span class="o"&gt;)&lt;/span&gt;
        modified:   src/components/Footer/Footer.tsx
        modified:   src/components/Nav/Nav.css
        modified:   src/components/Nav/Nav.tsx
        modified:   src/components/Posts/Post.tsx
        modified:   src/components/Posts/PostList.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In case you get in over your head here, don't worry&lt;/strong&gt; — you can always bring back your original commits! Every commit you make lives in your &lt;code&gt;.git&lt;/code&gt; folder, even after a &lt;code&gt;reset&lt;/code&gt;. Even though it might seem like they've disappeared, they're still there, hiding.&lt;/p&gt;

&lt;p&gt;If you want to go back to a commit where things weren't broken, &lt;code&gt;git reflog&lt;/code&gt; will show you a timeline of every commit you've referenced in your local repository, even across branches. Run &lt;code&gt;git reflog&lt;/code&gt; to find the commit you want to return to and then run &lt;code&gt;git reset &amp;lt;commit-sha&amp;gt;&lt;/code&gt;. This command will point the HEAD of your current branch to that commit, and you're back in business!&lt;/p&gt;

&lt;p&gt;From here, we're ready to start making our &lt;em&gt;new&lt;/em&gt; commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create new, logically-grouped commits
&lt;/h3&gt;

&lt;p&gt;Now, take a look at all the files you've changed. Are there any that you can logically group? For example, all the dependency updates or changes related to a particular model. There's no "right" way to group files, so use your best judgment here. Add these files to your staging area, and make a commit describing the changes.&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 add src/components/Nav/Nav.css
&lt;span class="nv"&gt;$ &lt;/span&gt;git add src/components/Nav/Nav.tsx
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s2"&gt;"Added new styles to navigation"&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git add src/components/Posts/Post.tsx
&lt;span class="nv"&gt;$ &lt;/span&gt;git add src/components/Posts/PostList.tsx
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s2"&gt;"Updated author images on posts"&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git add src/components/Footer/Footer.tsx
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s2"&gt;"Fixed responsive bug in footer"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you haven't changed many files, you might not need more than one commit, but we can often make our pull requests much easier to review by splitting up our changes into human-readable, easy-to-follow commits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the same file contains multiple changes that should be grouped separately?&lt;/strong&gt; It's possible to &lt;a href="https://nuclearsquid.com/writings/git-add/" rel="noopener noreferrer"&gt;stage &lt;em&gt;part&lt;/em&gt; of a file&lt;/a&gt; using &lt;code&gt;git add --patch&lt;/code&gt; (or &lt;code&gt;git add -p&lt;/code&gt;). Some code editors also provide a way to stage a &lt;em&gt;range&lt;/em&gt; of changes rather than a whole file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be mindful of not leaving your codebase in a broken state during this step.&lt;/strong&gt; Remember, a huge reason we're cleaning up our commits in the first place is so that nothing will break if we ever want to revert our changes. After making one of these new commits, you can &lt;code&gt;git stash&lt;/code&gt; the rest of the unstaged changes and test that everything's still in working order. If you realize you should have included another file in that commit, you can &lt;code&gt;git stash pop&lt;/code&gt; to bring back the other changes, &lt;code&gt;git add&lt;/code&gt; the missing file, and perform a &lt;code&gt;git commit --amend&lt;/code&gt; . This command will replace the last commit with a new one with the same description, including the old commit and the change you just made.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Final Result
&lt;/h3&gt;

&lt;p&gt;Once you've split your work into logically grouped commits, you're ready to create your pull request! The final result is a set of changes that your colleague can review one commit at a time in manageable chunks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdn0zd76mhkfvtk0qrjzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdn0zd76mhkfvtk0qrjzr.png" alt="Logically-organized commits in a PR" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The benefit of this git flow is that it allows for the fluidity of typical development while also providing some much-needed order to maintain the repository's history.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>productivity</category>
      <category>git</category>
    </item>
    <item>
      <title>Use One-Click Deploys for Secure Online VS Code</title>
      <dc:creator>Rosalind Benoit</dc:creator>
      <pubDate>Wed, 08 Dec 2021 17:49:13 +0000</pubDate>
      <link>https://forem.com/render/use-one-click-deploys-for-secure-online-vs-code-3mdh</link>
      <guid>https://forem.com/render/use-one-click-deploys-for-secure-online-vs-code-3mdh</guid>
      <description>&lt;h2&gt;
  
  
  Introducing "Deploy To Render"
&lt;/h2&gt;

&lt;p&gt;I’m currently getting up and running with Render. Luckily, getting new users started with very little friction is core to our mission, and it shows. One of my biggest challenges in helping to maintain and grow the Spinnaker project was the friction around getting started with it. New users and potential contributors often had a difficult time with operations or simply with deploying Spinnaker for the first time to check it out. They became frustrated, and they moved on. This made my job hard.&lt;/p&gt;

&lt;p&gt;I think that’s why, for my very first project here, I gravitated towards playing with Render’s awesome &lt;a href="https://render.com/docs/deploy-to-render" rel="noopener noreferrer"&gt;1-click deploy&lt;/a&gt; feature, which takes a few minutes to learn and use successfully. Setting up a Deploy to Render button also taps into our (expanding) infrastructure-as-code functionality, and that’s my jam.&lt;/p&gt;

&lt;p&gt;I get excited about use cases for Deploy to Render buttons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enablement&lt;/strong&gt;! Remove institutional/project knowledge as a barrier to entry. Got an OSS project? Got new reports or colleagues who need to deploy services to become productive? Level the playing field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing &amp;amp; CI&lt;/strong&gt;! Incorporate polished, simple, and complete deployments into your process so you’ll &lt;em&gt;know&lt;/em&gt; everything is working and can quickly (or programmatically) demonstrate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have browser-based VSCode on the brain after reading &lt;a href="https://dev.to/render/host-a-dev-environment-on-render-with-vs-code-and-tailscale-jah"&gt;David’s post&lt;/a&gt; about building hosted dev environments with VSCode, Tailscale, and Render. So, the first app I try deploying is &lt;a href="https://github.com/gitpod-io/openvscode-server" rel="noopener noreferrer"&gt;OpenVSCode Server&lt;/a&gt;, a neat project Gitpod recently open-sourced that shares a browser-friendly implementation of VSCode with a simple upgrade path and deployment strategy. It lets folks run the latest VSCode version on a remote server, and access it from anywhere in a browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqij4c9ssi1s43u1cnb0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqij4c9ssi1s43u1cnb0j.png" alt="Editing a render.yaml file in the browser using an Open VSCode instance hosted on Render" width="800" height="415"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Open VSCode joins my crowded tab landscape, allowing me to quickly edit a render.yaml file in browser.&lt;br&gt;&lt;br&gt;
  
  &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a &lt;code&gt;Dockerfile&lt;/code&gt; for GitPod OpenVSCode Server
&lt;/h2&gt;

&lt;p&gt;Gitpod makes OpenVSCode Server easy to deploy in several ways, including by running it in a Docker container, which is the most Render-friendly approach. Given a stable Docker image in a public registry such as Docker Hub, it’s quick to deploy that image on Render. My first newbie challenge is figuring out exactly how.&lt;/p&gt;

&lt;p&gt;GitPod’s instructions for running the server in a Docker container are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/home/workspace:cached"&lt;/span&gt; gitpod/vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can’t wait for the day that &lt;em&gt;this&lt;/em&gt; becomes ~1-click, and we can deploy registered images on Render in a single step. While we work on &lt;a href="https://feedback.render.com/features/p/deploy-docker-images-from-public-private-registries" rel="noopener noreferrer"&gt;this part of our roadmap&lt;/a&gt;, there’s a simple workaround you can use in the meantime.&lt;/p&gt;

&lt;p&gt;First, fork the project and add a basic &lt;code&gt;Dockerfile&lt;/code&gt; to it that pulls the image you want to deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gitpod/openvscode-server&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; [ "/bin/sh", "-c", "/home/openvscode-server-v$OPENVSCODE_SERVER_VERSION-linux-x64/server.sh"]&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; vscode-server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Meet Blueprints and &lt;code&gt;render.yaml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Next, meet &lt;a href="https://render.com/docs/yaml-spec" rel="noopener noreferrer"&gt;&lt;code&gt;render.yaml&lt;/code&gt;&lt;/a&gt;. A &lt;code&gt;render.yaml&lt;/code&gt; is a Blueprint spec: it allows you to define environment variables, secrets, disks, build directories, start commands, healthchecks, deployment regions, and other configuration settings for your application without using a GUI. Since the &lt;code&gt;docker run&lt;/code&gt; command in Gitpod’s OpenVSCode documentation mounts a volume, I need to add a volume mount to my Render service. I can easily add that in &lt;code&gt;render.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitpod-vscode-example&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
  &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/render-examples/gitpod-vscode-example.git&lt;/span&gt;
  &lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OPENVSCODE_SERVER_VERSION&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.60.0&lt;/span&gt;
  &lt;span class="na"&gt;disk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data&lt;/span&gt;
    &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/workspace&lt;/span&gt;
    &lt;span class="na"&gt;sizeGB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With my simple &lt;code&gt;render.yaml&lt;/code&gt; and &lt;code&gt;Dockerfile&lt;/code&gt; in place in the root directory of the cloned project, I am ready to deploy my new fork of OpenVSCode Server onto Render. I use the &lt;strong&gt;New&lt;/strong&gt; &amp;gt; &lt;strong&gt;Blueprint&lt;/strong&gt; drop-down to begin a new project, find the repository in the GitHub search, give the project a name, and that’s it. I click &lt;strong&gt;Apply&lt;/strong&gt; and watch it deploy via the Events stream. When it’s ready, I can click on the public address from the service page in the Dashboard, and enter my nifty new VSCode environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhh29amv1a138gbnvhkb.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fdhh29amv1a138gbnvhkb.jpeg" alt="Accessing my Open VSCode server instance from the Render dashboard" width="800" height="341"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    I click here to access my OpenVSCode Server instance.&lt;br&gt;&lt;br&gt;
  
  &lt;/p&gt;
&lt;h2&gt;
  
  
  Setting Up 1-Click Deploy
&lt;/h2&gt;

&lt;p&gt;With this up and running, and a bit more confidence in my Render skills, I think, hmm, how can I make it ridiculously easy for anyone else who wants to use Render to check out this project? Sure, as long as my repo is public, anyone can use it to set the service up themselves, but only by consulting a guide (like this one : ) to make sure that all of the Docker settings and advanced features like disk mounting are configured correctly. I want to remove all of that overhead.&lt;/p&gt;

&lt;p&gt;I thought setting up a Deploy to Render button would be more complicated, but it turns out I have already completed 90% of the effort. With &lt;code&gt;render.yaml&lt;/code&gt; already in place, all I need to do is paste a single line of markdown into my README.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![Deploy to Render&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://render.com/images/deploy-to-render-button.svg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://render.com/deploy)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Phew! Check out my Deploy to Render button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://render.com/deploy?repo=https://github.com/render-examples/gitpod-vscode-example" rel="noopener noreferrer"&gt;&lt;br&gt;
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frender.com%2Fimages%2Fdeploy-to-render-button.svg" alt="Deploy to Render" width="800" height="400"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

  Click to deploy OpenVSCode
  





&lt;h2&gt;
  
  
  Next Steps: Authentication, and Beyond!
&lt;/h2&gt;

&lt;p&gt;Now, a developer’s work is never done, and this is no exception. As David pointed out in his blog, “I don’t want to invest more in configuring my code-server instance until I figure out a way to make it secure.” Yeah, browser-based VSCode feels much less safe, and is much less useful as a tool for standardizing remote dev environments, without more security. So, my next challenge is to deploy an authentication service on Render to provide auth for my web services.&lt;/p&gt;

&lt;p&gt;Another handy open source project, &lt;a href="https://github.com/oauth2-proxy/oauth2-proxy" rel="noopener noreferrer"&gt;OAuth2-Proxy&lt;/a&gt;, is up to the task! To integrate the two projects in a secure environment, I venture further into using Blueprints. I learn to consolidate my infrastructure into a single &lt;code&gt;render.yaml&lt;/code&gt; file, and to use the &lt;code&gt;sync: false&lt;/code&gt; attribute when defining a key to allow users to securely provide their own secret values in the Render Dashboard at deploy time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fty5qq90buhwxm1h9tuv2.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fty5qq90buhwxm1h9tuv2.jpeg" alt="Deploying the blueprint for secure OpenVSCode server and entering secret values" width="800" height="476"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Here, I define the secret values for my OAuth2 app that I don’t want in source control.&lt;br&gt;&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/gitpod-io/openvscode-server/tree/docs/guides/render#deploy-secure-openvscode-server-to-render-with-oauth" rel="noopener noreferrer"&gt;Deploy Secure Open VSCode with OAuth2-Proxy Guide&lt;/a&gt; now, and deploy it to Render yourself with this button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://render.com/deploy?repo=https://github.com/render-examples/openvscode-with-oauth" rel="noopener noreferrer"&gt;&lt;br&gt;
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frender.com%2Fimages%2Fdeploy-to-render-button.svg" alt="Deploy to Render" width="800" height="400"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

  Click to deploy OpenVSCode with Authentication
  



&lt;p&gt;Stay tuned to read more about other ways to add authentication to web services...in an upcoming blog post : )&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux Lite
&lt;/h2&gt;

&lt;p&gt;I’ll leave you with one final Deploy to Render adventure. A little background: I have chosen my entry to Render as the opportune time to return to my roots as a desktop user of Linux. While I have more recently administered RHEL systems and of course, used Unix as a developer, I have not used Linux as my primary desktop since high school. But Fedora Core 1-4 will always hold a special place in my heart as the playground on which I tried [and failed?] to become great at Linux. My style has always been lazy: I’m not a workstation-optimization junkie; I muddle through, doing just enough to make things workable. I’m &lt;em&gt;psyched&lt;/em&gt; to be doing so again, on a delightful new brick of a ThinkPad P15 and Ubuntu 20.04. Especially since I have the support of Render’s UX, and can deploy changes to the cloud and use preview environments when messing with local configuration gives me a headache. Look for &lt;a href="https://twitter.com/dnilas0r" rel="noopener noreferrer"&gt;Tweets&lt;/a&gt;, StackOverflows, and general cries for help from me in the coming months as I refine my developer environment into something kickass, and weed through the occasional need to kill Gnome because my cursor has randomly turned into the screenshot-selector crosshairs and won’t...curse &lt;em&gt;[restarts X11 server]&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Lest I get discouraged, Render once again has my back. I can always fire up this (inexplicably popular) &lt;a href="https://github.com/blueedgetechno/windows11" rel="noopener noreferrer"&gt;OSS project&lt;/a&gt; and enjoy technicolor dreams of Windows 11:&lt;/p&gt;

&lt;p&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Ftdl5maxajx5ccj9gpnnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdl5maxajx5ccj9gpnnu.png" alt="Screenshot of the open source Windows 11 React project" width="800" height="510"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    A React &amp;amp; SCSS web app replica of the Windows 11 desktop experience - why not?&lt;br&gt;&lt;br&gt;
  
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Your Own Windows 11 to Render (for free)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://dashboard.render.com/select-repo?type=web" rel="noopener noreferrer"&gt;https://dashboard.render.com/select-repo?type=web&lt;/a&gt; (create a Render account and link your GitHub)&lt;/li&gt;
&lt;li&gt;Paste in the repo's URL and select it: &lt;code&gt;https://github.com/blueedgetechno/win11React&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name and choose the &lt;strong&gt;Free&lt;/strong&gt; plan.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Web Service&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Enjoy our &lt;a href="https://github.com/render-examples" rel="noopener noreferrer"&gt;Render Examples&lt;/a&gt; and so much more when you start playing with our &lt;a href="https://render.com/free" rel="noopener noreferrer"&gt;new plans&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hi! I’m Rosalind, the newest member of Render’s Growth Team. A former on-call systems engineer, I somehow fell down the Developer Marketing rabbit hole through stints working on DevOps and open source projects for Puppet, Armory, and the Continuous Delivery Foundation. I have a background in education, and I love working with developers learning to solve problems in new ways, with new tools. This often requires me to do some learning myself 🙃&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>productivity</category>
      <category>vscode</category>
      <category>coding</category>
    </item>
    <item>
      <title>How to Build an Anycast Network</title>
      <dc:creator>Jake Malachowski</dc:creator>
      <pubDate>Mon, 25 Oct 2021 20:54:28 +0000</pubDate>
      <link>https://forem.com/render/how-to-build-an-anycast-network-5bbl</link>
      <guid>https://forem.com/render/how-to-build-an-anycast-network-5bbl</guid>
      <description>&lt;p&gt;When you type a URL into your address bar, your browser performs a DNS lookup to resolve the human-readable domain name to an IP address necessary for networking software, for example &lt;code&gt;216.24.57.1&lt;/code&gt;. Armed with that IP, how do the packets comprising the request actually get from your local machine to a server somewhere in the world capable of handling that request? How do you ensure those packets route to the server that can handle the request most efficiently?&lt;/p&gt;

&lt;p&gt;Serving traffic as efficiently as possible, regardless of where it originates, is a critical requirement at Render. One answer to this challenge is to deploy a global fleet of servers to create an anycast network. What is an anycast network? What is it good for? How can you build your own? This post will answer all of these questions, but we’ll first need some background on the building blocks of global networking.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Brief Overview of the Internet
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswil0t3vv0wm6w4wtarx.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fswil0t3vv0wm6w4wtarx.jpeg" alt="A diagram describing how an HTTP request is routed between autonomous systems to its destination via BGP" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    The path that an HTTP request takes between autonomous systems communicating via BGP.&lt;br&gt;&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;The Internet is the greatest decentralized network in the history of mankind. There is no central authority that controls who is allowed to connect or how you can use it. How do we manage billions of internet-connected devices without any single organization in charge of maintaining the system? This is accomplished through the coordination of many independent entities called Autonomous Systems (AS). ASs are the networking backbone of the internet. These entities, often managed by ISPs or large technology organizations, are responsible for handling traffic for the IP addresses they manage and advertising the paths to all other IP prefixes of which they are aware. Connections between ASs, also known as peers, are established through the manual configuration of connections between routers.&lt;/p&gt;

&lt;p&gt;Once a connection has been established, ASs use the Border Gateway Protocol (BGP) to coordinate with each other. BGP carries the information necessary for ASs to determine the optimal path to a target. Many factors can be used to define "optimal", but the most common heuristic used is choosing the path with the fewest number of "hops" between ASs before reaching the destination. Each AS will store the best path for all IP prefixes in its routing table. When a route is added or removed, the AS will update its routing table, if necessary, and advertise the change to its peers so they can do the same. Each peer sends keep-alive messages to each other at regular intervals. If an AS does not send a keep-alive message before the configured timeout, the peer will stop advertising the AS as a reachable hop and packets will use a different path to reach the target if it exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Anycast?
&lt;/h2&gt;

&lt;p&gt;An anycast network is two or more servers advertising the same IP address from different locations. Why would we want to do this?&lt;/p&gt;

&lt;p&gt;Serving traffic from a single location often lacks the redundancy required for critical or latency-sensitive applications that serve users around the world. In the worst case of a global round-trip request, you can expect &lt;a href="https://hpbn.co/primer-on-latency-and-bandwidth/" rel="noopener noreferrer"&gt;300 ms of latency&lt;/a&gt; just in transit time. This doesn't account for the time it takes the server to process the request. Chain a few of these requests together and you're looking at a very sluggish application and a frustrating user experience. For applications that require high availability and low latency, you will want an architecture that has the following characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Globally distributed&lt;/li&gt;
&lt;li&gt;Automatically routes traffic to the optimal server&lt;/li&gt;
&lt;li&gt;Tolerates failures of individual servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can leverage the advanced routing capabilities of BGP to create a fault-tolerant, low-latency system by building an anycast network. By advertising an IP from many servers around the world, we can rely on BGP to route users' requests to the nearest Point of Presence (PoP) -- or server, in other words -- that advertises the IP. When one of those PoPs fails, it will stop sending keep-alive messages to its peers, connected ASs will stop advertising the path, and traffic will reroute to a healthy PoP.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Render Uses Anycast
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmg2ue5vgcbqwvwlgx1x.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fzmg2ue5vgcbqwvwlgx1x.jpeg" alt="A diagram describing how Render uses anycast to route traffic to the proper origin" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    How Render uses anycast to route traffic to the proper origin&lt;br&gt;&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;When you deploy a web service or static site on Render, you always get a subdomain of &lt;code&gt;onrender.com&lt;/code&gt; that makes your service accessible from the internet. In many cases, users want to use a custom domain that they own instead of an &lt;code&gt;onrender.com&lt;/code&gt; subdomain. Users can create a DNS &lt;code&gt;A&lt;/code&gt; record for their custom domain that points to an IP address provided by Render.&lt;/p&gt;

&lt;p&gt;Render strives to create the most delightful developer experience possible. One way we do this is by making it as simple as possible to configure your DNS settings for custom domains. We provide a single IP address (our anycast IP address) to use in your DNS settings. When a request reaches one of the servers advertising that IP, we will forward it to one of possibly several origins around the world, depending on where your site is hosted and whether it's a web service or a static site.&lt;/p&gt;

&lt;p&gt;We can't make any assumptions about where traffic will originate, and we can minimize latency by routing requests to the server closest to the end-user that's capable of responding to the request. For web services, the request always needs to go to the origin server--the server you've deployed to. But for static sites, we can rely on BGP to route requests to the server closest to the end-user; we advertise our IP from anycast servers worldwide, which inspect each request and forward it to a server capable of responding. So, the server that serves a request is one of hundreds of geographically distributed CDN edge servers containing a cached copy of your static site. If one of those servers fails, its peers will stop advertising the route and the traffic will be sent elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an Anycast Network
&lt;/h2&gt;

&lt;p&gt;Now that we understand the components that make up an anycast network, as well as the use cases for it, we're ready to dive into how you can build your own. At a high level, the steps for setting up anycast are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Obtain an Autonomous System Number&lt;/li&gt;
&lt;li&gt;Acquire an IP Address&lt;/li&gt;
&lt;li&gt;Find a Hosting Provider&lt;/li&gt;
&lt;li&gt;Configure BGP&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Obtain an Autonomous System Number
&lt;/h3&gt;

&lt;p&gt;The first step for setting up an anycast network is getting an autonomous system number (ASN). An ASN is a unique identifier that other ASs will use when announcing a path to reach you via BGP. Having an ASN is also a prerequisite for acquiring an IP address. ASNs are managed by five &lt;a href="https://en.wikipedia.org/wiki/Regional_Internet_registry" rel="noopener noreferrer"&gt;Regional Internet Registries&lt;/a&gt;. As the name suggests, each registry is responsible for managing the ASNs within specific geographic regions of the world.&lt;/p&gt;

&lt;p&gt;Each registry has different requirements for acquiring an ASN. If you’re based in the United States, you'll work with &lt;a href="https://www.arin.net/" rel="noopener noreferrer"&gt;ARIN&lt;/a&gt; to get yours. Their process is straightforward. You just need to &lt;a href="https://www.arin.net/resources/guide/request/#asn" rel="noopener noreferrer"&gt;submit a request&lt;/a&gt; with some supporting documentation along with an explanation of why you need the ASN. ARIN grants ASNs to organizations for two purposes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unique Routing Policy: Organizations who want to define routing policies that differ from their connected peers.&lt;/li&gt;
&lt;li&gt;Multi-homed: Organizations that will be peering with multiple providers. Multi-homing is usually done to improve coverage and resiliency to failures of individual providers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When Render applied for an ASN, we wanted the ability to advertise from multiple providers, so we used multi-homing as a justification for receiving an ASN. After submitting our application, ARIN responded with our ASN within a week.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqaha8ndrpaym2edi07p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqaha8ndrpaym2edi07p.png" alt="A screenshot showing a whois lookup for Render's ASN" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Acquire an IP Address
&lt;/h3&gt;

&lt;p&gt;Now that you have an ASN, you can acquire an IP block to advertise. The minimum &lt;a href="https://www.ripe.net/about-us/press-centre/understanding-ip-addressing" rel="noopener noreferrer"&gt;IP prefix&lt;/a&gt; that can be advertised is a &lt;code&gt;/24&lt;/code&gt; for IPv4 and a &lt;code&gt;/48&lt;/code&gt; for IPv6. This limit exists to prevent the routing tables that ASs need to manage from growing too large.&lt;/p&gt;

&lt;p&gt;You are going to need to decide whether you want to buy or lease your IP block. The dynamics at play here are similar to buying and leasing most things. Buying an IP block comes with a large upfront cost, while leasing requires less capital at the start but you will be paying a fee for as long as you use the IP block. Leasing also comes with additional terms and conditions on how you can use the IP block. If you have the capital and intend to use the IP block for an extended period of time, it likely makes sense to buy. Almost all available IPv4 addresses have been allocated and demand for them continues to rise. You can expect the cost to obtain them to continue to grow until IPv6 adoption is widespread enough to justify dropping support for IPv4.&lt;/p&gt;

&lt;p&gt;Since we knew that we would need the IP block for the foreseeable future, we decided to buy one. Asking all of our users to update their DNS records would be extremely disruptive, so we wanted to ensure that our IP block would remain stable for as long as it was in use. We had a good experience using &lt;a href="https://ipv4.global/" rel="noopener noreferrer"&gt;IPv4 Global&lt;/a&gt; to purchase our IP block. The remainder of this post will assume that you have chosen to buy instead of lease.&lt;/p&gt;

&lt;p&gt;There are a couple of things you should verify before buying an IP block. The block needs to be registered with the Regional Internet Registry that you acquired your ASN from, or it must be transferable to them. You will also want to verify that the IP addresses in the block are in good standing. Some IP addresses have been used for nefarious purposes and end up on widely-used blocklists. There are many free &lt;a href="https://whatismyipaddress.com/blacklist-check" rel="noopener noreferrer"&gt;services&lt;/a&gt; available that will check an IP address against the most popular databases of bad IPs.&lt;/p&gt;

&lt;p&gt;Once you purchase your IP block, you will need to go through the process of transferring ownership from the previous holder of the IP block. The service you use to purchase the block will likely guide you through that process, which will involve working with your Regional Internet Registry to transfer ownership. The exact steps will depend on the registry and whether the block needs to be transferred between registries. You can look at &lt;a href="https://www.arin.net/resources/registry/transfers/" rel="noopener noreferrer"&gt;ARIN's documentation&lt;/a&gt; to get an idea of what's involved in the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Find a Hosting Provider
&lt;/h3&gt;

&lt;p&gt;Now that we have an IP address to advertise, it's time to determine what providers we will use to advertise it. Not all providers support BGP, so you're going to want to find ones that have the capability. In our anycast network setup process, we decided to go with &lt;a href="https://www.vultr.com/" rel="noopener noreferrer"&gt;Vultr&lt;/a&gt; and &lt;a href="https://metal.equinix.com/" rel="noopener noreferrer"&gt;Equinix Metal&lt;/a&gt;.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; The latter is a bare metal provider rather than a VPS provider, so you should verify whether that makes sense for your use case before deciding to go with them. We chose Vultr because they had global coverage, low cost, and thorough documentation on how to set up BGP. You can find discussions comparing VPS providers on forums like &lt;a href="https://www.lowendtalk.com/discussion/120476/vps-providers-with-bgp-sessions" rel="noopener noreferrer"&gt;LowEndTalk&lt;/a&gt; to determine what provider makes sense for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure BGP
&lt;/h3&gt;

&lt;p&gt;The next step is to set up your server and start accepting traffic for your anycast IP address. After getting your application running on the server, you can get started on configuring BGP.&lt;/p&gt;

&lt;p&gt;When we configured BGP, we first needed to submit an application to Vultr requesting that they enable BGP for our account. They had us send a &lt;a href="https://www.vultr.com/docs/example-letter-of-authorization-for-bgp-announcements" rel="noopener noreferrer"&gt;Letter of Authorization&lt;/a&gt; that allowed them to advertise our IP address from their platform. As a part of this process, Vultr created a record in the &lt;a href="https://www.arin.net/resources/manage/irr/" rel="noopener noreferrer"&gt;Internet Routing Registry&lt;/a&gt; on our behalf that signals to the rest of the internet how to handle traffic for our IP. It's a great UX to have Vultr take care of this for you. However, it’s important to note that Vultr will delete this record if you end up migrating away from them. You must create a new record before they delete their copy to avoid any service disruptions.&lt;/p&gt;

&lt;p&gt;The next step is configuring BGP on your server. You're going to need to choose a BGP daemon to handle the advertisement. The two most common are &lt;a href="https://www.quagga.net/" rel="noopener noreferrer"&gt;Quagga&lt;/a&gt; and &lt;a href="https://bird.network.cz/" rel="noopener noreferrer"&gt;BIRD&lt;/a&gt;. We decided to use BIRD since Vultr recommended it and they have &lt;a href="https://www.vultr.com/docs/configuring-bgp-on-vultr" rel="noopener noreferrer"&gt;excellent documentation&lt;/a&gt; for getting it running.&lt;/p&gt;

&lt;p&gt;After installing BIRD, create a file called &lt;code&gt;/etc/bird.conf&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router id 203.0.113.123 # IP of the Vultr server running BIRD

protocol bgp vultr
{
    local as 123456; # your ASN
    source address 203.0.113.123;
    import none;
    export all;
    multihop 2;
    neighbor 169.254.169.254 as 64515; # Vultr’s peering information
    password "my-secret-password"; # BGP password provided by Vultr
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After restarting BIRD, your server will establish a peering session with Vultr’s AS. At this point, your server is capable of transmitting packets with the rest of the internet. Now you just need to tell the rest of the internet that you’re here! The final step is to announce your route. Append the following to the same &lt;code&gt;bird.conf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protocol static
{
        route 123.4.567.890/24 via 203.0.113.123; # your IP via Vultr’s IP
}

protocol device
{
        scan time 5;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart BIRD again to load the new configuration. At this point, your BGP server is announcing the route to Vultr, who is in turn announcing the route to their peers (who then announce it to &lt;em&gt;their&lt;/em&gt; peers). The process continues until your new route propagates across the internet. Your server can now serve traffic for your IP! Repeat these same steps with a second server in another region and you have your very own anycast network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While this post provides the information needed to get your anycast network running, you may still need to adjust your settings to fine-tune the traffic patterns for your network. As mentioned previously, ASs usually choose the path with the fewest number of hops to a destination. Fewer hops does not necessarily mean it will be faster if, for example, one of those hops crosses the Atlantic Ocean. If you notice suboptimal performance, you may want to look into changing the traffic patterns with tools like &lt;a href="https://www.noction.com/blog/understanding-bgp-communities" rel="noopener noreferrer"&gt;communities&lt;/a&gt; and &lt;a href="https://www.noction.com/blog/as-path-and-as-path-prepending" rel="noopener noreferrer"&gt;prepending&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Anycast networks are a powerful tool that leverages the networking infrastructure of the internet to create resilient and performant systems. I hope this post was insightful enough to add another tool to your toolbelt when building reliable systems.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;We don't use Vultr or Equinix Metal anymore because of how our Anycast needs have evolved; we'll discuss our new setup in another blog post! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>devops</category>
      <category>learning</category>
      <category>howto</category>
      <category>networking</category>
    </item>
  </channel>
</rss>
