<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://asadjb.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://asadjb.com/" rel="alternate" type="text/html" /><updated>2026-07-24T09:03:39+01:00</updated><id>https://asadjb.com/feed.xml</id><title type="html">Jibran’s Perspective</title><subtitle>A collection of my thoughts and stuff.</subtitle><entry><title type="html">Production deployments to exe.dev</title><link href="https://asadjb.com/blog/2026-07-19-production-deployments-to-exe-dev" rel="alternate" type="text/html" title="Production deployments to exe.dev" /><published>2026-07-19T00:00:00+01:00</published><updated>2026-07-19T00:00:00+01:00</updated><id>https://asadjb.com/blog/production-deployments-to-exe-dev</id><content type="html" xml:base="https://asadjb.com/blog/2026-07-19-production-deployments-to-exe-dev"><![CDATA[<p>Recently I’ve been doing most of my personal development work on VMs from <a href="https://exe.dev" target="_blank">https://exe.dev</a>. More on why in a future post, but in summary; these <a href="https://exe.dev/docs/serverful" target="_blank">long lived VMs</a> offer a safe way to run agentic coding tools like Claude Code in <code class="language-plaintext highlighter-rouge">--dangerously-skip-permissions</code> mode, which makes the models really shine.</p>

<p>For small side projects, these VMs can also be used for production deployments; even exe.dev <a href="https://exe.dev/docs/use-case-dev-prod-test" target="_blank">talks about this</a>. It’s not HA; no failover, and there’s little backup, but it works. More importantly, you can have an agent running on the VM do the entire setup for you.</p>

<p>I’ve done just this for 2 of my own projects; <a href="https://keepyourtribe.com" target="_blank">https://keepyourtribe.com</a> and <a href="https://vishlist.my/m/eougj4wc7jij" target="_blank">https://vishlist.my</a>. Both have production deployments on single exe.dev VMs. I’ve setup a few things in both that help make it a safer production environment. I hope this helps others set up their own production apps on such VMs as well.</p>

<h2 id="the-cd-pipeline">The CD pipeline</h2>

<p>I use regular Github actions to power my CI/CD pipeline. A Github workflow triggers on pushes/merges to main. Once the tests pass, they trigger a deployment. This is where having a production setup on exe.dev differs from Fly.io or ECS, my previous favourites.</p>

<p>I use <a href="https://github.com/adnanh/webhook" target="_blank">https://github.com/adnanh/webhook</a>, which is a small Linux HTTP server which listens to webhooks. It’s running as a systemd service and always starts with the VM. It listens on a specific port for a simple POST request on <code class="language-plaintext highlighter-rouge">/hooks/deploy</code>.</p>

<p>The Github CD action is just a simple curl to this path.</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">Trigger deploy on production VM</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">curl -fsS --max-time 600 -X POST \</span>
      <span class="s">-H "X-Exedev-Authorization: Bearer $" \</span>
      <span class="s">https://VM_NAME.exe.xyz:PORT/hooks/deploy</span>
</code></pre></div></div>

<p>The webhook server triggers a <code class="language-plaintext highlighter-rouge">bin/deploy</code> script when it receives this webhook. At this point your security alarms might be going off, but I feel confident in this setup for one reason, the webhook is secured by the <a href="https://exe.dev/docs/proxy" target="_blank">HTTPS proxy</a> offered by exe.dev. The webhook endpoint can only be accessed after authenticating with the proxy; which in the case of the Github action is done via a <a href="https://exe.dev/docs/https-tokens-for-vms" target="_blank">HTTPS Token</a>.</p>

<p>That script also accepts no input. It just runs a Rails deployment. Here’s the code:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail
<span class="nb">export </span><span class="nv">RAILS_ENV</span><span class="o">=</span>production

<span class="nb">cd</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">dirname</span> <span class="s2">"</span><span class="nv">$0</span><span class="s2">"</span><span class="si">)</span><span class="s2">/.."</span>

<span class="c"># Serialize deploys (belt-and-braces; the Actions concurrency group already</span>
<span class="c"># serializes triggers)</span>
<span class="nb">exec </span>9&gt;/tmp/kyt-deploy.lock
flock 9

git pull <span class="nt">--ff-only</span> origin main
bundle <span class="nb">install
</span>bin/rails db:prepare
bin/rails assets:precompile
<span class="nb">sudo </span>systemctl restart keep-your-tribe-jobs.service
<span class="nb">sudo </span>systemctl restart keep-your-tribe-production.service

<span class="c"># Wait for /up to come back before declaring success</span>
<span class="k">for </span>i <span class="k">in</span> <span class="si">$(</span><span class="nb">seq </span>1 30<span class="si">)</span><span class="p">;</span> <span class="k">do
  </span><span class="nb">sleep </span>2
  <span class="k">if </span>curl <span class="nt">-fsS</span> http://127.0.0.1:3000/up <span class="o">&gt;</span> /dev/null<span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"Deploy OK: </span><span class="si">$(</span>git rev-parse <span class="nt">--short</span> HEAD<span class="si">)</span><span class="s2">"</span>
    <span class="nb">exit </span>0
  <span class="k">fi
done
</span><span class="nb">echo</span> <span class="s2">"App did not become healthy after restart"</span> <span class="o">&gt;</span>&amp;2
<span class="nb">exit </span>1
</code></pre></div></div>

<p>With this, I have a functioning CI/CD system that updates my server with every update to the <code class="language-plaintext highlighter-rouge">main</code> branch.</p>

<p>With continuous deployment handled, let’s make sure we have a way to protect ourselves against data loss as well.</p>

<h2 id="database-setup--loss-prevention">Database setup &amp; loss prevention</h2>

<p>I run both my apps on a sqlite database. This can easily scale to a few thousand users; currently I have ZERO!</p>

<p>It’s easy to setup - there’s literally nothing I had to do. The only downside is having no backups.</p>

<p>There’s where <a href="https://litestream.io" target="_blank">litestream</a> comes in. Litestream is a sqlite replication service. With some acceptable replication delay (about a second I think), it replicates all changes to my application database to a Cloudflare R2 bucket - using the S3 compatible API Cloudflare offers.</p>

<p>That’s it. Nothing fancy, but it works. An added benefit I figured out recently was that I could use litestream on a dev VM to make a quick clone of the DB from the same R2 bucket; this helps a ton in debugging app issues. I can clone the DB on the dev VM, and point an AI agent at it.</p>

<h2 id="closing-thoughts">Closing thoughts</h2>

<p>I was sceptical when I first saw exe.dev <a href="https://exe.dev/docs/use-case-dev-prod-test" target="_blank">mention</a> that these VMs could be used for production. Without the deployment webhook and the litestream replication, I wouldn’t have been confident in deploying a production environment to exe.dev either.</p>

