<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Casey Juanxi Li</title>
    <description>The latest articles on DEV Community by Casey Juanxi Li (@sometimescasey).</description>
    <link>https://dev.to/sometimescasey</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%2Fuser%2Fprofile_image%2F387408%2Fd7ded91c-93c4-4c89-a8cb-18c9a428c32e.jpeg</url>
      <title>DEV Community: Casey Juanxi Li</title>
      <link>https://dev.to/sometimescasey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sometimescasey"/>
    <language>en</language>
    <item>
      <title>Adding Isso comments to a Ghost blog on AWS Lightsail</title>
      <dc:creator>Casey Juanxi Li</dc:creator>
      <pubDate>Sun, 06 Dec 2020 22:56:19 +0000</pubDate>
      <link>https://dev.to/sometimescasey/adding-isso-comments-to-a-ghost-blog-on-aws-lightsail-5ea2</link>
      <guid>https://dev.to/sometimescasey/adding-isso-comments-to-a-ghost-blog-on-aws-lightsail-5ea2</guid>
      <description>&lt;p&gt;I really like &lt;a href="https://posativ.org/isso/" rel="noopener noreferrer"&gt;Isso&lt;/a&gt;. It's a free, open-source, self-hosted comment server. It's a great lightweight alternative to Disqus, which contains too many trackers.&lt;/p&gt;

&lt;p&gt;This post will walk through adding Isso to a new project of mine, &lt;a href="https://affect.blog" rel="noopener noreferrer"&gt;affect.blog&lt;/a&gt;. This is a &lt;a href="https://dev.to/sometimescasey/setting-up-a-new-ghost-blog-for-3-50-a-month-with-aws-lightsail-42im"&gt;low-cost Ghost blog, hosted on AWS Lightsail&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-reqs
&lt;/h2&gt;

&lt;p&gt;The process is quite easy, but requires basic familiarity with the &lt;a href="https://ubuntu.com/tutorials/command-line-for-beginners#1-overview" rel="noopener noreferrer"&gt;Linux shell&lt;/a&gt; and editing Ghost template HTML.&lt;/p&gt;

&lt;p&gt;If you have never edited files via the Linux command line before, I suggest using &lt;a href="https://staffwww.fullcoll.edu/sedwards/Nano/IntroToNano.html" rel="noopener noreferrer"&gt;nano&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Isso and set PATH
&lt;/h2&gt;

&lt;p&gt;From the Lightsail console, SSH into your instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgsz0834ih88znkgalpqu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgsz0834ih88znkgalpqu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The official Isso docs recommend using &lt;code&gt;virtualenv&lt;/code&gt;, but I didn't bother because I have no other Python services running on the machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get install python-dev sqlite3 build-essential
pip install isso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may install the &lt;code&gt;isso&lt;/code&gt; executable to a non-standard directory which won't be on your PATH. You'll be notified of this location.&lt;/p&gt;

&lt;p&gt;In my case it was &lt;code&gt;/home/bitnami/.local/bin&lt;/code&gt;, so I added the following to my &lt;code&gt;~/.bashrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PATH="/home/bitnami/.local/bin:$PATH"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you missed the path, run &lt;code&gt;pip show isso&lt;/code&gt; and look at the &lt;code&gt;Location&lt;/code&gt;. The &lt;code&gt;bin/isso&lt;/code&gt; executable should be somewhere along that path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bitnami@ip-123-45-6-78:~&lt;span class="nv"&gt;$ &lt;/span&gt;pip show isso
Name: isso
Version: 0.12.2
Summary: lightweight Disqus alternative
Home-page: https://github.com/posativ/isso/
Author: Martin Zimmermann
Author-email: info@posativ.org
License: MIT
Location: /home/bitnami/.local/lib/python2.7/site-packages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When that's done (don't forget to &lt;code&gt;source ~/.bashrc&lt;/code&gt; if you changed it), verify that you can run &lt;code&gt;isso&lt;/code&gt; from the shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;bitnami@ip-123-45-6-78:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;isso
&lt;span class="go"&gt;usage: isso [-h] [--version] [-c /etc/isso.conf] {import,run} ...
isso: error: too few arguments
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great. The Isso executable is working, and it's telling us that it expects a config file. &lt;/p&gt;

&lt;h2&gt;
  
  
  Make a config file
&lt;/h2&gt;

