<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://abelcreates.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://abelcreates.com/" rel="alternate" type="text/html" /><updated>2026-09-04T04:04:04+00:00</updated><id>https://abelcreates.com/feed.xml</id><title type="html">Abel Creates</title><subtitle>Personal website, portfolio, and writings by Abel.</subtitle><author><name>Abel Martin</name></author><entry><title type="html">Why I Moved from Cloud Run Domain Mappings to Firebase Hosting for Poké Quiz</title><link href="https://abelcreates.com/posts/firebase/" rel="alternate" type="text/html" title="Why I Moved from Cloud Run Domain Mappings to Firebase Hosting for Poké Quiz" /><published>2026-08-17T16:00:00+00:00</published><updated>2026-08-17T16:00:00+00:00</updated><id>https://abelcreates.com/posts/firebase</id><content type="html" xml:base="https://abelcreates.com/posts/firebase/"><![CDATA[<p>When deploying full-stack web applications on Google Cloud Platform, <strong>Google Cloud Run</strong> is easily one of the most powerful and developer-friendly serverless container platforms available. You containerize your application (in my case, a Django + React trivia game called <a href="https://pokequiz.abelcreates.com/">Poké Quiz</a>), push it via GitHub Actions, and get autoscaling, health checks, and instant zero-to-one concurrency.</p>

<p>However, things got turbulent when connecting a custom subdomain (<code class="language-plaintext highlighter-rouge">pokequiz.abelcreates.com</code>).</p>

<p>Here is the story of how an innocent 😇 custom domain mapping spiral led to hours of SSL errors, why Cloud Run’s native domain mapping feature struggled, and how <strong>Firebase Hosting</strong> saved the architecture.</p>

<hr />

<h3 id="tldr">TL;DR</h3>
<p>Use Firebase Hosting for custom domains, SSL termination, global CDN, and peace of mind. It’s free<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> and it just works. 😅</p>

<h2 id="the-symptom-the-infinite-ssl_error_syscall-loop">The Symptom: The infinite <code class="language-plaintext highlighter-rouge">SSL_ERROR_SYSCALL</code> loop</h2>

<p>After deploying Poké Quiz to Cloud Run in <code class="language-plaintext highlighter-rouge">us-central1</code>, I used Cloud Run’s native <strong>Custom Domain Mappings</strong> feature in the Google Cloud Console and created a standard <code class="language-plaintext highlighter-rouge">CNAME</code> record in DNS pointing to <code class="language-plaintext highlighter-rouge">ghs.googlehosted.com</code>.</p>

<p>Testing with <code class="language-plaintext highlighter-rouge">curl</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-I</span> https://pokequiz.abelcreates.com
curl: <span class="o">(</span>35<span class="o">)</span> LibreSSL SSL_connect: SSL_ERROR_SYSCALL <span class="k">in </span>connection to pokequiz.abelcreates.com:443
</code></pre></div></div>

<p>A verbose network inspection revealed a strange behavior:</p>
<ol>
  <li><strong>HTTP (Port 80)</strong> was answering with a <code class="language-plaintext highlighter-rouge">302 Found</code> redirecting to <code class="language-plaintext highlighter-rouge">https://</code>.</li>
  <li><strong>HTTPS (Port 443)</strong> immediately dropped the TCP connection during the TLS <code class="language-plaintext highlighter-rouge">Client Hello</code> handshake (<code class="language-plaintext highlighter-rouge">read: errno=0, no peer certificate available</code>).</li>
</ol>

<p>In the GCP Console, the domain status stayed locked in a spinning state:</p>
<blockquote>
  <p><em>“Waiting for certificate provisioning. You must configure your DNS records for certificate issuance to begin.”</em></p>
</blockquote>