<p>With them however, I think this can work for quite a while; or even forever if my apps don’t get any users. :) :(</p>

<p>Checkout <a href="https://keepyourtribe.com" target="_blank">https://keepyourtribe.com</a> - I made it as a way to remind myself to keep in touch with the people I care about.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Recently I’ve been doing most of my personal development work on VMs from https://exe.dev. More on why in a future post, but in summary; these long lived VMs offer a safe way to run agentic coding tools like Claude Code in --dangerously-skip-permissions mode, which makes the models really shine.]]></summary></entry><entry><title type="html">I keep coming back to Amp Code</title><link href="https://asadjb.com/blog/2026-02-08-i-keep-coming-back-to-amp" rel="alternate" type="text/html" title="I keep coming back to Amp Code" /><published>2026-02-08T00:00:00+00:00</published><updated>2026-02-08T00:00:00+00:00</updated><id>https://asadjb.com/blog/i-keep-coming-back-to-amp</id><content type="html" xml:base="https://asadjb.com/blog/2026-02-08-i-keep-coming-back-to-amp"><![CDATA[<p>My first thread in <a href="https://ampcode.com/" target="_blank">Amp</a> is from 7 months ago, July 2025. Back then, Claude Sonnet 4 was the model used by Amp’s smart mode. Today that is Claude Opus 4.6. Much has changed in that time - the models have become smarter, and I’ve leaned into AI assisted software development more.</p>

<p>What remains constant is how much I like the Amp experience - even after honestly trying out Claude Code &amp; Codex (OpenAI). I’m not the only one. Here’s a <a href="https://x.com/EvanAndrewOwen/status/1957874593638891863" target="_blank">Tweet linked</a> from their home page.</p>

<div style="width: 100%; display: flex; justify-content: center;">
	<img style="width: 300px" src="/assets/images/i-keep-coming-back-to-amp.png" />
</div>

<p>I’ve been using Claude Code (Max $100/m plan) since Jan 2026, and recently started using Codex ($20/m plus plan with the 2X usage limits offer). I’ve used Opus 4.6 &amp; GPT-5.3 Codex since they launched. They are very capable models.</p>

<p>I like the Codex app - <a href="/blog/2026-02-06-the-codex-app-feature-i-really-like">especially</a> the diff UI &amp; the ability to comment on changes inline and have the agent act on those.</p>

<p>Despite that, when I started comparing the 3 on the same tasks, I consistently enjoyed working with Amp better.</p>

<p>Amp is more expensive - I spent $20 over 2 days in Jan and was on track to spend $10/day based on how much I was using it. That’s when I switched to the Claude Max $100/m plan, and I’ve used it a lot since then without having to worry about additional costs.</p>

<p>Yet I want to start using Amp again, even if it’s more expensive.</p>

<p>I can’t point to one thing that makes Amp feel better. It’s a combination of its speed and the way it interacts with me. It just <em>feels</em> like a better tool.</p>

<p>In no order, here are the reasons why I started out and stuck with Amp.</p>

<ul>
  <li>Amp code has usage based billing. This was the big one for me. I couldn’t bring myself to pay a monthly subscription when I wasn’t using agents for coding that frequently.</li>
  <li>When I discovered them, they talked up their philosophy of working with the agent instead of having it just do stuff. This was most visibly demonstrated by their VSCode extension (the CLI came much later) using the enter/return key for new lines by default. Every other extension defaulted to sending the prompt on enter, and you had to use shift+enter to add a new line. This one was big for me. It meant that they expected you to add the right context upfront instead of YOLOing it.</li>
  <li>The people behind Amp, and their communications, radiated a love of the craft. They didn’t “move fast &amp; break things”. They move deliberately, and they remove features that don’t seem to be working.
    <ul>
      <li>Offer a limited set of models to choose from. Those models however are very well integrated, both with the agent &amp; each other</li>
      <li>Removed <a href="https://ampcode.com/news/no-more-byok" target="_blank">Bring-Your-Own-Keys</a></li>
      <li>Removed <a href="https://ampcode.com/news/handoff" target="_blank">Compaction</a></li>
      <li>Removed <a href="https://ampcode.com/news/tab-tab-dead" target="_blank">Amp Tab</a> (auto-completion in VSCode)</li>
      <li>and recently it seems they are <a href="https://twitter.com/AmpCode/status/2019447473127702812" target="_blank">going to deprecate the VSCode extension</a> as they feel the CLI is the better UI for a coding agent (I agree).</li>
    </ul>
  </li>
</ul>

<p>Amp just offers a more curated/opinionated experience from people whose taste I share. I’m going to start using it again.</p>

<p>Given the value I’m getting out of AI assisted software development, I’m thinking of sticking with Amp until the costs start hurting, which feels like it would be around the $200/m point.</p>

<p>Claude &amp; Codex are very capable and get the job done equally well, but the <em>experience</em> of Amp is just better.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[My first thread in Amp is from 7 months ago, July 2025. Back then, Claude Sonnet 4 was the model used by Amp’s smart mode. Today that is Claude Opus 4.6. Much has changed in that time - the models have become smarter, and I’ve leaned into AI assisted software development more.]]></summary></entry><entry><title type="html">The 1 feature I’m really liking in the OpenAI Codex App</title><link href="https://asadjb.com/blog/2026-02-06-the-codex-app-feature-i-really-like" rel="alternate" type="text/html" title="The 1 feature I’m really liking in the OpenAI Codex App" /><published>2026-02-06T00:00:00+00:00</published><updated>2026-02-06T00:00:00+00:00</updated><id>https://asadjb.com/blog/the-codex-app-feature-i-really-like</id><content type="html" xml:base="https://asadjb.com/blog/2026-02-06-the-codex-app-feature-i-really-like"><![CDATA[<p>For the past few months I’ve been using AI coding agents heavily. So far I’ve used:</p>
<ul>
  <li>AmpCode - I started out mostly with their VSCode extension &amp; then moved exclusively to the CLI</li>
  <li>Claude Code - CLI</li>
  <li>Codex - CLI</li>
  <li>OpenCode - CLI</li>
  <li>Codex - App</li>
</ul>

<p>I’ve been using the Codex app from OpenAI for a few days now — they made it available via the $20/m subscription for a trial period :)</p>

<p>While I’ve only been using Codex for a few days, I’m really enjoying using it. It’s fast, an app instead of a CLI, allows organizing sessions by project, and shows a history of previous sessions that’s easy to access.</p>

<p>That part about being an app instead of a CLI has some great benefits; the one I’m really liking is their Git diff viewer and commenting system.</p>

<p><img src="/assets/images/codex-comments.png" alt="Codex Screenshot" /></p>