&lt;p&gt;I made mine in the home directory for simplicity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~
touch isso.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;isso.cfg&lt;/code&gt; I pasted the following:&lt;br&gt;
&lt;/p&gt;

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

dbpath = /home/bitnami/comments.db

host = https://affect.blog

[server]
listen = http://localhost:8080
public-endpoint = https://affect.blog/isso/
reload = off
profile = off

[guard]
enabled = true
ratelimit = 2
require-author = false
require-email = false
direct-reply = 10
reply-to-self = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it a whirl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isso -c ~/isso.cfg run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;bitnami@ip-123-45-6-78:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;isso &lt;span class="nt"&gt;-c&lt;/span&gt; isso.cfg run
&lt;span class="go"&gt;2020-12-06 20:35:48,893 INFO: connected to https://affect.blog
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great. Isso is now running in the foreground.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move isso to a background process
&lt;/h2&gt;

&lt;p&gt;More on &lt;a href="https://posativ.org/isso/docs/install/#init-scripts" rel="noopener noreferrer"&gt;init scripts&lt;/a&gt; later, but for now let's just get things working.&lt;/p&gt;

&lt;p&gt;We don't want the Isso process to die when we close the terminal, so hit &lt;strong&gt;Ctrl-Z&lt;/strong&gt; to suspend it, and then run &lt;code&gt;bg&lt;/code&gt; to move it into the background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;bitnami@ip-123-45-6-78:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;isso &lt;span class="nt"&gt;-c&lt;/span&gt; isso.cfg run
&lt;span class="go"&gt;2020-12-06 20:35:48,893 INFO: connected to https://affect.blog
^Z
[1]+  Stopped                 isso -c isso.cfg run
&lt;/span&gt;&lt;span class="gp"&gt;bitnami@ip-123-45-6-78:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bg&lt;/span&gt;
&lt;span class="go"&gt;[1]+ isso -c isso.cfg run &amp;amp;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now safely close the Lightsail terminal, and Isso will keep running at port 8080 (or your port of choice).&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Apache proxy
&lt;/h2&gt;

&lt;p&gt;Isso is now running at port 8080, but we need to proxy it to a public url.&lt;/p&gt;

&lt;p&gt;The Isso documentation gives an example for Nginx, but the Lightsail Bitnami Ghost stack uses Apache. Luckily, I was able to find an example Apache config &lt;a href="https://danuker.go.ro/installing-isso-on-debian-apache.html#apache" rel="noopener noreferrer"&gt;in this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Similar to this author, I wanted to serve Isso at &lt;code&gt;affect.blog/isso/&lt;/code&gt; instead of a new subdomain (&lt;code&gt;isso.affect.blog&lt;/code&gt;). This is to avoid having to make a new SSL certificate.&lt;/p&gt;

&lt;p&gt;Add the following lines to the end of &lt;code&gt;/opt/bitnami/apache2/conf/httpd.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proxy for Isso commenting
&lt;/span&gt;&lt;span class="n"&gt;ProxyPass&lt;/span&gt; /&lt;span class="n"&gt;isso&lt;/span&gt;/ &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;8080&lt;/span&gt;/
&lt;span class="n"&gt;ProxyPassReverse&lt;/span&gt; /&lt;span class="n"&gt;isso&lt;/span&gt;/ &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;8080&lt;/span&gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;sudo /opt/bitnami/ctlscript.sh restart&lt;/code&gt; or the &lt;code&gt;bn-helper&lt;/code&gt; tool to stop and restart all your services. Now verify that Isso is accessible at &lt;code&gt;https://affect.blog/isso/&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F09o0apddzc128rqx1btf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F09o0apddzc128rqx1btf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is actually great! The request fails because we haven't passed a uri, but we are successfully serving up Isso. (If this URL were set up incorrectly, we would see a "404 Page Not Found" handler from Ghost.)&lt;/p&gt;

&lt;p&gt;You can verify that the expected JS resource is available at &lt;code&gt;/isso/js/embed.min.js&lt;/code&gt;. This should load a minified file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Friegkdw39gp3xqr61mus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Friegkdw39gp3xqr61mus.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inject Isso script to header
&lt;/h2&gt;

&lt;p&gt;The quickest way to inject the Isso script into the header of your entire site is to use Ghost's &lt;strong&gt;Code Injection&lt;/strong&gt; feature:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F25nnjrsyje5o8htc99bu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F25nnjrsyje5o8htc99bu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Drop your script tag in here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;data-isso=&lt;/span&gt;&lt;span class="s"&gt;"//affect.blog/isso/"&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"//affect.blog/isso/js/embed.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fehqg3c49gmlq8ra6289t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fehqg3c49gmlq8ra6289t.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick page test
&lt;/h2&gt;