<p>Even after deleting the mapping, re-adding it, and watching the certificate successfully provision for a few hours, the mapping abruptly broke again later that evening, dropping back to <code class="language-plaintext highlighter-rouge">404 Not Found</code> on <code class="language-plaintext highlighter-rouge">ghs.googlehosted.com</code> and throwing <code class="language-plaintext highlighter-rouge">SSL_ERROR_SYSCALL</code>.</p>

<hr />

<h2 id="why-cloud-run-custom-domain-mappings-failed">Why Cloud Run custom domain mappings failed</h2>

<p>Behind the scenes, Cloud Run’s native custom domain mapping feature has been classified in <strong>“Preview”</strong> for years.</p>

<p>Here is why it frequently falls short in production:</p>

<ol>
  <li><strong>Aggressive Exponential Backoff on ACME Verification</strong>:<br />
If you create the domain mapping in GCP <em>before</em> the DNS record has completely propagated worldwide, Google’s automated ACME bot encounters a verification failure on Attempt #1 and enters a multi-hour backoff sleep cycle.</li>
  <li><strong>DNSSEC &amp; CNAME Resolution Latency</strong>:<br />
With DNSSEC enabled, intermediate resolver caches often prevent Google’s backend verifier from resolving the ACME challenge cleanly.</li>
  <li><strong>Flapping Routing Tables on GHS</strong>:<br />
<code class="language-plaintext highlighter-rouge">ghs.googlehosted.com</code> (Google Hosted Services) acts as a shared multi-tenant proxy. When background health checks or reconciliation routines hit transient timeouts, the domain mapping can become unbound, dropping your service back to a generic Google 404 page.</li>
</ol>

<p>In fact, Google’s own official Cloud Run documentation now explicitly advises against using the legacy preview domain mapping for production, steering developers toward <strong>Google Cloud Load Balancing</strong> or <strong>Firebase Hosting</strong>.</p>

<hr />

<h2 id="the-alternative-cloud-load-balancer-vs-firebase-hosting">The Alternative: Cloud Load Balancer vs. Firebase Hosting</h2>

<p>Google provides two primary alternatives:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Solution</th>
      <th style="text-align: left">Monthly Base Cost</th>
      <th style="text-align: left">Setup Complexity</th>
      <th style="text-align: left">CDN &amp; Caching</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Global External Application Load Balancer</strong></td>
      <td style="text-align: left">~$18 - $25/month (Forwarding rules + Serverless NEG)</td>
      <td style="text-align: left">High (Requires IP, SSL Policy, Backend Service, URL Maps)</td>
      <td style="text-align: left">Add-on (Cloud CDN)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Firebase Hosting (Reverse Proxy)</strong></td>
      <td style="text-align: left"><strong>$0 / month (Free Tier)</strong></td>
      <td style="text-align: left">Minimal (Single <code class="language-plaintext highlighter-rouge">firebase.json</code> configuration)</td>
      <td style="text-align: left">Built-in Global Edge CDN</td>
    </tr>
  </tbody>
</table>

<p>For indie projects, showcase tools, and side applications, paying $20+/month just for a basic custom domain SSL termination is unnecessary.</p>

<p><strong>Firebase Hosting</strong> is built directly on top of Google Cloud infrastructure, requires no separate billing, and natively supports <strong>Cloud Run Rewrites</strong>.</p>

<hr />

<h2 id="the-solution-pairing-firebase-hosting-with-cloud-run">The Solution: Pairing Firebase Hosting with Cloud Run</h2>

<p>Instead of fighting the preview domain mapping, Firebase Hosting acts as the <strong>Global Edge CDN and SSL Gateway</strong>, terminating HTTPS with a Let’s Encrypt / Google Trust Services certificate that auto-renews without downtime, and proxying 100% of the traffic directly to the Cloud Run container.</p>

<h3 id="step-1-configure-firebasejson">Step 1: Configure <code class="language-plaintext highlighter-rouge">firebase.json</code></h3>