<p>In other agents if I have to ask for some changes, I have to refer to code by its file and maybe line number if I can find it, or try other ways like saying “change this variable inside this function in this file”. With Codex, I can comment inline in the diff. This is really nice.</p>

<p>It’s certainly doable in other agents, asking it to change specific lines of code it generated, but the GitHub PR-like commenting mechanism just makes it so easy. You go through your list of changes, comment on the things you need changed, and then submit all your comments to the agent.</p>

<p>I think this will be the future of AI coding agents - doing everything in a CLI is fine, but having a rich UI unlocks so much. I guess this is also why we’re seeing more GUIs pop up around agents like Conductor or Emdash, which allow you to coordinate multiple agents in the same screen - replacing what people previously used terminal multiplexers like Tmux - or in my case, Zellij.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[For the past few months I’ve been using AI coding agents heavily. So far I’ve used: AmpCode - I started out mostly with their VSCode extension &amp; then moved exclusively to the CLI Claude Code - CLI Codex - CLI OpenCode - CLI Codex - App]]></summary></entry><entry><title type="html">Nuphy Air 75 V3 - Configuring on Linux</title><link href="https://asadjb.com/blog/2025-12-08-nuphy-air-75-v3-linux" rel="alternate" type="text/html" title="Nuphy Air 75 V3 - Configuring on Linux" /><published>2025-12-08T00:00:00+00:00</published><updated>2025-12-08T00:00:00+00:00</updated><id>https://asadjb.com/blog/nuphy-air-75-v3-linux</id><content type="html" xml:base="https://asadjb.com/blog/2025-12-08-nuphy-air-75-v3-linux"><![CDATA[<p>This will be a very short post to put something on the internet that I couldn’t easily find with Google.</p>

<p>I recently got the <a href="https://nuphy.com/collections/keyboards/products/nuphy-air75-v3-page">Nuphy Air 75 V3</a> mechanical keyboard. It’s great so far - from the 12 hours I’ve had it with me so far :)</p>

<p>The one problem I faced was trying to use the <a href="https://nuphy.io">Nuphy.io</a> configurator to change the keybindings. It worked on my Mac, but on my Linux (Omarchy btw :) ) it just failed with a confusing error about permissions. It showed up in the list of devices on Chrome when I tried to connect to it, but it just wouldn’t connect.</p>

<p>The solution is to create a <code class="language-plaintext highlighter-rouge">udev</code> rule that does <em>something</em> to allow the keyboard to be connected to from Chrome somehow. I have ZERO knowledge of the details, just that it works.</p>

<p>I created <code class="language-plaintext highlighter-rouge">/etc/udev/rules.d/50-nuphy.rules</code> and put the following content in it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="19f5", ATTR{idProduct}=="1028", MODE="0666"
KERNEL=="hidraw*", ATTRS{idVendor}=="19f5", ATTRS{idProduct}=="1028", MODE="0666"
</code></pre></div></div>

<p>For other Nuphy keyboards, the vendor id would stay <code class="language-plaintext highlighter-rouge">19f5</code>, but the product id will change. Use <code class="language-plaintext highlighter-rouge">lsusb</code> to find yours. On Omarchy I use the USBView application since <code class="language-plaintext highlighter-rouge">lsusb</code> isn’t installed by default.</p>

<p>Hope this shows up in the Google search for the next person facing this issue and helps them.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[This will be a very short post to put something on the internet that I couldn’t easily find with Google.]]></summary></entry><entry><title type="html">Project 2: Gift cards to Pakistan</title><link href="https://asadjb.com/blog/2024-11-11-project-2-gift-cards-to-pakistan" rel="alternate" type="text/html" title="Project 2: Gift cards to Pakistan" /><published>2024-11-11T00:00:00+00:00</published><updated>2024-11-11T00:00:00+00:00</updated><id>https://asadjb.com/blog/project-2-gift-cards-to-pakistan</id><content type="html" xml:base="https://asadjb.com/blog/2024-11-11-project-2-gift-cards-to-pakistan"><![CDATA[<p>I’ve completed a freelance project I was working on for a few months, and have started saying no to new opportunities. <em>It’s time to work on one of my own ideas again.</em> This is part of my plan to <a href="/blog/2023-12-31-i-have-not-failed-enough">start failing more</a>.</p>

<p>I’ve decided to build a business sending gift cards to Pakistan - and eventually other countries in that corner of the world.</p>

<h2 id="why">Why?</h2>
<p>A few years ago I had sent a gift card to a colleague in the UK. I found a number of very good options. They all had websites that inspired confidence, and used robust payment methods (Stripe in my example) that I could trust with my credit card.</p>

<p>I recently had to send a gift card to a colleague in Pakistan. I was confident that I would find a bunch of great options; instead I only <a href="https://www.giftkarte.com/" target="_new">found one</a> that I could think of trusting with my money.</p>

<p>I ended up using their services and the card was delivered, but there were a number of problems I saw:</p>
<ul>
  <li>No trust building around card payments. There was no clear mention of which provider they used. I did a bank transfer instead of using a CC. This meant my payment was manually verified and the card was only sent after a few hours.</li>
  <li>There was no confirmation email about my order. I was worried enough to call their helpline to confirm that my order had gone through.</li>
  <li>Once they had sent the card (which I also had to confirm via phone), I only got a confirmation email the next day.</li>
  <li>To get an invoice to expense this, I had to send them an email. I’m still waiting on an invoice.</li>
  <li>There were multiple colleagues who chipped in on this gift card. I had to collect the money from them and then pay for the card myself. In my previous experience of sending a gift card to the UK, I was able to include my colleagues in the process. They were able to add their contributions directly to the gift card I selected and a card of the total amount was sent to the recipient.</li>
  <li>Finally, there was no option for the receiver to choose which gift card they wanted. Instead I had to choose for them. There is a “Universal Gift Card” they claim works at all merchants and is the one I got, but redeeming that would be slightly more complicated.</li>
</ul>

<p>Interestingly, my colleague didn’t open the email they received with the gift card because they thought it was a spam/scam/malicious email. Only after I asked if they had received the card did they end up opening it.</p>

<p>I know a better user experience exists. I want to bring the same to Pakistan and solve my own problem at the same time.</p>

<p>Is there a market for this? I believe so, because:</p>
<ul>
  <li>It’s a problem I’ve just faced.</li>
  <li>I’ve seen my wife having to deal with low-trust companies sending gifts to Pakistan. Gift cards are different, but eventually I could also add the option to send physical gifts to the recipient.</li>
  <li>I’ve seen my employer deal with this. Recently a baby gift basket arrived 2 months after the baby was born. 🤯</li>
  <li>This is a recurring problem. People &amp; companies need to send gift cards on birthdays, weddings, etc.</li>
  <li>With more companies starting to hire remotely in Pakistan, this could be a valuable service for businesses to subscribe to.</li>