&lt;p&gt;Make a new page to test and add the following HTML snippet. (We'll put this in a post template later):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"isso-thread"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fckizjlorcgzvk3fnfxag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fckizjlorcgzvk3fnfxag.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp6cpynkmi95vow15s60v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp6cpynkmi95vow15s60v.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The HTML tag won't render to anything visible in the editor, but don't worry. Save the page, publish it, and view. If all went well you should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F72b7edsyhtu1mf95wcpp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F72b7edsyhtu1mf95wcpp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congrats! Your Isso comment server is working.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding comments to a post template
&lt;/h2&gt;

&lt;p&gt;Of course you don't want comments on just one page, but on all of your posts. This is achieved by adding the above tag to your post template. Ghost provides &lt;a href="https://ghost.org/tutorials/custom-page-templates/" rel="noopener noreferrer"&gt;detailed instructions here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The ideal place to do this will depend on your Ghost template. Here's how I did mine:&lt;/p&gt;
&lt;h3&gt;
  
  
  Download your theme to a .zip and unzip it:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5l6n8sltrmd6cixemrx4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5l6n8sltrmd6cixemrx4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Find post.hbs, make a copy, and edit
&lt;/h3&gt;

&lt;p&gt;You must start the new filename with &lt;code&gt;custom-&lt;/code&gt; in order for Ghost to pick up the template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz8ssh83pcnd26as1dw38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz8ssh83pcnd26as1dw38.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Insert your Isso section
&lt;/h3&gt;

&lt;p&gt;I opted to remove the default Disqus section and replace it with my Isso section instead. I also placed a new CSS class on the section to make it easy to style these comments using Code Injection:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb65vzvz3o97mf18exo3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb65vzvz3o97mf18exo3k.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Femwijrnirmup8mmrkio6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Femwijrnirmup8mmrkio6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Re-upload theme, and apply custom template
&lt;/h3&gt;

&lt;p&gt;Now zip up your edited theme using the same name, upload it (overwrite your current theme), and change one of your posts to use the new custom template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpamgn54dufb11zq10xy2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpamgn54dufb11zq10xy2.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkzf32ue3wysbdgc8ukz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkzf32ue3wysbdgc8ukz4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give the post a refresh, and voila - here is the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1rv7v9f348olozc8753p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1rv7v9f348olozc8753p.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything looks good, you can either update all your current and future posts to use the new template, or apply the change to &lt;code&gt;post.hbs&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Note: Isso loads and stores comments based on page URL
&lt;/h2&gt;

&lt;p&gt;The slug is the portion of a post's URL after the main slash:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4uk9ov0yxlkj5rd3cj6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4uk9ov0yxlkj5rd3cj6g.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that if you add some comments to a page and then change its &lt;strong&gt;slug&lt;/strong&gt;, the comments stored under the old slug will no longer appear for that page.&lt;/p&gt;

&lt;p&gt;The comments are still in the database, however. Deleting or editing a Ghost page does not affect its Isso comments at all. &lt;/p&gt;

&lt;p&gt;If you change the slug back, or make a new page with the same slug, the comments will re-appear.&lt;/p&gt;
&lt;h2&gt;
  
  
  Use a process manager to keep it running
&lt;/h2&gt;

&lt;p&gt;Running Isso as one process in the background was fine to get started, but is not great for long-term use. You don't want your blog to be left comment-less if the process dies when you're not around to restart it.&lt;/p&gt;

&lt;p&gt;The Isso docs &lt;a href="https://posativ.org/isso/docs/install/#id10" rel="noopener noreferrer"&gt;have some suggestions&lt;/a&gt;. I had trouble using systemd, but &lt;a href="http://supervisord.org/installing.html" rel="noopener noreferrer"&gt;supervisor&lt;/a&gt; did the trick. I placed the conf file my home directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install supervisord
echo_supervisord_conf &amp;gt; /home/bitnami/supervisord.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this &lt;code&gt;[program]&lt;/code&gt; section to &lt;strong&gt;supervisord.conf&lt;/strong&gt; (&lt;a href="https://github.com/posativ/isso/issues/47" rel="noopener noreferrer"&gt;reference&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[program:isso]
command = /home/bitnami/.local/bin/isso -c /home/bitnami/isso.cfg run
directory = /home/bitnami/isso
user = bitnami
autostart = true
autorestart = true
stdout_logfile = /home/bitnami/isso/isso.log
stderr_logfile = /home/bitnami/isso/isso_err.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And kick it all off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;supervisord -c /home/bitnami/supervisord.conf
supervisorctl start isso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check that all is working as intended, find the process running on port 8080, kill it, and verify that it restarts immediately with a new PID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;bitnami@ip-123-45-6-78:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;netstat &lt;span class="nt"&gt;-lpn&lt;/span&gt; |grep :8080
&lt;span class="go"&gt;tcp        0      0 127.0.0.1:8080          0.0.0.0:*
LISTEN      14196/python        
&lt;/span&gt;&lt;span class="gp"&gt;bitnami@ip-123-45-6-78:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;kill &lt;/span&gt;14196
&lt;span class="gp"&gt;bitnami@ip-123-45-6-78:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;netstat &lt;span class="nt"&gt;-lpn&lt;/span&gt; |grep :8080
&lt;span class="go"&gt;tcp        0      0 127.0.0.1:8080          0.0.0.0:*
LISTEN      14417/python        
&lt;/span&gt;&lt;span class="gp"&gt;bitnami@ip-123-45-6-78:~$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👍&lt;/p&gt;




&lt;p&gt;That's it!&lt;/p&gt;

&lt;p&gt;I was really happy to find a free, open-source commenting solution, without the bloat and privacy concerns of Disqus.&lt;/p&gt;

&lt;p&gt;If you were considering using Isso for your Ghost blog, I hope this post has helped. Please feel free to leave questions and comments below!&lt;/p&gt;

</description>
      <category>isso</category>
      <category>aws</category>
      <category>ghost</category>
      <category>lightsail</category>
    </item>
    <item>
      <title>Setting up a new Ghost blog for $3.50 a month with AWS Lightsail</title>
      <dc:creator>Casey Juanxi Li</dc:creator>
      <pubDate>Wed, 14 Oct 2020 00:17:27 +0000</pubDate>
      <link>https://dev.to/sometimescasey/setting-up-a-new-ghost-blog-for-3-50-a-month-with-aws-lightsail-42im</link>
      <guid>https://dev.to/sometimescasey/setting-up-a-new-ghost-blog-for-3-50-a-month-with-aws-lightsail-42im</guid>
      <description>&lt;p&gt;At $3.50 per month, running a Ghost blog on the smallest AWS Lightsail machine using a Bitnami image is an attractive alternative to paying $29 per month for Ghost's Basic plan. I prefer Ghost to Wordpress - it's lighter and prettier. 😏&lt;/p&gt;

&lt;p&gt;The documentation at &lt;a href="https://aws.amazon.com/blogs/compute/building-a-photo-diary-ghost-on-amazon-lightsail/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/compute/building-a-photo-diary-ghost-on-amazon-lightsail/&lt;/a&gt; gets you 90% there in an hour or so, with a custom domain. It does have a few typos and odd quirks - I'll make a note of my hiccups:&lt;/p&gt;

&lt;h1&gt;
  
  
  "Not Secure": setting up https
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flrwct3hl9044ilb3oqd7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flrwct3hl9044ilb3oqd7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without a TLS or SSL certificate you get that sketchy little "Not Secure" notification in your browser bar - which is not really acceptable nowadays. 😷&lt;/p&gt;

&lt;p&gt;The documentation points you to a tutorial for setting up a certificate using Let's Encrypt in relation to NGINX, which is sort of correct, but requires some tweaks. Read on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3eijo83vbodeaywpn2ma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3eijo83vbodeaywpn2ma.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  /opt/bitnami
&lt;/h2&gt;

&lt;p&gt;SSH into your instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fojf3bb8th0m51ocwxhwa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fojf3bb8th0m51ocwxhwa.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every useful tool can be found at &lt;code&gt;/opt/bitnami&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(Confusingly, there is also a &lt;code&gt;/home/bitnami/stack/&lt;/code&gt; with similar contents. Do not run tools from there.)&lt;/p&gt;

&lt;p&gt;Run the &lt;code&gt;bnhelper-tool&lt;/code&gt; as a superuser at &lt;code&gt;/opt/bitnami&lt;/code&gt;. You'll need sudo privileges for many of the downstream tools that it runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /opt/bitnami
sudo ./bnhelper-tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Set up a Let's Encrypt certificate for HTTPS
&lt;/h2&gt;

&lt;p&gt;"Set up Let's Encrypt" runs the &lt;code&gt;bncert-tool&lt;/code&gt;, which you can also just run directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhqlm6ysyzg6oj2i8bono.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhqlm6ysyzg6oj2i8bono.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you get an error saying ...&lt;code&gt;server.crt' does not exist or is empty&lt;/code&gt;, you'll need to generate it using Certbot and symlink the created &lt;code&gt;.pem&lt;/code&gt; files. Follow Steps 2-7 in the &lt;a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-using-lets-encrypt-certificates-with-nginx" rel="noopener noreferrer"&gt;Amazon documentation&lt;/a&gt;, but keep reading as we'll need to adapt the tutorial for Ghost.&lt;/p&gt;

&lt;p&gt;To pass the DNS challenges, add your TXT records under &lt;strong&gt;Home&lt;/strong&gt; &amp;gt; &lt;strong&gt;Networking&lt;/strong&gt; &amp;gt; &lt;strong&gt;DNS Zones&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flpuwr4cnwq5nzvs1je3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flpuwr4cnwq5nzvs1je3v.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After step 6, Certbot will generate two &lt;code&gt;.pem&lt;/code&gt; files which need to be symlinked to the location where the &lt;code&gt;bncert-tool&lt;/code&gt; will look for them. Note that this differs from the NGINX-specific path given in Step 7 of the Amazon tutorial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ln -s /etc/letsencrypt/live/$DOMAIN/privkey.pem /opt/bitnami/apps/ghost/conf/certs/server.key
sudo ln -s /etc/letsencrypt/live/$DOMAIN/fullchain.pem /opt/bitnami/apps/ghost/conf/certs/server.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your &lt;code&gt;server.crt&lt;/code&gt; is in the right place, run the &lt;code&gt;bncert-tool&lt;/code&gt;, and you should be on your way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start/stop services
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;bnhelper-tool&lt;/code&gt; you can also start/stop/restart your services (ghost, apache, mysql). This is just a wrapper for &lt;code&gt;./ctlscript.sh status&lt;/code&gt;, &lt;code&gt;./ctlscript.sh start&lt;/code&gt;, &lt;code&gt;./ctlscript.sh stop&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbfkfh13x6i5nk0sshokl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbfkfh13x6i5nk0sshokl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F200e6rp16r8x5z1il53k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F200e6rp16r8x5z1il53k.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Remove Bitnami "Manage" banner
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd9qd1rhlxff3kje3ikfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd9qd1rhlxff3kje3ikfm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also remove the little "Manage" banner that appears in the bottom right. This actually runs &lt;code&gt;sudo /opt/bitnami/apps/wordpress/bnconfig --disable_banner 1&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkfdba5in55z077443e1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkfdba5in55z077443e1h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may get a &lt;code&gt;child process exited abnormally&lt;/code&gt; error. Inexplicably, something that I did caused bnconfig to be renamed to &lt;code&gt;bnconfig.disabled&lt;/code&gt;. I'm not sure why.&lt;/p&gt;

&lt;p&gt;Renaming it back does the trick - I had to do this a couple of times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mv /opt/bitnami/apps/ghost/bnconfig.disabled bnconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Serve it via a CloudFront distribution
&lt;/h2&gt;

&lt;p&gt;The final thing which isn't mentioned in the tutorial is serving the site via a CloudFront distribution. If you've never used a CDN, the quick and dirty idea is that multiple copies of your site are stored around the world for quick access from any browser - and these copies are updated on a regular basis.&lt;/p&gt;

&lt;p&gt;Fortunately, AWS' &lt;a href="https://aws.amazon.com/blogs/compute/improving-website-performance-with-lightsail-content-delivery-network/" rel="noopener noreferrer"&gt;documentation for this&lt;/a&gt; is up to date. A CloudFront distribution will add $2.50 per month to your costs, though the entire first &lt;em&gt;year&lt;/em&gt; is free - not bad.&lt;/p&gt;

&lt;h2&gt;
  
  
  To sum up
&lt;/h2&gt;

&lt;p&gt;All in all, this is much easier than setting up from scratch on AWS (trying to tie together Ghost, EC2, Route 53, CloudFront, etc). Lightsail puts all these button clicks in the same place and provides a handy Bitnami image which (almost) works perfectly out of the box. The documentation isn't perfect, but it'll do.&lt;/p&gt;

&lt;p&gt;If $29/month for Ghost seems a little pricey for a blog (which it did to me), this is a great way to still have a self-hosted setup on the cheap, with minimal config faff.&lt;/p&gt;

&lt;p&gt;Any questions, feel free to ask below!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ghost</category>
      <category>lightsail</category>
    </item>
    <item>
      <title>Firebase Cloud Firestore permissions: don't do "allow read, write: if true;"</title>
      <dc:creator>Casey Juanxi Li</dc:creator>
      <pubDate>Mon, 17 Aug 2020 13:30:16 +0000</pubDate>
      <link>https://dev.to/sometimescasey/firebase-cloud-firestore-permissions-don-t-do-allow-read-write-if-true-121b</link>
      <guid>https://dev.to/sometimescasey/firebase-cloud-firestore-permissions-don-t-do-allow-read-write-if-true-121b</guid>
      <description>&lt;p&gt;Say you're using Firebase with Cloud Firestore to handle user login and registration for a React Native app. You have the following handler for a user registration button: (Credit: &lt;a href="https://www.freecodecamp.org/news/react-native-firebase-tutorial/"&gt;this freeCodeCamp tutorial&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;onRegisterPress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;confirmPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Passwords don't match.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;firebase&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createUserWithEmailAndPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nx"&gt;usersRef&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&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;navigation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
                    &lt;span class="p"&gt;})&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;firebase.auth().createUserWithEmailAndPassword()&lt;/code&gt; method is called to perform the actual user creation. After hitting the button you can see your new user being added to the Firebase console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z9LBYriR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6or2yubzp9nryjrpt7x8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z9LBYriR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6or2yubzp9nryjrpt7x8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what if you hit the following error?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FirebaseError: [code=permission-denied]: Missing or insufficient permissions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cpO-3hAq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rq0w66vwnnb4ebipgnni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cpO-3hAq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rq0w66vwnnb4ebipgnni.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many &lt;a href="https://stackoverflow.com/questions/56510745/firebaseerror-code-permission-denied-missing-or-insufficient-permissions"&gt;top-voted answers&lt;/a&gt; on StackOverflow recommend setting unsafe rules to solve the problem. Here's a common example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  } 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is bad. &lt;code&gt;allow read, write: if true;&lt;/code&gt; does exactly what it says: It allows everyone (yes, everyone on the internet) to read and write to any document in your Firebase store. &lt;strong&gt;This is not appropriate for production&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Despite these warnings, such answers still float to the top of every StackOverflow thread on the topic, for the simple reason that it "solves" the problem for "testing purposes". But what happens after "testing"?&lt;/p&gt;