<p>In the root of the project, we configured Firebase to route all wildcard traffic (<code class="language-plaintext highlighter-rouge">**</code>) directly to our Cloud Run service:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"hosting"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"public"</span><span class="p">:</span><span class="w"> </span><span class="s2">"frontend/dist"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ignore"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"firebase.json"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"**/.*"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"**/node_modules/**"</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"rewrites"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"**"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"run"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"serviceId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pokefun"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"region"</span><span class="p">:</span><span class="w"> </span><span class="s2">"us-central1"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="step-2-configure-direct-a-records">Step 2: Configure Direct <code class="language-plaintext highlighter-rouge">A</code> Records</h3>

<p>In my DNS (Squarespace), instead of a fragile <code class="language-plaintext highlighter-rouge">CNAME</code> chain, we pointed the subdomain directly to Firebase Hosting’s Anycast IP:</p>

<ul>
  <li><strong>Host</strong>: <code class="language-plaintext highlighter-rouge">pokequiz</code></li>
  <li><strong>Type</strong>: <code class="language-plaintext highlighter-rouge">A</code></li>
  <li><strong>Data</strong>: <code class="language-plaintext highlighter-rouge">199.36.158.100</code></li>
</ul>

<h3 id="step-3-automate-in-github-actions-cicd">Step 3: Automate in GitHub Actions CI/CD</h3>

<p>To make the entire deployment continuous and hands-off, I updated <code class="language-plaintext highlighter-rouge">.github/workflows/ci.yml</code> so that every push to <code class="language-plaintext highlighter-rouge">main</code> builds the assets, deploys the container to Cloud Run, and refreshes Firebase Hosting:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build Frontend Assets</span>
        <span class="na">working-directory</span><span class="pi">:</span> <span class="s">./frontend</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">npm ci</span>
          <span class="s">npm run build</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Cloud Run</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">gcloud run deploy pokefun \</span>
            <span class="s">--source . \</span>
            <span class="s">--region us-central1</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Firebase Hosting</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">npx -y firebase-tools deploy --only hosting --project pokequiz-505401</span>
</code></pre></div></div>
<blockquote>
  <p><strong>A quick note on security:</strong> You might notice the GCP project ID (<code class="language-plaintext highlighter-rouge">pokequiz-505401</code>) is written explicitly in the step above. In Google Cloud and Firebase, project IDs are public identifiers rather than secrets. They are already exposed in default hosting domains (<code class="language-plaintext highlighter-rouge">&lt;project-id&gt;.web.app</code>), client-side configuration objects, and DNS records. Actual deployment permissions and resource access are strictly enforced via IAM roles, Workload Identity Federation, and authenticated credentials stored securely in GitHub Secrets.</p>
</blockquote>

<hr />

<h2 id="the-result">The Result</h2>

<p>Running <code class="language-plaintext highlighter-rouge">curl -I https://pokequiz.abelcreates.com</code> now returns:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/2 200 
content-type: text/html; charset=utf-8
strict-transport-security: max-age=31556926
server: Google Frontend
x-served-by: cache-iad-kiad7000110-IAD
</code></pre></div></div>

<ul>
  <li>✅ <strong>Instant SSL handshake</strong>: Auto-managed TLS certificate via Google Trust Services with zero manual renewals.</li>
  <li>✅ <strong>Global CDN caching</strong>: Assets are served from edge points of presence with low latency.</li>
  <li>✅ <strong>Full Container Capability</strong>: Django REST Framework, WebSockets, background APIs, and SQLite/Postgres run seamlessly in Docker on Cloud Run.</li>
  <li>✅ <strong>100% Free &amp; Scalable</strong>: Zero monthly overhead while remaining fully enterprise-scalable.</li>
</ul>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>If you are running containerized apps on Google Cloud Run and need a custom domain, <strong>skip Cloud Run’s legacy Domain Mappings (Preview)</strong>.</p>

<p>Using <strong>Firebase Hosting as a reverse proxy</strong> gives you the best of both worlds: a world-class global CDN with automated SSL at the edge, backed by the raw power of Cloud Run containers behind the scenes.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://cloud.google.com/run/docs/mapping-custom-domains">Cloud Run Domain Mappings</a></li>
  <li><a href="https://firebase.google.com/docs/hosting">Firebase Hosting</a></li>
  <li><a href="https://github.com/features/actions">GitHub Actions</a></li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Firebase Hosting is a static site host, but it can also be used as a reverse proxy for dynamic Python applications. For projects with low traffic, this is a great <strong><a href="https://firebase.google.com/pricing">free</a></strong> option 👍🏾. If you need more features, you can use Google Cloud Load Balancing. Once you go beyond a low-traffic state you’ll be reminded that Alphabet is a multi-trillion dollar company and they want their money 🤑. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Abel Martin</name></author><category term="DevOps" /><category term="Cloud" /><category term="GCP" /><category term="cloud-run" /><category term="firebase" /><category term="gcp" /><category term="ssl" /><category term="devops" /><category term="react" /><category term="django" /><summary type="html"><![CDATA[A deep dive into why Google Cloud Run's native domain mappings fail, the dreaded SSL_ERROR_SYSCALL loop 😒, and how pairing Firebase Hosting with Cloud Run delivers a resilient, zero-cost production setup.]]></summary></entry><entry><title type="html">Welcome to Abel Creates</title><link href="https://abelcreates.com/posts/welcome-to-abel-creates/" rel="alternate" type="text/html" title="Welcome to Abel Creates" /><published>2026-08-12T22:00:00+00:00</published><updated>2026-08-12T22:00:00+00:00</updated><id>https://abelcreates.com/posts/welcome-to-abel-creates</id><content type="html" xml:base="https://abelcreates.com/posts/welcome-to-abel-creates/"><![CDATA[<p>Welcome to my corner of the web! 🚀</p>

<p>I’ve set up <strong>abelcreates.com</strong> as a space to document what I’m working on, share technical writeups, and publish notes on software engineering, creative coding, and system design.</p>

<h3 id="why-jekyll--ruby">Why Jekyll &amp; Ruby?</h3>

<p>When building a personal site, simplicity and durability win every time. Jekyll gives us:</p>

<ol>
  <li><strong>Zero Runtime Overhead</strong>: Everything compiles down to plain HTML, CSS, and vanilla JS. It loads instantly and requires zero server maintenance.</li>
  <li><strong>Markdown-First Workflow</strong>: Writing an article or tutorial is as simple as creating a <code class="language-plaintext highlighter-rouge">.md</code> file in the <code class="language-plaintext highlighter-rouge">_posts</code> folder.</li>
  <li><strong>Ruby Simplicity</strong>: Bundler manages dependencies cleanly, making local testing reproducible across any machine.</li>
  <li><strong>Git-Driven Deployments</strong>: Pushing to the <code class="language-plaintext highlighter-rouge">main</code> branch triggers a GitHub Actions workflow that builds the site and serves it via GitHub Pages with HTTPS.</li>
</ol>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># The simplicity of static sites</span>
<span class="k">def</span> <span class="nf">publish_post</span><span class="p">(</span><span class="n">title</span><span class="p">)</span>
  <span class="nb">puts</span> <span class="s2">"Push to main -&gt; GHA builds -&gt; deployed to abelcreates.com!"</span>
<span class="k">end</span>
</code></pre></div></div>

<h3 id="whats-next">What’s Next?</h3>

<p>I’ll be posting updates on projects, architecture deep-dives, and tutorials here regularly. Stay tuned!</p>]]></content><author><name>Abel Martin</name></author><category term="General" /><category term="Web" /><category term="jekyll" /><category term="ruby" /><category term="github-pages" /><summary type="html"><![CDATA[Why I decided to rebuild my personal site using Jekyll, Ruby, and GitHub Pages.]]></summary></entry></feed>