</ul>

<h2 id="validation">Validation?</h2>
<p>I haven’t found an easy way to validate this idea. There is no community of “people sending gift cards to Pakistan” that I can tap into. That isn’t a cohort I can find in one place.</p>

<p>I could make a list of B2B customers; companies that hire remotely in Pakistan.</p>

<p>However, I want to start with individual customers - because I’m starting from a place of solving my own problem. It should be possible to pivot to B2B if I don’t find any interest from individual customers.</p>

<p>Validation then involves me starting with a blog - suggesting gift cards to send to Pakistan. I’ll use SEO to bring in traffic. If I see enough visitors, I could start building a business. This also means that if/when the actual product launches, I’ll have a distribution channel already working.</p>

<h2 id="what-if-im-wrong">What if I’m wrong?</h2>
<p>There’s a <strong>very strong</strong> possibility that I’m wrong about this idea. That I’ll spend a bunch of time for it to get nowhere, or that I have picked a problem that isn’t very valuable to solve.</p>

<p>This is my unique brand of fear of failure. I used to think I didn’t fear failing, because I had already failed many times. Instead, my fear of failure manifests as a fear of picking the wrong thing and wasting time on it. The way I am dealing with this is to realize that if I don’t pick anything - which I have frequently done in the past - I have an <strong>exactly</strong> 0% chance of succeeding. Just trying something makes that probability &gt; 0%.</p>

<blockquote>
  <p>You miss 100% of the shots you don’t take.</p>
</blockquote>

<p>Another thing that’s helping me is to time box this idea. I will spend 6 weeks on building the blog and populating it with as much useful content as possible. After that I can spend an hour or two every week to add a few more pieces of content. I can start researching and working on a different idea after the 6 week period and wait for the SEO to have an impact before making a decision to continue or abandon this.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’ve completed a freelance project I was working on for a few months, and have started saying no to new opportunities. It’s time to work on one of my own ideas again. This is part of my plan to start failing more.]]></summary></entry><entry><title type="html">Deploying Ruby on Rails to AWS with Kamal</title><link href="https://asadjb.com/blog/2024-06-22-deploying-ruby-on-rails-to-aws-with-kamal" rel="alternate" type="text/html" title="Deploying Ruby on Rails to AWS with Kamal" /><published>2024-06-22T00:00:00+01:00</published><updated>2024-06-22T00:00:00+01:00</updated><id>https://asadjb.com/blog/deploying-ruby-on-rails-to-aws-with-kamal</id><content type="html" xml:base="https://asadjb.com/blog/2024-06-22-deploying-ruby-on-rails-to-aws-with-kamal"><![CDATA[<p>As part of a contracting project, I’ve been building an analytics dashboard for a feedback collection SaaS. The app is built in Ruby on Rails and given all the nice things I’ve heard about <a href="https://kamal-deploy.org/">Kamal</a>; I decided to use it for deploying the app.</p>

<p>The experience has been phenomenal; outside of some frustration with the initial deployment.</p>

<p>The app is deployed on a pretty standard AWS setup; a couple of EC2 servers hosting the web app running inside Docker containers, and a load balancer in front.</p>

<p>One of the problems I faced during the initial deployment was forwarding headers from the AWS application load balancer to the RoR server running in the Docker container.</p>

<p>The challenge with Kamal is that it relies heavily on <a href="https://traefik.io/traefik/">Traefik</a>, and while Traefik is a great tool, it takes some getting used to. It’s configuration is not very intuitive, and there’s no easy way to see how things are configured outside of looking at the text logs.</p>

<p>The Traefik document is pretty thorough, so a bit of searching led me to this CLI argument which needs to be passed to the Traefik container:</p>

<p><code class="language-plaintext highlighter-rouge">entrypoints.http.forwardedheaders.insecure: true</code></p>

<p>However, no matter what I tried, when I added this, the app container would stop responding to web requests. Without the config the container would work but throw an exception related to the <code class="language-plaintext highlighter-rouge">Origin</code> header not matching the configured hosts.</p>

<p>After a lot of experimentation, I stumbled upon the other config I needed to add by pure luck.</p>

<p><code class="language-plaintext highlighter-rouge">entrypoints.http.address: ":80"</code></p>

<p>As far as I can tell, when I added the <code class="language-plaintext highlighter-rouge">forwardedheaders</code> config, the entrypoint no longer got the correct <code class="language-plaintext highlighter-rouge">address</code> configuration. I’m not sure if this is related to Kamal or Traefik.</p>

<h2 id="kamal-deployyml">Kamal <code class="language-plaintext highlighter-rouge">deploy.yml</code></h2>
<p>If you’re looking to replicate a similar setup, here’s the Kamal <code class="language-plaintext highlighter-rouge">deploy.yml</code> file that I am using with this project to deploy to AWS, with a load balancer terminating the SSL connection and forwarding traffic to web servers that are configured via Kamal. As a bonus, this config also deploys <a href="https://sidekiq.org/">Sidekiq</a> for background tasks.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span> <span class="s">&lt;SERVICE NAME&gt;</span>

<span class="na">image</span><span class="pi">:</span> <span class="s">&lt;IMAGE NAME&gt;</span>

<span class="na">ssh</span><span class="pi">:</span>
  <span class="na">user</span><span class="pi">:</span> <span class="s">ubuntu</span>
  <span class="na">proxy</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ubuntu@A.B.C.D"</span>

<span class="na">servers</span><span class="pi">:</span>
  <span class="na">web</span><span class="pi">:</span>
    <span class="na">hosts</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">A.B.C.D"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">A.B.C.D"</span>
    <span class="na">labels</span><span class="pi">:</span>
      <span class="na">traefik.http.routers.&lt;SERVICE NAME&gt;-web.rule</span><span class="pi">:</span> <span class="s">Host(`&lt;YOUR HOST NAME&gt;`)</span>
  <span class="na">sidekiq</span><span class="pi">:</span>
    <span class="na">hosts</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">A.B.C.D"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">A.B.C.D"</span>
    <span class="na">traefik</span><span class="pi">:</span> <span class="no">false</span>
    <span class="na">cmd</span><span class="pi">:</span> <span class="s">bundle exec sidekiq</span>


<span class="na">registry</span><span class="pi">:</span>
  <span class="na">server</span><span class="pi">:</span> <span class="s">&lt;AWS ACCOUNT ID&gt;.dkr.ecr.&lt;AWS REGION&gt;.amazonaws.com</span>
  <span class="na">username</span><span class="pi">:</span> <span class="s">AWS</span>
  <span class="na">password</span><span class="pi">:</span> <span class="s">&lt;%= %x(aws ecr get-login-password --region &lt;AWS REGION&gt;) %&gt;</span>