&lt;p&gt;I found the mess of answers and the official &lt;a href="https://firebase.google.com/docs/rules/basics"&gt;Firebase documentation&lt;/a&gt; somewhat confusing to wade through. I hope the following will help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to set rules?
&lt;/h2&gt;

&lt;p&gt;It wasn't obvious to me. They are here (ensure you are in &lt;strong&gt;Cloud Firestore&lt;/strong&gt; and not Realtime Database):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zGHkmub4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5m2l5kzqfzk7mq7lqwqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zGHkmub4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5m2l5kzqfzk7mq7lqwqd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at some of the suggested solutions from StackOverflow or the &lt;a href="https://firebase.google.com/docs/rules/basics"&gt;Firebase documentation&lt;/a&gt; and see what each is actually doing:&lt;/p&gt;

&lt;h2&gt;
  
  
  Default: allow open access for 30 days
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if
          request.time &amp;lt; timestamp.date(2020, 9, 16);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is the default rule set that you are given when you set up your Firebase project and add a Cloud Firebase database: it allows open access to everyone for 30 days, and then will deny access to everyone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---DEI0dRi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zwbif0vb83u0bc03fzyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---DEI0dRi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zwbif0vb83u0bc03fzyu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some answers suggest simply pushing this date forward. This is clearly as bad as setting &lt;code&gt;allow read, write: true&lt;/code&gt;. This is not a permanent solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Allow read/write by any authenticated user