<span class="na">builder</span><span class="pi">:</span>
  <span class="na">local</span><span class="pi">:</span>
    <span class="na">arch</span><span class="pi">:</span> <span class="s">amd64</span> <span class="c1"># Because I develop on a Apple Silicon machine, I need to use a build target</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">clear</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">DATABASE_URL</span><span class="pi">:</span> <span class="s">&lt;DATABASE URL&gt;</span>
  <span class="na">secret</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">RAILS_MASTER_KEY</span>
    <span class="pi">-</span> <span class="s">DB_PASSWORD</span>

<span class="na">traefik</span><span class="pi">:</span>
 <span class="na">args</span><span class="pi">:</span>
   <span class="na">entrypoints.http.address</span><span class="pi">:</span> <span class="s2">"</span><span class="s">:80"</span>
   <span class="na">entrypoints.http.forwardedheaders.insecure</span><span class="pi">:</span> <span class="no">true</span>
   <span class="na">log.level</span><span class="pi">:</span> <span class="s">DEBUG</span>
   <span class="na">accesslog</span><span class="pi">:</span> <span class="no">true</span>
   <span class="na">accesslog.format</span><span class="pi">:</span> <span class="s">json</span>
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[As part of a contracting project, I’ve been building an analytics dashboard for a feedback collection SaaS. The app is built in Ruby on Rails and given all the nice things I’ve heard about Kamal; I decided to use it for deploying the app.]]></summary></entry><entry><title type="html">Failure 1: Django + NextJS Boilerplate</title><link href="https://asadjb.com/blog/2024-06-16-failure-1" rel="alternate" type="text/html" title="Failure 1: Django + NextJS Boilerplate" /><published>2024-06-16T00:00:00+01:00</published><updated>2024-06-16T00:00:00+01:00</updated><id>https://asadjb.com/blog/failure-1</id><content type="html" xml:base="https://asadjb.com/blog/2024-06-16-failure-1"><![CDATA[<p>I have failed, and that is exactly what I had hoped for a few months ago in <a href="/blog/2023-12-31-i-have-not-failed-enough">this blog post</a>.</p>

<p>This is a good failure. It has taught me things, lessons I can use in the future to avoid failing this way again.</p>

<p>But first a bit of context. What did I fail at?</p>

<hr />

<p>In February of 2024 I decide to try my hands on my first “Indie Hacker” hustle, something that would make me money on the internet without having to trade my time for it. A product instead of consultancy services that I usually provide.</p>

<p>I had seen a number of people on Twitter (X) rave about how well their bootstrap templates were doing; and I had just gotten out of a consultancy project where I needed to connect a Next.js frontend to a Django backend. I thought it was the perfect project to start my indie hacking journey.</p>

<p>I put up a <a href="/blog/2024-02-15-project-1-django-nextjs-boilerplate">launch post</a> and started working, updating a <a href="/build_logs/project-1">build log</a> as I went along.</p>

<p>I gave myself until 28th March 2024 to finish it. That of course did not happen.</p>

<p>Let’s talk about why I failed and what I learned.</p>

<hr />

<h2 id="episode-1-the-one-where-i-dont-understand-the-meaning-of-mvp">Episode 1: The one where I don’t understand the meaning of MVP</h2>

<p>My initial plan was to build a Django+Next.js boilerplate template the provided all of these:</p>
<ul>
  <li>the base template that provided a Django backend &amp; Next.js frontend</li>
  <li>working authentication b/w the backend &amp; frontend</li>
  <li>Dockerfile that would create the backend &amp; frontend containers for deployment</li>
  <li>Terraform scripts to setup an infrastructure on AWS</li>
  <li>Celery + Redis for background task processing</li>
  <li>TailwindCSS for the frontend (comes mostly for free with Next.js)</li>
  <li>social auth</li>
</ul>

<p>This looks like something achievable in a week or two of work - but only if you’re working full time on this. I failed to consider that I have a day job and a life. I was barely able to tick of the first two of these deliverables by the time my 6 week deadline came up.</p>

<p>As a good friend told me later, I should have focused on the minimum amount of value I could deliver. Just having the first two things on my list be done would have been enough. I couldn’t charge the $20 I had planned for, but I could have charged $1-$5 for just that.</p>

<p>And if no one was interested in spending the cost of a coffee on the MVP of the template, that would have been a good signal that this wasn’t going anywhere in it’s current shape.</p>

<p>Instead, by focusing on building something much bigger, I robbed myself of the ability to validate the idea quickly. I spent all my available time coding the template instead of trying to talk to potential customers about it.</p>

<p><strong>Lesson 1</strong>: Scope down aggressively.</p>

<h2 id="episode-2-where-i-jumped-on-the-hype-wagon">Episode 2: Where I jumped on the hype-wagon</h2>

<p>I settled on building a boilerplate template because that’s what I had seen a lot of people on Twitter/X doing lately; I’m chalking this down to recency bias.</p>

<p>I had no personal interest in a boilerplate template. It’s also not a product that I would personally use. I have so far made <em>one</em> project that uses this tech stack. Most of my other projects are Django, and Ruby on Rails.</p>

<p>The most successful boilerplate templates I come across are from people who made a bunch of projects in 1 tech stack then realized they needed to do the same thing over-and-over again; which they then packaged into a template they could use. Selling to others was a bonus at first I guess.</p>

<p>I was very enthusiastic about the project at the start, but as time went on I had to force myself to work on it. My lack of interest in this type of project was a big factor.</p>

<p>Another factor was there being no way to see the fruits of my labor. I am currently working on an analytics dashboard for another client (a RoR project) and every time I build a feature, I love to play around with it in my free time. I test how it works, make sure the UX is a good one, and just play around and admire the app I’ve made.</p>

<p>Without me using my template to build new projects, I lacked that feedback loop. Without the loop, I quickly lost interest.</p>

<p><strong>Lesson 2</strong>: Build something I can use myself. This isn’t a job I’m getting paid for, so the only motivation I have initially until it starts generating money is to build something interesting for myself.</p>

<h2 id="episode-3-where-i-had-nothing-for-potential-customers-to-play-around-with">Episode 3: Where I had nothing for potential customers to play around with</h2>

<p>This is related to the <a href="#episode-1-the-one-where-i-dont-understand-the-meaning-of-mvp">1st lesson</a>. Because I didn’t have a path to quickly get something out there, there was no way for me to get my “product” into the hands of people who could test and provide feedback.</p>

<p>I think the problem with a boilerplate template style of product is that you can’t give people a half-backed thing and ask them to test it. Unlike other SaaS apps, there’s no mid-way version of a template. Customers have to “buy-in” to use your template with any project they are starting. With SaaS, users can sign up and test, and then leave if they don’t like it. There’s no easy way of testing with a template.</p>

<p><strong>Lesson 3</strong>: Build something that can be tested by potential customers easily. For now, I’m going to stick with SaaS style web apps.</p>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>Moving forward:</p>

<ol>
  <li>I’ll be working on web app products that users can sign up for and test very quickly.</li>
  <li>My next few experiments/products will be things that I can use myself as well.</li>
</ol>

<p>I’ll post what I’m going to work on next when I decide and have some time away from my job &amp; freelance projects that are currently in progress.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I have failed, and that is exactly what I had hoped for a few months ago in this blog post.]]></summary></entry><entry><title type="html">Cookie Based Auth for Django and NextJS</title><link href="https://asadjb.com/blog/2024-03-03-cookie-based-auth-for-django-and-nextjs" rel="alternate" type="text/html" title="Cookie Based Auth for Django and NextJS" /><published>2024-03-03T00:00:00+00:00</published><updated>2024-03-03T00:00:00+00:00</updated><id>https://asadjb.com/blog/cookie-based-auth-for-django-and-nextjs</id><content type="html" xml:base="https://asadjb.com/blog/2024-03-03-cookie-based-auth-for-django-and-nextjs"><![CDATA[<blockquote>
  <p>If you’re just looking for implementation instructions, skip my ramblings and go straight to the <a href="#implementation">code here</a>.</p>
</blockquote>

<p>I’m currently working on my <a href="/blog/2024-02-15-project-1-django-nextjs-boilerplate">first project</a> after deciding that I needed to <a href="/blog/2023-12-31-i-have-not-failed-enough">fail more</a> and practice finishing projects instead of abandoning them midway once they got “boring”.</p>

<p>Anyways… This one is till in it’s interesting phase, so here’s a blog post with some things I learned yesterday while working on it.</p>

<p>The project is a <a href="https://asadjb.gumroad.com/l/nextjs-django-template">boilerplate template</a> that should make it easy for devs. to start a new project with a Django backend and a Next.js frontend, something I had to struggle with recently.</p>

<h2 id="the-problem">The problem</h2>

<p>The first thing I’m looking to solve is authentication. That was my biggest challenge when working on the contracting project that inspired this template.</p>

<p>While there are a number of good posts around how to setup authentication b/w Django &amp; Next.js, nothing “definitive” came up and I had to cobble together a
weird mess of Django+DRF (Django Rest Framework) and Next.js+NextAuth, sharing
a token from Django that was masquarading as a JWT token for Next.js. It wasn’t pretty and I knew I could do better.</p>

<h2 id="the-options">The options</h2>

<p>I considered 2 options for authenticating the Next.js frontend with the Django backend:</p>

<ol>
  <li>Token based auth. On logging in, a user receives a token that is stored in local storage by the frontend and send with every request to the backend.</li>
  <li>Session/Cookie based auth. This is how authentication works in Django by default and is very easy to get started with - it basically comes for free out of the box when you start a new Django project.</li>
</ol>

<p>While token based auth. is what almost everyone suggests to use when using a Next.js frontend with any backend technology, I wanted to give session based auth. a try. I was curious what it would take to make it work - if it was even possible.</p>

<p><strong>tl;dr:</strong> It was possible to use cookie/session auth. b/w Django &amp; Next.js - though with a few constraints which make it less appealing than the token based solution</p>

<p>What follows are my notes on how to set it up, the problems I faced, and why for the template I’m going to go with token based auth. instead.</p>

<h2 id="learning-how-cors--set-cookie-works">Learning how CORS &amp; Set-Cookie works</h2>

<p>It took me a few hours to get my head around how cross-origin requests and cookies work together, but the actual implementation was surprisingly straight forward.</p>

<p>This “mini-quest” gave me a chance to learn a lot about how CORS and cookies work, and I’m happy with the time I spent on this. These are the resources which helped me the most (all are from MDN):</p>

<ul>
  <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">Cross-Origin Resource Sharing</a></li>
  <li><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy">Same-origin policy</a></li>
  <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">Using HTTP cookies</a></li>
  <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value">Set-Cookie</a></li>
</ul>

<p>And finally, there was a surprise waiting for me! Browsers are almost universally making changes to restrict 3rd party or cross-domain cookies because of their privacy implications. Here’s a nice article from MDN about it: <a href="https://developer.mozilla.org/en-US/blog/goodbye-third-party-cookies/">Saying goodbye to third-party cookies in 2024</a>.</p>

<p>This is the reason why; while this approach works, I won’t be using it in the template. <a href="#why-not">More on that later</a>.</p>

<h2 id="implementation">Implementation</h2>

<p>Implementing the session based auth. b/w Django &amp; Next.js is pretty simple.</p>

<h3 id="django-configuration">Django configuration</h3>

<ol>
  <li>Install the <a href="https://github.com/adamchainz/django-cors-headers"><code class="language-plaintext highlighter-rouge">django-cors-headers</code></a> Python package.
    <ol>
      <li>Add <code class="language-plaintext highlighter-rouge">"corsheaders",</code> to your <code class="language-plaintext highlighter-rouge">INSTALLED_APPS</code>.</li>
      <li>Add the <code class="language-plaintext highlighter-rouge">"corsheaders.middleware.CorsMiddleware",</code> middleware, right above the existing <code class="language-plaintext highlighter-rouge">CommonMiddleware</code>.</li>
      <li>Set <code class="language-plaintext highlighter-rouge">CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]</code>, replacing the URL with your frontend URL.</li>
      <li>Set <code class="language-plaintext highlighter-rouge">CORS_ALLOW_CREDENTIALS = True</code></li>
    </ol>
  </li>
  <li>Configure <code class="language-plaintext highlighter-rouge">settings.py</code> to allow cross-domain access for the session cookie.
    <ol>
      <li>Set <code class="language-plaintext highlighter-rouge">SESSION_COOKIE_SAMESITE = "None"</code></li>
      <li>Set <code class="language-plaintext highlighter-rouge">SESSION_COOKIE_SECURE = True</code></li>
    </ol>
  </li>
</ol>

<h3 id="nextjs-configuration">Next.js configuration</h3>

<p>No configuration is needed on the frontend. However, you do need to use the <code class="language-plaintext highlighter-rouge">credentials: "include",</code> option when using the <code class="language-plaintext highlighter-rouge">fetch()</code> API to access your backend.</p>

<p>Here’s a minimal example.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">use client</span><span class="dl">"</span><span class="p">;</span>