&lt;/h2&gt;

&lt;p&gt;Another common suggestion is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Better, if you are comfortable with any authenticated user being able to read and write to anything. However, I am making a registration handler - which means anyone can make an account and become an authenticated user. Let's keep looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content-owner only access
&lt;/h2&gt;

&lt;p&gt;Firebase documentation then suggests this for content-owner only access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service cloud.firestore {
  match /databases/{database}/documents {
    // Allow only authenticated content owners access
    match /some_collection/{userId}/{documents=**} {
      allow read, write: if request.auth != null &amp;amp;&amp;amp; request.auth.uid == userId
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Seems perfect, right? Except this literal rule won't work for my registration handler either. A mindless copy-paste won't do here: &lt;code&gt;some_collection&lt;/code&gt; does not exist. In fact, in a new Firebase Cloud Firestore, no collections exist:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FMKTMwta--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2dcbyoygf2aor0v1d5se.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FMKTMwta--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2dcbyoygf2aor0v1d5se.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you recall from the handler above, the &lt;code&gt;then()&lt;/code&gt; callback accesses a Firestore collection called &lt;code&gt;users&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const usersRef = firebase.firestore().collection('users')
                usersRef
                    .doc(uid)
                    .set(data)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So the final &lt;a href="https://stackoverflow.com/questions/63272544/how-do-i-set-up-my-firebase-firestore-rules-for-my-personal-portfolio-website"&gt;non-obvious step&lt;/a&gt; is to ensure that your rule and the &lt;code&gt;firebase.firestore().collection()&lt;/code&gt; call are actually referencing the same collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  The collection doesn't need to exist; you just need a rule matching it
&lt;/h2&gt;

&lt;p&gt;There is no need to create an empty &lt;code&gt;users&lt;/code&gt; collection ahead of time. The &lt;code&gt;firebase.firestore().collection('users').doc(uid).set(data)&lt;/code&gt; call simply has to find a matching ruleset. In this case, the match is &lt;code&gt;/users/{userId}/{documents=**}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;users&lt;/code&gt; collection does not exist, it will be created.&lt;/p&gt;

&lt;p&gt;Note that a typo (&lt;code&gt;collection('Users')&lt;/code&gt;, &lt;code&gt;collection('user')&lt;/code&gt;) would result in a permission error - &lt;strong&gt;not because the collection doesn't already exist&lt;/strong&gt;, but because there is &lt;strong&gt;no matching ruleset to allow the write&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can separate read and write rules
&lt;/h2&gt;

&lt;p&gt;And finally, read and write rules can be separated into their own conditions. For example, the following will allow any authenticated user to read data for any document in the &lt;code&gt;users&lt;/code&gt; collection. But they can only write to (create/update/delete) their own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service cloud.firestore {
  match /databases/{database}/documents {
    // Allow only authenticated content owners access
    match /users/{userId}/{documents=**} {
      allow write: if request.auth != null &amp;amp;&amp;amp; request.auth.uid == userId;
      allow read: if request.auth != null;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Lastly, I recommend looking at your newly created documents to understand their structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RvenFu7S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h2dsjnbw3wgofabymfr3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RvenFu7S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h2dsjnbw3wgofabymfr3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the Rules Playground to test rules
&lt;/h2&gt;

&lt;p&gt;Note in this example, authentication with uid=&lt;code&gt;jill&lt;/code&gt; cannot write to the path &lt;code&gt;users/jack&lt;/code&gt;. The line responsible for the write deny is highlighted:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1SKAA9W6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gdft9tn0dlu7mvb84i9b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1SKAA9W6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gdft9tn0dlu7mvb84i9b.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3vMYnvnP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/774yunyuihkm0myvgg19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3vMYnvnP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/774yunyuihkm0myvgg19.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But authentication with uid=&lt;code&gt;jill&lt;/code&gt; can read from path &lt;code&gt;users/jack&lt;/code&gt;, and the line allowing this is highlighted as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AmMt6G5g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g9d4ps7klw0dogk0bx8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AmMt6G5g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g9d4ps7klw0dogk0bx8e.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No more nuclear option
&lt;/h2&gt;

&lt;p&gt;I hope this helped clarify use of Cloud Firestore rules, and allows you to steer away from the unnecessarily broad &lt;code&gt;allow read, write: if true;&lt;/code&gt; option. Please feel free to leave comments below.&lt;/p&gt;

</description>
      <category>react</category>
      <category>reactnative</category>
      <category>firebase</category>
      <category>cloudfirestore</category>
    </item>
  </channel>
</rss>