<span class="k">import</span> <span class="p">{</span> <span class="nx">BACKEND_URL</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@/constants</span><span class="dl">"</span><span class="p">;</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">signIn</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">loginData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">();</span>
  <span class="nx">loginData</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">"</span><span class="s2">username</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">admin</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">loginData</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">"</span><span class="s2">password</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">admin</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">return</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">BACKEND_URL</span><span class="p">}</span><span class="s2">/accounts/login/`</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">body</span><span class="p">:</span> <span class="nx">loginData</span><span class="p">,</span>
    <span class="na">credentials</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">whoAmI</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span>
    <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">BACKEND_URL</span><span class="p">}</span><span class="s2">/accounts/me/`</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">GET</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">credentials</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span><span class="p">,</span>
    <span class="p">}),</span>
  <span class="p">);</span>
<span class="p">}</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Home</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="o">&lt;</span><span class="nx">main</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">flex min-h-dvh w-full flex-col justify-around</span><span class="dl">"</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="nx">h1</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">text-center</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Home</span><span class="o">&lt;</span><span class="sr">/h1</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">className</span><span class="o">=</span><span class="dl">""</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">signIn</span><span class="p">}</span><span class="o">&gt;</span>
        <span class="nx">Sign</span> <span class="nx">In</span>
      <span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">whoAmI</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Who</span> <span class="nx">Am</span> <span class="nx">I</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt;
</span>    <span class="o">&lt;</span><span class="sr">/main</span><span class="err">&gt;
</span>  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That’s it. That simple piece of code &amp; configuration took me hours to find. Hopefully you can use this example to skip all that time spent trying to figure things out.</p>

<p><em>Side quest log</em>: Initially, I was not using the <code class="language-plaintext highlighter-rouge">credentials: "include"</code> option in the <code class="language-plaintext highlighter-rouge">signIn()</code> function above; thinking that I didn’t need to send any cookies with the login call, only the second API call to the <code class="language-plaintext highlighter-rouge">/accounts/me</code> endpoint.</p>

<p>That mistake cost me about 2 hours of debugging time. If I had <a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch#credentials">RTFM</a> correctly the first time, I would have seen this:</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">include</code>: Tells browsers to include credentials in both same- and cross-origin requests, and always use any credentials sent back in responses.</p>
</blockquote>

<p>The <code class="language-plaintext highlighter-rouge">credentials: "include"</code> not only controls if cookies are sent, but also if they are saved when returned by the server.</p>

<h2 id="why-not">Why I won’t use this solution in the template</h2>

<p>Browsers are phasing out 3rd party cookies (<a href="https://developer.mozilla.org/en-US/blog/goodbye-third-party-cookies/">Saying goodbye to third-party cookies in 2024</a>) and adding features to work around that restriction where needed.</p>

<p>The simplest way that doesn’t require much change is to use <a href="https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies">Cookies Having Independent Partitioned State (CHIPS)</a>.</p>

<p>To enable CHIPS, you simply put a <code class="language-plaintext highlighter-rouge">Partitioned</code> flag on your <code class="language-plaintext highlighter-rouge">Set-Cookie</code> header, like so:</p>

<p><code class="language-plaintext highlighter-rouge">Set-Cookie: session_id=1234; SameSite=None; Secure; Path=/; Partitioned;</code></p>

<p>Unfortunately, there’s no straight forward way to do this in Django for now. There’s an open issue to resolve this, but looking at the comments, it won’t likely be solved anytime soon.</p>

<p>Considering this, I opted to use the token based auth. method for my template. I’ll write a blog on that once I get it working over the next few days.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[If you’re just looking for implementation instructions, skip my ramblings and go straight to the code here.]]></summary></entry><entry><title type="html">Project 1: Django + NextJS Boilerplate</title><link href="https://asadjb.com/blog/2024-02-15-project-1-django-nextjs-boilerplate" rel="alternate" type="text/html" title="Project 1: Django + NextJS Boilerplate" /><published>2024-02-15T00:00:00+00:00</published><updated>2024-02-15T00:00:00+00:00</updated><id>https://asadjb.com/blog/project-1-django-nextjs-boilerplate</id><content type="html" xml:base="https://asadjb.com/blog/2024-02-15-project-1-django-nextjs-boilerplate"><![CDATA[<p>Links:</p>

<ul>
  <li><a href="https://asadjb.gumroad.com/l/nextjs-django-template">Gumroad page</a></li>
  <li><a href="/build-logs/project-1">Build Log</a></li>
</ul>

<hr />

<p>My <em>accidental</em> <a href="/blog/2023-12-31-i-have-not-failed-enough">new years resolution</a> was to work on the 1 problem that has plagued me for my entire adult life; failure to commit and focus. I decided to work in 6 week “sprints” (inspired by <a href="https://basecamp.com/shapeup">Shape Up</a>) and complete the projects I start - for some <strong>known</strong> definition of complete.</p>

<p>This is the 1st project I have decided to work on. I’ll work on this from today (15th Feb 2024) to (28th Mar 2024). I’ll follow-up then with another post talking about how it went.</p>

<h2 id="the-project">The project</h2>

<p>The goal is to make &amp; sell a Django + NextJS boilerplate template. What’s a boilerplate template?</p>

<p>It’s the source code for a project that’s already setup with many things that are needed in a new project; for example:</p>

<ul>
  <li>Stripe subscriptions functionality</li>
  <li>Background jobs</li>
  <li>CSS framework</li>
  <li>User/team management</li>
</ul>

<p>A great example is <a href="https://www.saaspegasus.com/">Saas Pegasus</a>, which seems like an amazing boilerplate loved by many people.</p>

<p>My boilerplate is going to be <em>much</em> simpler - and also much cheaper. SaaS Pegasus comes with so many features that it’s worth the $249 starting price. I’m aiming for $5-$10.</p>

<h2 id="goals">Goals</h2>

<p>My goal is to sell this boilerplate to at least 10 people - and have them be happy using it. This means:</p>

<ul>
  <li>talking to prospective customers and seeing if this can be useful to them. People will have the option of scheduling a 15 minute pre-purchase call with me for $5 to see if this would be useful to them. The payment is purely to make sure that I only spend time talking to people who are somewhat serious about purchasing.</li>
  <li>providing excellent after sales support. I’ll include a 60 minute setup call with me for any purchase. While a 60 minute call for a $10 sale isn’t scalable, it’s a great way for me to talk to customers at the start.</li>
  <li>having a no questions asked refund policy. My experiences with running an <a href="https://khalil-ahmed.com/">e-commerce store</a> in the past tell me this is an amazing way to build trust.</li>
  <li>provide on-going support, updates, and fixes over email.</li>
  <li>build a mailing list of people interested in my work who I can email when I launch my future projects.</li>
</ul>

<h2 id="the-deliverable">The deliverable</h2>

<p>The boilerplate will allow developers to quickly start a project that uses Django for the backend and NextJS for the frontend. My recent experiences with another project in this tech stack required me to spend significant time on:</p>

<ul>
  <li>figuring out how to setup authentication b/w Django &amp; NextJS (this took the most time &amp; effort)</li>
  <li>setting up Django Rest Framework so I could write APIs that would be used by the frontend</li>
  <li>writing Docker files that would build 2 containers - backend &amp; frontend</li>
  <li>writing Terraform scripts to deploy those containers to AWS ECS</li>
  <li>writing config &amp; scripts to run the project on Gitpod so it could be easily worked on by my team members</li>
</ul>

<p>My plan is to build a boilerplate that already has most those features built in, plus a few extras:</p>

<ul>
  <li>Celery with Redis for background task processing</li>
  <li>Tailwind CSS for the frontend (in my project I used ChakraUI but Tailwind would be a better option for a boilerplate)</li>
  <li>If there’s demand for it, a stretch goal is to include social auth (sign-in with Google/Apple/etc)</li>
</ul>

<p>Once complete, I’ll put this on Gumroad and create a landing page there. From then on, it’s all about marketing it; that’s the part which I have no experience with and hope to learn the most from.</p>

<h2 id="the-marketing-plan">The marketing plan</h2>

<p>This is the area where I lack <em>any</em> experience; so I’m not sure how I’m going to market this. Some ideas I have:</p>

<ul>
  <li>build it in public on Twitter. I have a tiny Twitter following (312 followers) so not sure how useful this could be. But I have to try something.</li>
  <li>share it with people asking how to setup Django &amp; NextJS on forums like Reddit, Stackoverflow, and others.</li>
  <li><em>maybe</em> write a blog post on how to setup Django &amp; NextJS and then link to the boilerplate from there. The blog post would provider all the steps necessary for the basic setup and the boilerplate would go beyond that with something that’s ready to use.</li>
</ul>

<h2 id="the-build-log">The build log</h2>

<p>I’d also like to create a build log with this project. This will be a daily note of what I did for this project. I’ll keep it in my notes app <a href="https://reflect.app/">Reflect</a> and periodically put it here in this blog post. These daily notes might also serve as content for my build-in-public marketing strategy.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Links:]]></summary></entry><entry><title type="html">I have not failed enough</title><link href="https://asadjb.com/blog/2023-12-31-i-have-not-failed-enough" rel="alternate" type="text/html" title="I have not failed enough" /><published>2023-12-31T00:00:00+00:00</published><updated>2023-12-31T00:00:00+00:00</updated><id>https://asadjb.com/blog/i-have-not-failed-enough</id><content type="html" xml:base="https://asadjb.com/blog/2023-12-31-i-have-not-failed-enough"><![CDATA[<p>I was recently listening through the <a href="https://freakonomics.com/podcast-tag/how-to-succeed-at-failing/">How to succeed at failing</a> series on the Freakonomics podcast and started to think about how often I had failed in the past few years. The first answer was - not too much. I couldn’t think of too many instances of where I had “failed”.</p>

<p>This was not a good thing. It was very much a sad thing that I couldn’t quickly think of many things I had failed at.</p>

<h2 id="why-is-not-failing-bad">Why is not failing bad?</h2>
<p>I realized I didn’t fail a lot because - <strong>I didn’t try a lot.</strong> I didn’t fail because I didn’t have good goals.</p>

<p>Sure, I have a folder full of projects I thought of, researched, some that I even started working on; that I gave up on pretty soon after starting. <em>But those don’t count as failures right?</em> I was just <em>playing around</em>, not really aiming for anything. It was just a fun side project.</p>

<p>That’s why I didn’t fail a lot, and that’s also why it’s not a good thing.</p>

<h2 id="i-need-to-fail-more">I need to fail more</h2>
<p>Which means that I need to set goals and work towards them. Without goals to aim for, I keep spending my time in the same unproductive loop.</p>
<ol>
  <li>Think of a shiny new project.</li>
  <li>Play around a bit, maybe learn a few things.</li>
  <li>Give up when things get hard or boring.</li>
  <li>Repeat</li>
</ol>

<p>While I do learn some things, I don’t really learn new things. Picking up another web backend framework when I’m very good at 1 already (Django if you’re looking to get something built 🙂) is very small incremental progress.</p>

<p>I should be learning about running a business, marketing, sales, how to talk to people, cold calling, etc. Instead I’m playing around with NextJS/Remix because it’s easier to do and there’s very little chance of me failing at it.</p>

<h2 id="what-im-planning-to-do">What I’m planning to do</h2>
<ul>
  <li>Any new project that I start, I will have an achievable goal for it. Something that can be called “done”.
    <ul>
      <li><em>Achievable</em> is different for everyone. Creating a small SaaS and have 5 people pay for it might be childs play for some, it’s my Everest for now.</li>
      <li>The plan is to aim for things just outside of my reach – to work towards those and grow in the process.</li>
    </ul>
  </li>
  <li>Have a deadline. For now I’m starting with 6 week cycles of project work.</li>
  <li>In order meet deadlines, I need to have a clear picture of the goal. Goes back to the first thing on this list. Having a clear definition of “done” is important. But being able to cut scope to achieve some version of the goal is also important.
    - I’ve just finished reading Shape Up by Jason Fried and I like the Basecamp approach of deciding on an appetite (time commitment) to a project and cutting scope to meet that appetite.
    - That’s my plan moving forward. Each project I work on will be “shapped” into an achievable outcome and will have an allocated appetite - when I want to launch it or decide to abandon it. Continuing it for the next 6 week cycle will be another option but should be done sparingly.</li>
</ul>

<p>This approach of timeboxing projects and having an achievable goal is also discussed by Zack Freedman in this video <a href="https://www.youtube.com/watch?v=L1j93RnIxEo">Here’s what’s preventing you from finishing projects</a>. See it when you get the chance.</p>

<h2 id="this-wasnt-supposed-to-be-a-new-years-resolution">This wasn’t supposed to be a new years resolution</h2>
<p>I started thinking about this over 2 months ago and have been planning to write a post on it. Today seemed like a good day for it. So I guess this is my new year’s resolution. <strong>Fail more, fail better.</strong></p>]]></content><author><name></name></author><summary type="html"><![CDATA[I was recently listening through the How to succeed at failing series on the Freakonomics podcast and started to think about how often I had failed in the past few years. The first answer was - not too much. I couldn’t think of too many instances of where I had “failed”.]]></summary></entry></feed>