<?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: Adam Hill</title>
    <description>The latest articles on DEV Community by Adam Hill (@adamghill).</description>
    <link>https://dev.to/adamghill</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%2F104471%2F7608e081-23b6-45f4-8122-d5c72dc26e9a.jpeg</url>
      <title>DEV Community: Adam Hill</title>
      <link>https://dev.to/adamghill</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adamghill"/>
    <language>en</language>
    <item>
      <title>Checking Django Settings</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Thu, 12 Feb 2026 15:57:58 +0000</pubDate>
      <link>https://dev.to/adamghill/checking-django-settings-12g4</link>
      <guid>https://dev.to/adamghill/checking-django-settings-12g4</guid>
      <description>&lt;p&gt;I still keep coming back to this "the settings file still feels like a lot" concern in &lt;a href="https://jvns.ca/blog/2026/01/27/some-notes-on-starting-to-use-django/#the-settings-file-still-feels-like-a-lot" rel="noopener noreferrer"&gt;https://jvns.ca/blog/2026/01/27/some-notes-on-starting-to-use-django/#the-settings-file-still-feels-like-a-lot&lt;/a&gt;. What I was thinking about before was a LSP (Languge Server Protocol) which basically gives an IDE more metadata about the code it is parsing.&lt;/p&gt;

&lt;p&gt;I have a prototype of that here: &lt;a href="https://github.com/adamghill/django-lsp" rel="noopener noreferrer"&gt;https://github.com/adamghill/django-lsp&lt;/a&gt;. It works pretty well for settings files, however I also wanted to approach this problem from another angle.&lt;/p&gt;

&lt;p&gt;Could Python type hinting be used to alert the developer when they misconfigured a setting?&lt;/p&gt;

&lt;h2&gt;
  
  
  Type-checking settings
&lt;/h2&gt;

&lt;p&gt;For example, a common one that has bit me in the past is something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;getenv&lt;/span&gt;

&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run &lt;code&gt;DEBUG=False python manage.py&lt;/code&gt;, your &lt;code&gt;DEBUG&lt;/code&gt; setting will be a string, not a boolean. Then, something like &lt;code&gt;if settings.DEBUG:&lt;/code&gt; will evaluate to &lt;code&gt;True&lt;/code&gt;. Why? Because the string "False" is truthy. But, obviously, that is unintended and a bug.&lt;/p&gt;

&lt;p&gt;If we define the types for all Django core settings, e.g. &lt;code&gt;DEBUG: bool&lt;/code&gt;, &lt;code&gt;DATABASES: list&lt;/code&gt;, etc. then we can at least validate that the setting is using the correct Python type.&lt;/p&gt;

&lt;p&gt;One downside is that this doesn't help the developer when writing the code, i.e. this is not autocomplete in the IDE. However, it &lt;em&gt;can&lt;/em&gt; be used as a runtime check as part of Django's check system. This would automatically alert the developer they are using the wrong type when &lt;code&gt;runserver&lt;/code&gt; starts up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling dictionaries
&lt;/h2&gt;

&lt;p&gt;The above type check seems useful for simple settings, however another problem is that some settings are lists of dictionaries or dictionaries of dictionaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENGINE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.db.backends.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db.sqlite3&lt;/span&gt;&lt;span class="sh"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be very error-prone for developers because the keys like "ENGINE", "NAME", etc. are just strings. One approach here might be to provide helper methods which can guide the developer to use the correct functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django_settings_check&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DATABASE&lt;/span&gt;

&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.db.backends.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db.sqlite3&lt;/span&gt;&lt;span class="sh"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DATABASE&lt;/code&gt; is actually a function with defined args which means the IDE can autocomplete the acceptable options. And then it returns a dictionary which is what Django expects for settings.&lt;/p&gt;

&lt;p&gt;There &lt;em&gt;are&lt;/em&gt; a few other libraries for Django that provide cleaner ways to specify settings, but I have shied away from them in the past because they seem too "different" than regular Django. Maybe there is a way to implement this without changing too much about the default Django setup.&lt;/p&gt;

&lt;p&gt;Looking for feedback about these ideas or if developers think this might be useful.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
    </item>
    <item>
      <title>Setup Dozzle with Auth on Coolify</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Tue, 11 Nov 2025 12:37:55 +0000</pubDate>
      <link>https://dev.to/adamghill/setup-dozzle-with-auth-on-coolify-cof</link>
      <guid>https://dev.to/adamghill/setup-dozzle-with-auth-on-coolify-cof</guid>
      <description>&lt;p&gt;I have been using &lt;a href="https://coolify.io" rel="noopener noreferrer"&gt;Coolify&lt;/a&gt; for over a year to self-host on &lt;a href="https://hetzner.cloud/?ref=2SkHDuMdo4Vt" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt; and I am a big fan. However, I wanted a lightweight way to see how all of my websites were performing.&lt;/p&gt;

&lt;p&gt;I had used &lt;a href="https://dozzle.dev/" rel="noopener noreferrer"&gt;Dozzle&lt;/a&gt; in the past as a way to see logs, but it has drastically improved and can now show real-time resource information now as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dozzle.dev/guide/authentication" rel="noopener noreferrer"&gt;Adding authentication to Dozzle&lt;/a&gt; is not too challenging, however, I had to cobble together a few resources, so here is a step-by-side that I did for future me.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In your deployed Coolify app, &lt;code&gt;Resources&lt;/code&gt;-&amp;gt;&lt;code&gt;+ New&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type "dozzle" in search bar&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Dozzle With Auth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;Service Name&lt;/code&gt; to "dozzle"&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Save&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Persistent Storages&lt;/code&gt; in the left side bar&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Files&lt;/code&gt; in the tab bar&lt;/li&gt;
&lt;li&gt;In a terminal on your local machine, run &lt;code&gt;docker run -it --rm amir20/dozzle generate admin --password YOUR_PASSWORD --email YOUR_EMAIL_ADDRESS&lt;/code&gt; and copy the output&lt;/li&gt;
&lt;li&gt;Paste the output from your terminal into the &lt;code&gt;Content&lt;/code&gt; text area under the &lt;code&gt;Files&lt;/code&gt; tab in Coolify&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Save&lt;/code&gt; at the bottom of the text area&lt;/li&gt;
&lt;li&gt;(optional) Under &lt;code&gt;Services&lt;/code&gt;, click &lt;code&gt;Settings&lt;/code&gt; and update &lt;code&gt;Domains&lt;/code&gt; to point to whatever domain you set up&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Start&lt;/code&gt;/&lt;code&gt;Restart&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dozzle dashboard:&lt;/p&gt;

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

&lt;p&gt;Dozzle logs:&lt;/p&gt;

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

</description>
      <category>coolify</category>
      <category>devops</category>
      <category>logging</category>
      <category>docker</category>
    </item>
    <item>
      <title>Making Time</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Sun, 10 Aug 2025 12:44:35 +0000</pubDate>
      <link>https://dev.to/adamghill/making-time-4ij0</link>
      <guid>https://dev.to/adamghill/making-time-4ij0</guid>
      <description>&lt;p&gt;I get asked how I "do so much" sometimes when I meet people in the context of starting &lt;a href="https://adamghill.com" rel="noopener noreferrer"&gt;new side projects&lt;/a&gt;, releasing &lt;a href="https://github.com/adamghill" rel="noopener noreferrer"&gt;open source libraries&lt;/a&gt;, or being active in a community. None of this is prescriptive -- everyone has to decide what they want for themselves, but I &lt;em&gt;have&lt;/em&gt; self-reflected on this question a decent amount.&lt;/p&gt;

&lt;p&gt;But, first a caveat: one lesson I have learned over time is to try to not judge myself compared to others -- especially those I do not know well. I have gone through seasons of my life (a term I first heard on a Startups for the Rest of Us podcast a long time ago) where I mostly consumed instead of created. I have taken sabbaticals where I did not think about or write any code at all. I have taken vacations where I have completely disconnected from the digital life. To me, that's healthy and everyone gets to decide what makes them the happiest. However, if you are interested in "making more time" in your life to work on a side project (either for a short amount of time or longer), I do have some ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cut out everything extraneous
&lt;/h2&gt;

&lt;p&gt;Time with family, day job, health (working out, sleep, cooking). Those are my priorities (and my current priority order) and I stick to them, but everything else is negotiable. You should make your own list! What isn't on my list: watching TV, sports, video games, etc. All things which tend to eat. up. time. If the goal is to make more free time in your day, I (mostly) avoid things that will suck up lots of it with minimal return on investment.&lt;/p&gt;

&lt;p&gt;But but but, I hear you say, I like video games! That's fine, make your own list. But, know there is only so much finite time in the day so you are making an implicit trade-off.&lt;/p&gt;

&lt;p&gt;Your trade-offs will change over time as well. When I was younger I could get less sleep to free up more time, but I cannot do that anymore without wrecking my next day. So, sleep became a non-negotiable, but other things got de-prioritized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consume
&lt;/h2&gt;

&lt;p&gt;It might sound counter-intuitive after the last point, but one way I get exposed to different ideas is to consume more media. However, it needs to be a specific type which inspires me -- for me that is podcasts, books (mostly around business, bootstrapping, UX, and design), and (maybe surprisingly) social media.&lt;/p&gt;

&lt;h3&gt;
  
  
  Podcasts
&lt;/h3&gt;

&lt;p&gt;The benefit is that I can listen to podcasts when I'm already doing something else: mowing the lawn, doing dishes, driving, grocery shopping, etc. I curate my subscriptions religiously, but I tend to listen to tech or business-focused podcasts -- here are the ones I prioritize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.startupsfortherestofus.com" rel="noopener noreferrer"&gt;Startups for the Rest of Us&lt;/a&gt; (tactics for bootstrappers, some interviews, some Q&amp;amp;A)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://microconfpodcast.com" rel="noopener noreferrer"&gt;MicroConf On Air&lt;/a&gt; (conference talks about bootstrapping)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mostlytechnical.com" rel="noopener noreferrer"&gt;Mostly Technical&lt;/a&gt; (2 guys talking, bootstrapping, somewhat Laravel-focused)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://notesonwork.com" rel="noopener noreferrer"&gt;Notes on Work&lt;/a&gt; (off-the-cuff Caleb Porzio, creator of Livewire and Flux)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://softskills.audio" rel="noopener noreferrer"&gt;Soft Skills Engineering&lt;/a&gt; (not business at all, but I love it)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.theringer.com/podcasts/the-town-with-matthew-belloni" rel="noopener noreferrer"&gt;The Town&lt;/a&gt; (the business of Hollywood)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mfmpod.com" rel="noopener noreferrer"&gt;My First Million&lt;/a&gt; (somewhat bro-y business dudes)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.lennysnewsletter.com/podcast" rel="noopener noreferrer"&gt;Lenny's Podcast&lt;/a&gt; (product-focused, but can feel "big tech"-heavy)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://newsletter.pragmaticengineer.com/podcast" rel="noopener noreferrer"&gt;The Pragmatic Engineer&lt;/a&gt; (can feel AI-heavy)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://djangochat.com/" rel="noopener noreferrer"&gt;Django Chat&lt;/a&gt; (interviews with Django folks)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have tried listening to audio books, but I could never get into the habit for some reason. In my opinion, most podcasts are shorter and are easier to jump into and out of, so maybe that's why.&lt;/p&gt;

&lt;h3&gt;
  
  
  Books
&lt;/h3&gt;

&lt;p&gt;I mostly read before I go to bed as a way to wind down. I tend to go back and forth between fiction (creativity! fun!) and non-fiction (learn! new ideas!).&lt;/p&gt;

&lt;h3&gt;
  
  
  Social media
&lt;/h3&gt;

&lt;p&gt;With the death of Twitter (yes it died, no I'm not on X 😂), I have switched to Bluesky and Mastodon which I use similarly. With both, I heavily curate my feeds to tech and business folks. If someone is consistently a downer -- unfollow. If someone always posts about politics -- unfollow. If someone only posts about technology I don't care about -- unfollow. I don't feel bad about it and neither should you. &lt;/p&gt;

&lt;p&gt;People get to post whatever they want, but I also get to unfollow them if I want. 🤷&lt;/p&gt;

&lt;p&gt;Social media exposes me to ideas and thoughts from outside of my bubble which can be useful. And since Bluesky and Mastodon don't have aggresive algorithms boosting rage-inducing posts all the time, it's mostly pleasant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limit innovation tokens
&lt;/h2&gt;

&lt;p&gt;"Innovation tokens" are a concept from &lt;a href="https://mcfunley.com/choose-boring-technology" rel="noopener noreferrer"&gt;Choose Boring Technology&lt;/a&gt; which resonated strongly with me when I first read it and I think about constantly. It is basically a way to reduce risk by using technology where you already know the trade-offs. It applies to my dayjob, but it is &lt;em&gt;especially&lt;/em&gt; important for side projects when time is limited.&lt;/p&gt;

&lt;p&gt;Be explicit about your goals -- if the goal is to learn a new technology, then use as many innovation tokens as you want. Go bananas. Spend 4 weeks setting up a kubernetes cluster because you read a blog post about it. However, your users do. not. care. Probably. 😉&lt;/p&gt;

&lt;p&gt;For every new side project, I limit the number of new technology I try to incorporate -- and stick with my "boring technology stack" for everything else. However, my goal is normally to launch &lt;em&gt;something&lt;/em&gt; as fast as possible. However, I also think it's important to play with new tech -- that is part of what makes it fun to me personally.&lt;/p&gt;

&lt;h2&gt;
  
  
  De-scope to de-risk
&lt;/h2&gt;

&lt;p&gt;To me, the ability to launch new side projects fast is tied to de-scoping and prioritization. What is the minimal amount of features so that someone else can try your thing? It's different for every project and it's where you get to demonstrate your personal taste. I've released a new open source library that is literally one function. I have launched (along with &lt;a href="https://sangeeta.io" rel="noopener noreferrer"&gt;Sangeeta&lt;/a&gt;) a "social network" where you can only sign up and add links to a profile.&lt;/p&gt;

&lt;p&gt;I tend to put things out in the world that solve one particular problem without thinking about it too much. Do people seem to like it? Are they sharing it, giving it stars, etc? Are you getting feedback? Does this thing &lt;em&gt;resonate&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Steve Blank says &lt;a href="https://steveblank.com/2010/04/08/no-plan-survives-first-contact-with-customers-%E2%80%93-business-plans-versus-business-models/" rel="noopener noreferrer"&gt;"No plan survives first contact with customers"&lt;/a&gt;. In the same vein, I think "Projects that never launch never progress". You need to launch to get feedback, so launching as fast as possible is one of my expicit goals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeatedly roll the ball forward
&lt;/h2&gt;

&lt;p&gt;Consistently working on one thing and making small iterative improvements based on a highly prioritized list are the compound interest of side projects: it stacks up over time. After a month you can look back and think, "how did I get from 0 to here with only an hour a day?!" But, it's true and I have seen it happen over and over again. &lt;/p&gt;

&lt;p&gt;Repeatedly showing up and focusing can build amazing functionality over time. Start small, iterate, and improve.&lt;/p&gt;

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

&lt;p&gt;Again, I'll reiterate that what works for me probably won't work for you -- everyone is in a different stage of life and gets to prioritize their life as they want. However, hopefully some of this has been helpful or given you some ideas about how to make time in your life if you want to work on more side projects or launch that new open source library!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thank you to Tim White and Sangeeta Jadoonanan for proofreading and edits.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>Request for maintainer(s) for django-unicorn</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Tue, 03 Jun 2025 00:38:38 +0000</pubDate>
      <link>https://dev.to/adamghill/request-for-maintainers-4b08</link>
      <guid>https://dev.to/adamghill/request-for-maintainers-4b08</guid>
      <description>&lt;p&gt;After starting building &lt;code&gt;django-unicorn&lt;/code&gt; in July 2020 and spending probably 3-4 years diligently adding features, fixing bugs, and driving the project forward, I have decided I need to officially look for other maintainers to help out with the project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;django-unicorn&lt;/code&gt; is an interactive component library for Django. It sort of works similarly to &lt;code&gt;Livewire&lt;/code&gt; for Laravel.&lt;/p&gt;

&lt;p&gt;Because I am, to a fault, pretty transparent, here are some of the pros and cons of working on &lt;code&gt;django-unicorn&lt;/code&gt;. 🦄&lt;/p&gt;

&lt;h2&gt;
  
  
  The good
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;django-unicorn&lt;/code&gt; has over 2.5k stars on GitHub, so I'm pretty sure it is solving a pain point for Django developers.&lt;/li&gt;
&lt;li&gt;It's pretty unique in the Django ecosystem, but other languages have similar libraries, so there are constant places to look for inspiration.&lt;/li&gt;
&lt;li&gt;Pretty high test coverage, the use of Python types, etc.&lt;/li&gt;
&lt;li&gt;It is used in production by a number of companies AFAIK.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bad
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It's complicated: there is both a custom JavaScript reactive library and Django back-end code.&lt;/li&gt;
&lt;li&gt;The backend in particular does some ✨ magic ✨ with undocumented Django APIs.&lt;/li&gt;
&lt;li&gt;Replicating user bugs can be a challenging experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The ugly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Some features probably need a re-think, e.g. child components.&lt;/li&gt;
&lt;li&gt;Requiring a cache for components was useful, but probably not a great approach going forward.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HTMX&lt;/code&gt; seems to have "won" for frontend libraries (that aren't React/Vue) in the Django ecosystem (no shade here -- I'm a fan of &lt;code&gt;HTMX&lt;/code&gt; as well).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The future
&lt;/h2&gt;

&lt;p&gt;Ideally, one (or more!) people would be interested in helping me work on &lt;code&gt;django-unicorn&lt;/code&gt; going forward. I'm not sure I can commit to working on it forever, but I do pledge to help others ramp up, work through issues together, and help guide the project forward.&lt;/p&gt;

&lt;p&gt;I do think there are lots of opportunities to improve the project (I have a list of pie-in-the-sky ideas if anyone is interested), but even if someone was willing to help triage and write tests for issues that would be a good start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why would you want to help?
&lt;/h2&gt;

&lt;p&gt;A chance to work on a project that already has some traction, improve it, and help out the Django community to build cooler (read: more interactive) websites quicker.&lt;/p&gt;

&lt;p&gt;Do you have ideas about how to build better websites in Django quicker? I'd love to hear from you. Please DM on Mastodon (&lt;a href="mailto:adamghill@indieweb.social"&gt;adamghill@indieweb.social&lt;/a&gt;) or in this discussion: &lt;a href="https://github.com/adamghill/django-unicorn/discussions/746" rel="noopener noreferrer"&gt;https://github.com/adamghill/django-unicorn/discussions/746&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  More context
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.django-unicorn.com" rel="noopener noreferrer"&gt;https://www.django-unicorn.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adamghill/django-unicorn" rel="noopener noreferrer"&gt;https://github.com/adamghill/django-unicorn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The journey
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/adamghill/add-some-magic-to-your-django-website-l8k"&gt;https://dev.to/adamghill/add-some-magic-to-your-django-website-l8k&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/adamghill/how-to-escape-the-tyranny-of-complicated-javascript-in-4-easy-steps-1eo7"&gt;https://dev.to/adamghill/how-to-escape-the-tyranny-of-complicated-javascript-in-4-easy-steps-1eo7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/adamghill/lessons-learned-building-a-fullstack-framework-for-django-2cml"&gt;https://dev.to/adamghill/lessons-learned-building-a-fullstack-framework-for-django-2cml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>html</category>
    </item>
    <item>
      <title>The definitive guide to using Django with SQLite in production 💡</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Sat, 18 Jan 2025 19:02:13 +0000</pubDate>
      <link>https://dev.to/adamghill/the-definitive-guide-to-using-django-with-sqlite-in-production-2e9b</link>
      <guid>https://dev.to/adamghill/the-definitive-guide-to-using-django-with-sqlite-in-production-2e9b</guid>
      <description>&lt;p&gt;I have been running Django sites in production under heavy load for over 10 years at my day job. We started with a MySQL database backend but, after running into a few issues, switched to PostgreSQL which has been rock-solid. I tend to use the same stack for side projects. Especially because, initially, most of my projects were hosted on Heroku and they had stellar support for PostgreSQL. Now, having bounced from Heroku to Render to Fly.io to &lt;a href="https://m.do.co/c/617d629f56c0" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt; (with &lt;a href="https://caprover.com/" rel="noopener noreferrer"&gt;CapRover&lt;/a&gt;) to &lt;a href="https://hetzner.cloud/?ref=2SkHDuMdo4Vt" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt; (with &lt;a href="https://coolify.io" rel="noopener noreferrer"&gt;Coolify&lt;/a&gt;), I am re-evaluating my default choice of database.&lt;/p&gt;

&lt;p&gt;I currently have a managed PostgreSQL database at &lt;a href="https://m.do.co/c/617d629f56c0" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt; which has worked well, but I have been looking into using &lt;a href="https://www.sqlite.org/" rel="noopener noreferrer"&gt;SQLite&lt;/a&gt; in production to reduce server costs and network latency. And, since I'm not particularly DevOps-y, I do not want to be on the hook for maintaining my own PostgreSQL database. So, I have been investigating other solutions for my newest side project, &lt;a href="https://filmcliq.com" rel="noopener noreferrer"&gt;filmcliq.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The promise
&lt;/h2&gt;

&lt;p&gt;There has been a lot of conversation about using SQLite as a production database for websites for the past few years, espeically in the Rails community with &lt;a href="https://github.com/oldmoe/litestack" rel="noopener noreferrer"&gt;Litestack&lt;/a&gt;. And now, in the latest version of Rails, SQLite has become the defacto backend for many parts of the stack.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://rubyonrails.org/2024/11/7/rails-8-no-paas-required" rel="noopener noreferrer"&gt;Rails 8 release notes&lt;/a&gt; details a lot of the "why" for using SQLite in production. But, mostly it comes down to: reduce the complexity of building and maintaining a website.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No separate database server, e.g. PostgreSQL&lt;/li&gt;
&lt;li&gt;No separate cache server, e.g. redis&lt;/li&gt;
&lt;li&gt;No separate queue broker server, e.g. RabbitMQ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Especially&lt;/em&gt; for side projects with limited traffic requirements and scaling concerns, the promise of SQLite is that it can remove a lot of ongoing hassles. Without a separate database, cache, or queue, there is less network traffic (because the SQLite file is local), and less servers to manage, maintain, and backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some mild concerns
&lt;/h2&gt;

&lt;p&gt;It's not all gravy, though. There are a few things to watch out for when using SQLite in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  One container to rule them all
&lt;/h3&gt;

&lt;p&gt;Because the SQLite file is typically local to the container, it is not straight-forward to have multiple containers share the same database. Usually that will not be a problem for low to medium traffic sites because they might not need to scale horizontally beyond a single container. Increasing the number of worker processes in &lt;code&gt;gunicorn&lt;/code&gt; (or other webservers) and/or increasing the amount of CPUs available to the container can mitigate this restriction.&lt;/p&gt;

&lt;p&gt;There are also a few other options that attempt to scale SQLite across multiple hosts. I have not investigted any of these fully, but wanted to mention them just in case.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://sqlitecloud.io/" rel="noopener noreferrer"&gt;SQLite Cloud&lt;/a&gt;: Cloud-based SQLite database service (&lt;a href="https://docs.sqlitecloud.io/docs/quick-start-django" rel="noopener noreferrer"&gt;Django quick start&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://turso.tech/libsql" rel="noopener noreferrer"&gt;Turso libSQL&lt;/a&gt;: A fork of SQLite that supports distributed databases (&lt;a href="https://github.com/profusion/django_libsql" rel="noopener noreferrer"&gt;django-libsql&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rqlite.io/" rel="noopener noreferrer"&gt;rqlite&lt;/a&gt;: The lightweight, user-friendly, distributed relational database built on SQLite&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/maxpert/marmot" rel="noopener noreferrer"&gt;marmot&lt;/a&gt;: A distributed SQLite replicator built on top of NATS&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deployment downtime
&lt;/h3&gt;

&lt;p&gt;Because of the "only one host" issue, no-downtime deployment is a little tricky. I detail an approach for limited downtime during deployment below using &lt;a href="https://litestream.io/" rel="noopener noreferrer"&gt;Litestream&lt;/a&gt; to replicate/restore the database, which seems like an acceptable trade-off for many projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django settings
&lt;/h2&gt;

&lt;p&gt;Django has great initial support for SQLite and, with a few tweaks, it can serve production traffic for the database, cache, and queue broker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database
&lt;/h3&gt;

&lt;p&gt;Django has &lt;a href="https://docs.djangoproject.com/en/stable/ref/databases/#sqlite-notes" rel="noopener noreferrer"&gt;built-in support for SQLite&lt;/a&gt; and the default &lt;code&gt;settings.py&lt;/code&gt; uses SQLite, so you have probably seen something like this before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENGINE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.db.backends.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db.sqlite3&lt;/span&gt;&lt;span class="sh"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These settings will work okay for local development, but they are not optimal for production. When running under any sort of load you will run into the dreaded &lt;a href="https://blog.pecar.me/django-sqlite-dblock" rel="noopener noreferrer"&gt;database is locked&lt;/a&gt; error. To prevent this issue, change the &lt;code&gt;"default"&lt;/code&gt; database to include the following options which are based on best practices from Rails. I also tend to put the database in a separate directory (configured with the &lt;code&gt;"NAME"&lt;/code&gt; key) called "db" to keep the root directory clean.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The settings below are only applicable in Django 5.1+. If you are running an older version see &lt;a href="https://blog.pecar.me/sqlite-django-config#in-django-50-42-or-older" rel="noopener noreferrer"&gt;Anže's article&lt;/a&gt; for another approach.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENGINE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.db.backends.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db/site.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPTIONS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transaction_mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IMMEDIATE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# seconds
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;init_command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
                PRAGMA journal_mode=WAL;
                PRAGMA synchronous=NORMAL;
                PRAGMA mmap_size=134217728;
                PRAGMA journal_size_limit=27103364;
                PRAGMA cache_size=2000;
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You might wonder if the &lt;a href="https://www.sqlite.org/pragma.html#pragma_foreign_keys" rel="noopener noreferrer"&gt;foreign_keys PRAGMA&lt;/a&gt; would be useful. Lucky for you, Django &lt;a href="https://github.com/django/django/blob/stable/5.1.x/django/db/backends/sqlite3/base.py#L203" rel="noopener noreferrer"&gt;already includes it&lt;/a&gt; for SQLite databases!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Cache
&lt;/h3&gt;

&lt;p&gt;Django has a &lt;a href="https://docs.djangoproject.com/en/stable/topics/cache/#database-caching" rel="noopener noreferrer"&gt;built-in database cache&lt;/a&gt;. You can enable it by adding the following to your &lt;code&gt;settings.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;CACHES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BACKEND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.core.cache.backends.db.DatabaseCache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOCATION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache&lt;/span&gt;&lt;span class="sh"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, that will use the &lt;code&gt;"default"&lt;/code&gt; database configured above. Depending on your needs, you may want to use a separate SQLite database for the cache. I like this approach because I tend to think of cache as ephemeral, therefore I do not want the cache database table to be backed up along with the main database.&lt;/p&gt;

&lt;h4&gt;
  
  
  Update databases settings
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENGINE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.db.backends.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db/site.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPTIONS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transaction_mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IMMEDIATE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# seconds
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;init_command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
                PRAGMA journal_mode=WAL;
                PRAGMA synchronous=NORMAL;
                PRAGMA mmap_size=134217728;
                PRAGMA journal_size_limit=27103364;
                PRAGMA cache_size=2000;
            &lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENGINE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.db.backends.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db/cache.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPTIONS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transaction_mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IMMEDIATE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# seconds
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;init_command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
                PRAGMA journal_mode=WAL;
                PRAGMA synchronous=NORMAL;
                PRAGMA mmap_size=134217728;
                PRAGMA journal_size_limit=27103364;
                PRAGMA cache_size=2000;
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Add a cache database router
&lt;/h4&gt;

&lt;p&gt;Because the cache database is separate from the &lt;code&gt;"default"&lt;/code&gt; database, we need to tell Django about it with a &lt;a href="https://docs.djangoproject.com/en/stable/topics/db/multi-db/#database-routers" rel="noopener noreferrer"&gt;database router&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;routers.py&lt;/code&gt; and add the following to it. I put it in a directory named "project", but it can go anywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# routers.py
&lt;/span&gt;
&lt;span class="n"&gt;DJANGO_CACHE_APP_LABEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django_cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CacheRouter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Route cache queries to a separate &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; database.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;db_for_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DJANGO_CACHE_APP_LABEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;db_for_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DJANGO_CACHE_APP_LABEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allow_migrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;app_label&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DJANGO_CACHE_APP_LABEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the following to &lt;code&gt;settings.py&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;DATABASE_ROUTERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project.database_routers.CacheRouter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cache table creation
&lt;/h4&gt;

&lt;p&gt;You will need to call &lt;a href="https://docs.djangoproject.com/en/5.1/ref/django-admin/#django-admin-createcachetable" rel="noopener noreferrer"&gt;createcachetable&lt;/a&gt; as part of the deployment process to create the cache table like the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py createcachetable &lt;span class="nt"&gt;--database&lt;/span&gt; cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Queue
&lt;/h3&gt;

&lt;p&gt;In the past, there were specific services like &lt;code&gt;RabbitMQ&lt;/code&gt; that were used for queueing tasks. However, there are a few Django libraries that can use the database (and by extension SQLite) to queue background jobs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/django-q2/django-q2" rel="noopener noreferrer"&gt;django-q2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dabapps/django-db-queue" rel="noopener noreferrer"&gt;django-db-queue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huey.readthedocs.io" rel="noopener noreferrer"&gt;huey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployments
&lt;/h2&gt;

&lt;p&gt;As mentioned above, because SQLite is just a file in the same container as the webserver, when a new container spins up with a deployment, the current state of the database will be lost.&lt;/p&gt;

&lt;p&gt;Thankfully, there are a few approaches to handle this. One is to replicate the continually current database state to an S3 bucket and then restore it during new deployments.&lt;/p&gt;

&lt;p&gt;The basic idea is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replicate the current state of the database to an S3 bucket while container 1 is serving traffic&lt;/li&gt;
&lt;li&gt;When a new deployment starts, spin up container 2&lt;/li&gt;
&lt;li&gt;Container 2 downloads the current state of the database from the S3 bucket&lt;/li&gt;
&lt;li&gt;Switch traffic from container 1 to container 2&lt;/li&gt;
&lt;li&gt;Container 2 starts serving traffic and replicating the database to the S3&lt;/li&gt;
&lt;li&gt;Container 1 shuts down&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With something like &lt;a href="https://coolify.io/" rel="noopener noreferrer"&gt;Coolify&lt;/a&gt;, the switch from container 1 to container 2 will be handled automatically for you. There will be a &lt;em&gt;slight&lt;/em&gt; blip during deployments, but it will be very minimal -- that's the trade-off for less complexity and a simpler infrastructure setup. And with a CDN like Cloudflare or Fastly serving the website, the downtime potentially can be non-existent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://litestream.io/" rel="noopener noreferrer"&gt;Litestream&lt;/a&gt; is the gold standard for replicating SQLite databases to S3. There are many S3-compatible storage options available. I currently ship a replica to both &lt;a href="https://www.cloudflare.com/developer-platform/products/r2/" rel="noopener noreferrer"&gt;Cloudflare R2&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; &lt;a href="https://www.hetzner.com/storage/object-storage/" rel="noopener noreferrer"&gt;Hetzner Object Storage&lt;/a&gt; just in case. &lt;a href="https://www.backblaze.com/cloud-storage" rel="noopener noreferrer"&gt;Backblaze B2&lt;/a&gt; is another option, as is &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;AWS S3&lt;/a&gt;, obviously.&lt;/p&gt;

&lt;p&gt;First, I install &lt;code&gt;Litestream&lt;/code&gt; as part of my &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;...

# Install wget and Litestream
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/var/cache/apt,sharing&lt;span class="o"&gt;=&lt;/span&gt;locked &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/apt,sharing&lt;span class="o"&gt;=&lt;/span&gt;locked &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get update &lt;span class="nt"&gt;--fix-missing&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; wget &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    wget https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.deb &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; litestream-v0.3.13-linux-amd64.deb

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["./entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the &lt;code&gt;entrypoint.sh&lt;/code&gt; file, I add the following to restore the database from S3, collect static assets, migrate the database, create the cache table, and finally, start the webserver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Setup database..."&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"db"&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; a+rwX &lt;span class="s2"&gt;"db"&lt;/span&gt;
litestream restore &lt;span class="nt"&gt;-config&lt;/span&gt; litestream.yml &lt;span class="nt"&gt;-if-db-not-exists&lt;/span&gt; &lt;span class="nt"&gt;-if-replica-exists&lt;/span&gt; &lt;span class="s2"&gt;"db/site.sqlite"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Collect static files..."&lt;/span&gt;
python manage.py collectstatic &lt;span class="nt"&gt;--noinput&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Migrate databases..."&lt;/span&gt;
python manage.py migrate &lt;span class="nt"&gt;--noinput&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Create cache table..."&lt;/span&gt;
python manage.py createcachetable &lt;span class="nt"&gt;--database&lt;/span&gt; cache

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Start litestream and gunicorn..."&lt;/span&gt;
litestream replicate &lt;span class="nt"&gt;-config&lt;/span&gt; litestream.yml &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="s2"&gt;"gunicorn project.wsgi --config=gunicorn.conf.py"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;-exec&lt;/code&gt; argument for &lt;a href="https://litestream.io/reference/replicate/" rel="noopener noreferrer"&gt;&lt;code&gt;replicate&lt;/code&gt;&lt;/a&gt; starts the &lt;code&gt;gunicorn&lt;/code&gt; process and allows &lt;code&gt;litestream&lt;/code&gt; to do simple process management for the webserver.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the &lt;code&gt;litestream.yml&lt;/code&gt; file, I have the following &lt;a href="https://litestream.io/reference/config/" rel="noopener noreferrer"&gt;configuration&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db/site.sqlite3&lt;/span&gt;
    &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_s3&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;s3&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;s3-endpoint.com&lt;/span&gt;
        &lt;span class="na"&gt;access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ACCESS_KEY_ID&lt;/span&gt;
        &lt;span class="na"&gt;secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SECRET_ACCESS_KEY&lt;/span&gt;
        &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;bucket-name&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;site/site.sqlite3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;access-key-id&lt;/code&gt; and &lt;code&gt;secret-access-key&lt;/code&gt; can be read from environment variables to prevent checking in secrets to version control.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;With the above setup we have accomplished a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced complexity: no separate servers for database, cache, or queue broker&lt;/li&gt;
&lt;li&gt;Reduced network latency: the SQLite file is local to the container so there are no network hops&lt;/li&gt;
&lt;li&gt;Lower cost: no managed servers to pay for&lt;/li&gt;
&lt;li&gt;Less maintenance: less servers to manage, keep up to date, and backup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully this was helpful if you are looking to use SQLite with Django in production. Please reach out to me on &lt;a href="https://indieweb.social/@adamghill" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; or &lt;a href="https://bsky.app/profile/adamghill.com" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt; if you have any questions or feedback!&lt;/p&gt;

&lt;h2&gt;
  
  
  More resources, documentation, and details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://litestream.io" rel="noopener noreferrer"&gt;Litestream&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/piepworks/blaze-starter/blob/main/start.sh" rel="noopener noreferrer"&gt;blaze-starter start.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.pecar.me/django-sqlite-dblock" rel="noopener noreferrer"&gt;Django, SQLite, and the Database is Locked Error&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.pecar.me/sqlite-django-config" rel="noopener noreferrer"&gt;Django SQLite Production Config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.pecar.me/sqlite-prod" rel="noopener noreferrer"&gt;Gotchas with SQLite in Production&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.pecar.me/django-sqlite-benchmark" rel="noopener noreferrer"&gt;Django SQLite Benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/django/django/blob/stable/5.1.x/django/db/backends/sqlite3/base.py#L203" rel="noopener noreferrer"&gt;Django SQLite foreign_keys pragma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oldmoe/litestack/blob/master/WHYLITESTACK.md" rel="noopener noreferrer"&gt;Why Litestack?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.wesleyac.com/posts/consider-sqlite" rel="noopener noreferrer"&gt;Consider SQLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=39065201" rel="noopener noreferrer"&gt;Ask HN: Are you using SQLite and Litestream in production?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Thank you to &lt;a href="https://fosstodon.org/@timlwhite" rel="noopener noreferrer"&gt;Tim White&lt;/a&gt; and &lt;a href="https://fosstodon.org/@sjbitcode" rel="noopener noreferrer"&gt;Sangeeta Jadoonanan&lt;/a&gt; for proof-reading this article.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@jankolar?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Jan Antonin Kolar&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/brown-wooden-drawer-lRoX0shwjUQ?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>sqlite</category>
      <category>devops</category>
    </item>
    <item>
      <title>So you want to make a podcast</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Fri, 31 May 2024 12:50:48 +0000</pubDate>
      <link>https://dev.to/adamghill/so-you-want-to-make-a-podcast-242c</link>
      <guid>https://dev.to/adamghill/so-you-want-to-make-a-podcast-242c</guid>
      <description>&lt;p&gt;On my drive home from &lt;a href="https://2023.djangocon.us"&gt;DjangoCon 2023&lt;/a&gt; I came up with a wild idea.&lt;/p&gt;

&lt;p&gt;I should make a podcast about Django.&lt;/p&gt;

&lt;p&gt;But, I didn't want to make a podcast by myself! However, I had just worked with my coworker and friend, Sangeeta, to create &lt;a href="https://djangostickers.com"&gt;quirky stickers&lt;/a&gt; to give out at DjangoCon, and she would be the perfect co-host.&lt;/p&gt;

&lt;p&gt;There are already a few podcasts about Django available, but I thought we could do a fun spin on it. And similar to when we decided, spur of the moment, to sign up to give a Lightning Talk at DjangoCon, the idea was half-exciting and half-terrifying.&lt;/p&gt;

&lt;p&gt;We both listen to a lot of podcasts so we already had some ideas about what makes good content and what doesn't. And between the Django stickers and an internal work presentation we co-created in the past, Sangeeta and I know how to work well together on a creative project.&lt;/p&gt;

&lt;p&gt;We talked about a few other topics we could make a podcast about, but we kept coming back to Django -- we both use it daily, have lots of production experience with it, have opinions about it, &lt;em&gt;and&lt;/em&gt; it seemed like a nice way to give something back to the Django community.&lt;/p&gt;

&lt;p&gt;From inception (October 2023) to the first episode launch (March 2024) of &lt;a href="https://djangobrew.com"&gt;Django Brew&lt;/a&gt;, it has been a huge learning curve. We knew what we wanted the podcast to be like, but we didn't know how to create it. We still aren't professional podcasters or anything, but we are three episodes in, so we know more now than we did at the beginning!&lt;/p&gt;

&lt;h2&gt;
  
  
  Decisions, decisions
&lt;/h2&gt;

&lt;p&gt;First, decide on what sort of content you are uniquely qualified to talk about. Lean into your "unfair advantage" because that will resonate best with the audience. For us, we are passionate about Django and the community. I think all of those qualities come through in the podcast. If we didn't care as much the audience would instinctively know.&lt;/p&gt;

&lt;p&gt;Then, decide on what kind of podcast you want to make. There are interviews, news, tutorials, long-winded opinion shows, and lots of things in-between. We knew of a few interview shows for Django, but fewer where developers talked about their experience building with Django.&lt;/p&gt;

&lt;p&gt;And decide why your podcast will be different than all the others. Our goal was to make the podcast like two friends talking about Django -- we wanted it to be funny, full of excitement, and a little bit goofy. We try to capture our personalities while talking about a framework that we love. I couldn't (and wouldn't!) want to make a podcast by myself. My favorite podcasts have 2 hosts because the dynamic completely changes with a second person -- I'm definitely not engaging enough to host a podcast by myself. 😂&lt;/p&gt;

&lt;h2&gt;
  
  
  Equipment
&lt;/h2&gt;

&lt;p&gt;Originally, we tried iPhone Bluetooth mics, laptop mics, and a few other corded and cordless mics. But, the sound quality wasn't great and we kept reading that a better mic would help, so we decided to splurge and each got a dedicated microphone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microphone
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.amazon.com/gp/product/B07K1XSDZP/"&gt;Samson Q2U&lt;/a&gt; came highly recommended from Reddit and &lt;a href="https://transistor.fm/how-to-start-a-podcast/podcasting-equipment-guide/"&gt;Transistor's podcast equipment guide&lt;/a&gt;. We've been pretty happy with the sound quality -- they aren't super high-end (we can't all be Wes Bos!), but for $100 for a mic, boom, shock mount, and pop filter it seemed reasonable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software
&lt;/h2&gt;

&lt;p&gt;We tried a few different software setups before settling on our current setup. I am sure there are less time-intensive ways to record a podcast, but this is the software and process we currently use. For other types of podcasts (or more seasoned professionals!), it might be completely different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notion
&lt;/h3&gt;

&lt;p&gt;Sangeeta and I use the free tier of &lt;a href="https://www.notion.so"&gt;Notion&lt;/a&gt; to brainstorm episode topics, write scripts, and keep a rough outline for each episode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Riverside
&lt;/h3&gt;

&lt;p&gt;We use the free tier of &lt;a href="https://riverside.fm"&gt;Riverside&lt;/a&gt; to record the actual podcast audio. One benefit over Zoom is that there is no time limit for the "meeting". It also has the ability to export each person's audio separately which makes the editing of the final audio easier. We don't use any other features in Riverside other than recording audio, but might look into it more in the future.&lt;/p&gt;

&lt;p&gt;There &lt;em&gt;is&lt;/em&gt; a limit in Riverside for how much recorded content can be stored every month, but we don't currently hit that limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audacity
&lt;/h3&gt;

&lt;p&gt;After recording each session, I export each audio track as a WAV and import it into &lt;a href="https://www.audacityteam.org"&gt;Audacity&lt;/a&gt;. There is a little bit of a learning curve with Audacity, but it is free and rock-solid. For the limited things I want to do (i.e. cut/copy/paste, silence, limit, fade-in, and fade-out) it works great.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8iujlcvqg9tt9nwgvuu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8iujlcvqg9tt9nwgvuu.png" alt="Image description" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a 20-30 minute episode, we tend to record 2-3 hours of audio over multiple sessions (that includes lots of re-takes, general chatting,  and some planning of what to talk about next)!&lt;/p&gt;

&lt;p&gt;I do rough cuts of all of the audio together and then I send drafts to Sangeeta for her to listen to and give me notes. We'll go back and forth over multiple drafts, making notes for each other, and deciding on different parts we want to change or re-record. It probably takes at least a few hours to do all of the editing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiik7vgsvubpj95fni8ih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiik7vgsvubpj95fni8ih.png" alt="Image description" width="556" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As part of the editing process, I also grab anything particularly goofy and keep another project with all of our episode bloopers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ableton
&lt;/h3&gt;

&lt;p&gt;Once we get close to a finished product, I send the episode and bloopers to Sangeeta who uses &lt;a href="https://www.ableton.com"&gt;Ableton&lt;/a&gt; for the final edits. Ableton is not free, but Sangeeta had used it before for other projects and had already paid for it.&lt;/p&gt;

&lt;p&gt;She adds the intro and outro music (from &lt;a href="https://pixabay.com/music/"&gt;Pixabay&lt;/a&gt;) and goes through the episode one more time to tweak audio levels and do a general clean up.&lt;/p&gt;

&lt;p&gt;Then, she edits all of the bloopers together (side note: this is my favorite part of every episode). Most episodes have 5+ minutes of unedited bloopers, but she grabs only the best ones and stitches them together.&lt;/p&gt;

&lt;p&gt;After that's all done, Sangeeta exports the final cut and uploads it to Buzzsprout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buzzsprout
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.buzzsprout.com/"&gt;Buzzsprout&lt;/a&gt; is where we host the podcast episodes. It pushes new episodes to &lt;em&gt;all&lt;/em&gt; of the podcast aggregators, stores the podcasts, provides a handy embeddable player, and podcast analytics. Personally, I am not sure how accurate the podcast analytics are, but it is nice to get a sense if people are listening to the episodes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejckgv0bedy4dtxap07u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejckgv0bedy4dtxap07u.png" alt="Image description" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Coltrane
&lt;/h3&gt;

&lt;p&gt;We used the built-in Buzzspout hosted website for a little while, but eventually wanted more control over the styles and content. I grabbed all of the HTML and CSS from the hosted site and used my own mini web framework, &lt;a href="https://coltrane.readthedocs.io"&gt;Coltrane&lt;/a&gt;, to build a static site. We have a few ideas about potential further enhancements for the site in the future, but for now it's good enough for our purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Process
&lt;/h2&gt;

&lt;p&gt;For most of our episodes it's taken around a month to decide what to talk about, record, and edit. That's a long time! However, we only work on it nights and weekends because we have day jobs and other life priorities also tend to get in the way!&lt;/p&gt;

&lt;p&gt;For some episodes we have written out a basic script for what we wanted to talk about. For others, we had an outline, but it was recorded more "off the cuff". I don't think we have nailed down a perfect process yet, but we are still working through it. I think we'll also get better at talking "on-mic" over time, but like with most things practice makes &lt;del&gt;perfect&lt;/del&gt; better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing
&lt;/h2&gt;

&lt;p&gt;After we upload the new episode to Buzzsprout, we write custom show notes with links to everything we talked about.  The new episode will automatically show up on the website once it's published, but we also write a post for &lt;a href="https://twitter.com/djangobrew"&gt;Twitter&lt;/a&gt; and &lt;a href="https://fosstodon.org/@djangobrew"&gt;Mastodon&lt;/a&gt; to notify people on those platforms.&lt;/p&gt;

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

&lt;p&gt;That's our current process to record the &lt;a href="https://djangobrew.com"&gt;Django Brew&lt;/a&gt; podcast! I'm sure we'll learn new techniques and the process will change over time, but we've been pleasantly surprised by the community engagement and we look forward to creating more podcasts in the future!&lt;/p&gt;

&lt;p&gt;If you are interested in Django at all, please check out &lt;a href="https://djangobrew.com"&gt;Django Brew&lt;/a&gt; and let us know what you think. Or just listen to our bloopers and see how often we mess up while recording. 😉&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thanks to Sangeeta Jadoonanan for reading the first draft and giving some helpful feedback. And also being my podcast co-host.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@austindistel?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Austin Distel&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/two-black-headphones-on-brown-wooden-table-VCFxt2yT1eQ?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>podcast</category>
      <category>django</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Django Roadmap 2024</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Sun, 21 Jan 2024 16:39:36 +0000</pubDate>
      <link>https://dev.to/adamghill/django-roadmap-2024-2ocn</link>
      <guid>https://dev.to/adamghill/django-roadmap-2024-2ocn</guid>
      <description>&lt;p&gt;I saw Sarah's &lt;a href="https://mastodon.social/@sabderemane/111787906469153045"&gt;request&lt;/a&gt; for "what people want for Django" which included a link to Paolo's &lt;a href="https://fosstodon.org/@paulox/111783847246554164"&gt;post&lt;/a&gt; and &lt;a href="https://www.paulox.net/2024/01/19/my-django-roadmap-ideas/"&gt;blog article&lt;/a&gt; with details. I'm glad this was brought up by &lt;a href="https://fosstodon.org/@thibaudcolas"&gt;@thibaudcolas&lt;/a&gt; in this &lt;a href="https://forum.djangoproject.com/t/informal-roadmap-retrospective-workshops-for-django/26835"&gt;forum post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Turns out I have a few thoughts. 😅 Caveat: this is mostly "pie in the sky" sort of thoughts and I'm conveniently ignoring what would actually need to happen to make any of these ideas happen.&lt;/p&gt;

&lt;p&gt;This is also not meant to degrade any of the excellent work that has made Django what it is today. I ❤️ Django, use it for all of my projects, and just want the best for it so it continues to thrive long-term.&lt;/p&gt;

&lt;p&gt;One side note: I wonder if the annual Django survey could be leveraged for potential roadmap ideas? Something like: collect ideas, core devs whittle down the ideas to the top 20, and then the community votes. It wouldn't be binding or anything, but it might give insight on ideas from multiple different perspectives?&lt;/p&gt;

&lt;p&gt;In true roadmap fashion, I'm also going to try to include goals or metrics for the high-level things I'm thinking about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marketing
&lt;/h2&gt;

&lt;p&gt;As I mentioned in my &lt;a href="https://indieweb.social/@adamghill/111794263074539636"&gt;reply&lt;/a&gt; to Sarah, I'm not sure if it's a "roadmap item", but in my opinion Django needs better &lt;em&gt;marketing&lt;/em&gt; more than it needs new features. So, I'm going to focus on that first. Don't worry I also have my own pet features I would like to see in Django core. 😉&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of downloads goes up&lt;/li&gt;
&lt;li&gt;Developer use goes up (based on &lt;a href="https://survey.stackoverflow.co/2023/#section-most-popular-technologies-web-frameworks-and-technologies"&gt;StackOverflow developer survey&lt;/a&gt; maybe?)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Homepage
&lt;/h3&gt;

&lt;p&gt;I'm going to ignore all of the JavaScript web frameworks and I'm mostly going to focus on &lt;a href="https://rubyonrails.org"&gt;&lt;code&gt;Ruby on Rails&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://laravel.com"&gt;&lt;code&gt;Laravel&lt;/code&gt;&lt;/a&gt; because I think they are closest to the current use-case of &lt;code&gt;Django&lt;/code&gt;. They share the same downside of needing to know an additional language after JavaScript, are mostly server-side rendered, and database-centric.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Django headline is "Django makes it easier to build better web apps more quickly and with less code." which isn't bad per se, but doesn't hold a candle to the punchier Rails H1 "Compress the complexity of modern web apps."&lt;/li&gt;
&lt;li&gt;Both RoR and Laravel prominently showcase the companies that use them -- Django makes no mention of the huge companies that are built on top of it&lt;/li&gt;
&lt;li&gt;Rails has a 35 minute (!) video on the homepage which seems like overkill, but people do love seeing how something works, as opposed to just a bunch of words&lt;/li&gt;
&lt;li&gt;both talk about the features included in the framework: database modeling, authentication, etc.; Django has all these things (and more!)&lt;/li&gt;
&lt;li&gt;both talk about the number of contributors and community; Laravel even has user testimonials (similar to a SaaS product)&lt;/li&gt;
&lt;li&gt;both prominently say they are a "full-stack" framework&lt;/li&gt;
&lt;li&gt;both mention how to build SPA-like applications (Hotwire and Laravel, respectively)&lt;/li&gt;
&lt;li&gt;both show actual code to give people a sense of how "beautiful" it is&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall both of the Rails and Laravel homepages "feel" modern, fresh, and exciting. Django should take some inspiration and make the framework feel fresh and inviting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;Django's documentation is best in class in my opinion -- it is vast and covers everything I have ever needed to find. I think an aesthetic refresh of the docs site would be useful, but I don't have any specific tweaks, although Paolo mentioned a few things in his article.&lt;/p&gt;

&lt;p&gt;However, when I search on Google or Kagi djangoproject.com doesn't always show up first in the results. I wonder if there are some SEO tweaks so that search results consistently point to djangoproject.com?&lt;/p&gt;

&lt;h3&gt;
  
  
  Evangelists
&lt;/h3&gt;

&lt;p&gt;Django needs a constant drumbeat of people talking about it, saying how great it is, and advocating for it. Django needs companies that utilize it to shout about how it's helping their business. In short, Django needs evangelists. Lots of tech decisions are made word of mouth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modernize
&lt;/h2&gt;

&lt;p&gt;Generally, look at the current most-used packages for Django and incorporate them into core. Again, I'm totally ignoring why this is a bad idea and how it creates extra maintenance burden, reducing how often fixes can be released, etc. But, in general if you say that a framework is "batteries included", but then 80% of new installs add the same 5 packages, then... maybe they should just be included by default? Solve the 80/20!&lt;/p&gt;

&lt;p&gt;Another approach would be to have "blessed" framework configurations ala &lt;a href="https://laravel.com/docs/10.x/starter-kits"&gt;Laravel Starter Kits&lt;/a&gt; to reduce the amount of code that would go into core.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce the number of additional packages needed for different Django use-cases (i.e. what is needed for a web app, what is needed for a backend API, etc)?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Serve static assets
&lt;/h3&gt;

&lt;p&gt;Just include &lt;code&gt;whitenoise&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asset management
&lt;/h3&gt;

&lt;p&gt;Almost every site will need to use and bundle JavaScript and CSS, so it should be included. I use &lt;code&gt;django-compressor&lt;/code&gt;, but whatever... just pick one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create APIs
&lt;/h3&gt;

&lt;p&gt;A huge use case for Django currently is as an API backend, so let's make it easier. Bundling DRF maybe is overkill (maybe not?), but the closer to a frictionless API the better. DRF is probably closer to current Django, but I personally like what &lt;code&gt;django-ninja&lt;/code&gt; is doing here, but it's probably because of my affinity for FastAPI/Pydantic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background tasks
&lt;/h3&gt;

&lt;p&gt;I tend to use &lt;code&gt;django-q2&lt;/code&gt;, but again I don't really care. I just want something supported and straight-forward to use.&lt;/p&gt;

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

&lt;p&gt;I am sure there are features that Django should add, but as it is, it solves huge use-cases for the majority of web applications. But, people don’t know all of its features or they have a feeling it’s “outdated”. With better marketing and a few core additions Django would be used (and loved) by even more developers.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Python Package Manager Comparison 📦</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Mon, 13 Nov 2023 13:52:59 +0000</pubDate>
      <link>https://dev.to/adamghill/python-package-manager-comparison-1g98</link>
      <guid>https://dev.to/adamghill/python-package-manager-comparison-1g98</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article started as a random thought and then &lt;a href="https://indieweb.social/@adamghill/111395305035092074" rel="noopener noreferrer"&gt;this post&lt;/a&gt; on Mastodon.&lt;/p&gt;

&lt;p&gt;If you haven't read &lt;a href="https://cjolowicz.github.io/posts/hypermodern-python-01-setup/" rel="noopener noreferrer"&gt;Hypermodern Python&lt;/a&gt; from 2020, it is a good companion to this article and &lt;a href="https://www.oreilly.com/library/view/hypermodern-python-tooling/9781098139575/" rel="noopener noreferrer"&gt;apparently will be a released as a book in 2024&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;It feels like Python packaging has changed pretty substantially over the past few years. When I started creating Python packages the preferred approach was with &lt;code&gt;setup.py&lt;/code&gt; and (what felt like) a myriad of other random files that were needed like &lt;code&gt;MANIFEST.in&lt;/code&gt;, &lt;code&gt;setup.cfg&lt;/code&gt;, etc. I basically found a guide once and then copied and pasted files around once I seemed to get something working.&lt;/p&gt;

&lt;p&gt;I was an early adopter of &lt;a href="https://pipenv.pypa.io/" rel="noopener noreferrer"&gt;&lt;code&gt;pipenv&lt;/code&gt;&lt;/a&gt; with the primary appeal being lock files which promised consistent deployments. My early excitement got me burned multiple times and I looked around some more.&lt;/p&gt;

&lt;p&gt;I remember &lt;a href="https://python-poetry.org" rel="noopener noreferrer"&gt;&lt;code&gt;Poetry&lt;/code&gt;&lt;/a&gt; being the cool, new upstart at the time. It completely eschewed &lt;code&gt;setup.py&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, and other disparate files -- it had one &lt;code&gt;pyproject.toml&lt;/code&gt; file to configure a project, developer-friendly tooling to add dependencies, a lock file for consistent deployments, and an algorithm to make sure that a tree of dependencies was all compatible with each other (which was not possible with that version of &lt;code&gt;pip&lt;/code&gt; at that time).&lt;/p&gt;

&lt;p&gt;I was in love. There were  definite rough patches and there were a few bugs that caught me here or there, but overall &lt;code&gt;Poetry&lt;/code&gt; has been my go-to for a while.&lt;/p&gt;

&lt;p&gt;However, in the past few years, &lt;code&gt;Python&lt;/code&gt; packaging seems to be going through a new era of innovation. &lt;code&gt;pip&lt;/code&gt; &lt;a href="https://pip.pypa.io/en/latest/user_guide/#changes-to-the-pip-dependency-resolver-in-20-3-2020" rel="noopener noreferrer"&gt;released a dependency resolver in 2020&lt;/a&gt; that now complains when dependencies conflict. Also in 2020, &lt;a href="https://peps.python.org/pep-0621/" rel="noopener noreferrer"&gt;PEP 621&lt;/a&gt; and &lt;a href="https://peps.python.org/pep-0631/" rel="noopener noreferrer"&gt;PEP 631&lt;/a&gt; standardized &lt;code&gt;pyproject.toml&lt;/code&gt; as the new normal for Python packages instead of &lt;code&gt;setup.py&lt;/code&gt;. &lt;a href="https://peps.python.org/pep-0517/" rel="noopener noreferrer"&gt;PEP 517&lt;/a&gt; and &lt;a href="https://peps.python.org/pep-0660/" rel="noopener noreferrer"&gt;PEP 660&lt;/a&gt; created standards for Python build systems.&lt;/p&gt;

&lt;p&gt;Based on those PEPs, &lt;code&gt;Poetry&lt;/code&gt; isn't the only forward-thinking package manager anymore. &lt;code&gt;pipenv&lt;/code&gt; is still around (and I presume more stable at this point!), but &lt;a href="https://hatch.pypa.io/" rel="noopener noreferrer"&gt;&lt;code&gt;Hatch&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://pdm-project.org/" rel="noopener noreferrer"&gt;&lt;code&gt;PDM&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://rye-up.com" rel="noopener noreferrer"&gt;&lt;code&gt;Rye&lt;/code&gt;&lt;/a&gt; all promise a pleasant developer experience when creating Python packages.&lt;/p&gt;

&lt;h1&gt;
  
  
  Benefits
&lt;/h1&gt;

&lt;p&gt;Most of these package managers are of the "all in one" mentality and provide similar benefits: adding dependencies from the command-line, lock files, building, and publishing packages. If you want to do this manually &lt;a href="https://packaging.python.org/en/latest/tutorials/packaging-projects/" rel="noopener noreferrer"&gt;Packaging Python Projects&lt;/a&gt; lays out the steps necessary. Reducing the number of tools required to create new Python packages means there is only one place to look for documentation and keep up to date.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://python-poetry.org" rel="noopener noreferrer"&gt;Poetry&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;My first modern Python package manager. I would say the documentation is still the best available, although it might be too "designed" for some. It does &lt;em&gt;a lot&lt;/em&gt; -- maybe too much? -- but, in my opinion, it pioneered a lot of features that are now expected in other Python package managers. I get the feeling it sometimes pushes forward without waiting for official PEPs, although I think that has changed over the past few years.&lt;/p&gt;

&lt;p&gt;Occasionally lock files are not backwards-compatible which can be challenging when working in a team -- everyone needs to update their &lt;code&gt;Poetry&lt;/code&gt; install at the same time.&lt;/p&gt;

&lt;p&gt;Another annoying feature lacking in &lt;code&gt;Poetry&lt;/code&gt; that is available in every other option is the ability to define "scripts". For example, &lt;code&gt;poetry run dev&lt;/code&gt; as an alias for &lt;code&gt;poetry run manage.py runserver 0:8000&lt;/code&gt;. That is not available in standard &lt;code&gt;Poetry&lt;/code&gt;, although I use &lt;a href="https://github.com/nat-n/poethepoet" rel="noopener noreferrer"&gt;&lt;code&gt;poethepoet&lt;/code&gt;&lt;/a&gt; (a &lt;code&gt;Poetry&lt;/code&gt; plugin) to provide that functionality.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://hatch.pypa.io/" rel="noopener noreferrer"&gt;Hatch&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Hatch&lt;/code&gt; never deviates from the Python PEP standards and brings a few innovative features to the table, including being able to group dependencies and scripts into custom &lt;a href="https://hatch.pypa.io/latest/environment/" rel="noopener noreferrer"&gt;environments&lt;/a&gt;. The &lt;a href="https://hatch.pypa.io/latest/intro/#new-project" rel="noopener noreferrer"&gt;&lt;code&gt;hatch new [project]&lt;/code&gt;&lt;/a&gt; command is surprisingly opinionated and has settings for &lt;code&gt;ruff&lt;/code&gt; (my new favorite Python tool), &lt;code&gt;pytest&lt;/code&gt;, and &lt;code&gt;coverage&lt;/code&gt;. I'm here for all of the tool choices, but I would be lying if I did not say it was a little surprising.&lt;/p&gt;

&lt;p&gt;Under the PyPA GitHub organization which probably gives it more of a sheen of officialdom than other tools.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://pdm-project.org/" rel="noopener noreferrer"&gt;PDM&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;PDM&lt;/code&gt; for some reason feels like the sleeper option compared to the rest. I have not used it too much other than to set up a new project and play around with it a little bit. &lt;a href="https://fosstodon.org/@LucidDan/111401652323465954" rel="noopener noreferrer"&gt;Dan Sloan chimed in on Mastodon&lt;/a&gt; pointed out that &lt;code&gt;PDM&lt;/code&gt; follows all of the current PEP standards, has good support for cross-platform lock files, and also includes a useful &lt;code&gt;pdm export&lt;/code&gt; command to output a standard &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://rye-up.com" rel="noopener noreferrer"&gt;Rye&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;rye&lt;/code&gt; seems to have had a quick and meteoric rise. It was developed by the creator of Flask, &lt;a href="https://github.com/mitsuhiko" rel="noopener noreferrer"&gt;Armin Ronacher&lt;/a&gt;, based on his opinionated approach to building &lt;code&gt;Python&lt;/code&gt; packages. It reminds me of &lt;code&gt;black&lt;/code&gt; a little bit in which a respected developer made an opinionated tool and it caught fire in the community. &lt;code&gt;Rye&lt;/code&gt; also provides an &lt;a href="https://rye-up.com/philosophy/" rel="noopener noreferrer"&gt;overview of its philosophy here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Rye&lt;/code&gt; has multiple cautions in the docs that it is experimental and not production-ready, although I did not see any glaring issues with my simple testing. &lt;code&gt;Rye&lt;/code&gt; is a little different than the other options because it mostly consolidates a set of other tools (&lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;pip-tools&lt;/code&gt;, etc.) into a particular workflow. Another distinguishing factor is that it handles multiple installing &lt;code&gt;Python&lt;/code&gt; versions directly -- &lt;code&gt;Poetry&lt;/code&gt; recommends using &lt;code&gt;pyenv&lt;/code&gt; to deal with the same use case.&lt;/p&gt;

&lt;h1&gt;
  
  
  GitHub Stats
&lt;/h1&gt;

&lt;p&gt;I do not believe GitHub stats are especially useful, but the number of stars do give some indication of the community engagement with a particular tool (current as of 2023-11-12).&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%2Falldjango.com%2Fstatic%2Fimg%2Fpython-package-manager-star-history-20231112.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%2Falldjango.com%2Fstatic%2Fimg%2Fpython-package-manager-star-history-20231112.png" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusions and takeaways
&lt;/h1&gt;

&lt;p&gt;Personally I have multiple libraries using &lt;code&gt;setup.py&lt;/code&gt; and &lt;code&gt;Poetry&lt;/code&gt;, one using &lt;code&gt;Hatch&lt;/code&gt;, and now one using &lt;code&gt;Rye&lt;/code&gt;. The next one might use &lt;code&gt;PDM&lt;/code&gt;. 😎 I like trying new tools to see how they work and to improve my workflow.&lt;/p&gt;

&lt;p&gt;One could look at all of the options for Python packaging and be frustrated by the &lt;a href="https://en.wikipedia.org/wiki/The_Paradox_of_Choice" rel="noopener noreferrer"&gt;paradox of choice&lt;/a&gt;. I understand that viewpoint, but I also appreciate that all of these tools are making different trade-offs and pushing the state of the art forward in their own ways. Maybe at some point in the future there will be one "approved" Python package manager ala &lt;code&gt;NPM&lt;/code&gt; or &lt;code&gt;Cargo&lt;/code&gt;, but I am doubtful. There are pros and cons with multiple options, but I tend to think it's useful overall.&lt;/p&gt;

&lt;p&gt;I personally have not used each package manager extensively. But, I have started a new project from scratch with each, did some simple tasks, and read through all of the documentation. Each project is relatively mature, so you can just choose any of the options and you would be fine. Standardizing on &lt;code&gt;pyproject.toml&lt;/code&gt; means that the conversion from one package manager to another is usually just updating some &lt;code&gt;TOML&lt;/code&gt;. So, choose the package manager that speaks to you (or try them all!) and improve your developer experience henceforth. 🛠️&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@purzlbaum?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Claudio Schwarz&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/brown-cardboard-boxes-on-black-plastic-crate-q8kR_ie6WnI?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>packaging</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Best tools for Mastodon in 2023</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Sun, 19 Mar 2023 16:34:31 +0000</pubDate>
      <link>https://dev.to/adamghill/best-tools-for-mastodon-in-2023-3084</link>
      <guid>https://dev.to/adamghill/best-tools-for-mastodon-in-2023-3084</guid>
      <description>&lt;p&gt;Here are the best tools for Mastodon I have found and personally used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding Servers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://fedi.garden/"&gt;fedi.garden&lt;/a&gt;: Discovering nice servers on Mastodon and the fediverse. Made by &lt;a href="https://social.growyourown.services/@FediGarden"&gt;@FediGarden&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://instances.social/"&gt;instances.social&lt;/a&gt;: A guided wizard that helps pick the right Mastodon instance for you. Made by &lt;a href="https://mastodon.xyz/@TheKinrar"&gt;@TheKinrar&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Following Accounts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://followgraph.vercel.app"&gt;Followgraph&lt;/a&gt;: Find awesome people on Mastodon by looking up who you follow on and surfacing who &lt;em&gt;they&lt;/em&gt; follow. It expands your connection graph based on who you are already connected to. Made by &lt;a href="https://mastodon.online/@gabipurcaru"&gt;@gabipurcaru&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Timeline Summaries
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://fediview.com"&gt;fediview&lt;/a&gt;: Aggregate the most popular posts from your timelines to get a personalized view of posts. Made by &lt;a href="https://indieweb.social/@adamghill/"&gt;@adamghill&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://news.feedseer.com/"&gt;Feedseer&lt;/a&gt;: See the most popular links that have appeared in your Mastodon feed over the past day.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Web Clients
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;Mastodon&lt;/code&gt; has a robust API anyone can create a different interface for browsing your network.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://elk.zone"&gt;Elk&lt;/a&gt;: A web-only interface for Mastodon that feels very similar to Twitter.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://n1k0.github.io/tooty/"&gt;tooty&lt;/a&gt;: An experimental multi-account Mastodon web client that is all client-side (no server needed!).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://phanpy.social"&gt;Phanpy&lt;/a&gt;: A minimalist, opinionated Mastodon web client. Made by &lt;a href="https://mastodon.social/@cheeaun"&gt;@cheeaun&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.cuckoo.social/"&gt;Cuckoo&lt;/a&gt;: A GooglePlus-Like third-party web client for mastodon.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://feditrends.com/"&gt;feditrends&lt;/a&gt;: Aggregates what is trending across the fediverse.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pinafore.social"&gt;Pinafore&lt;/a&gt;: An alternative web client for Mastodon, focused on speed and simplicity. Made by &lt;a href="https://toot.cafe/@nolan"&gt;@nolan&lt;/a&gt;, but &lt;a href="https://nolanlawson.com/2023/01/09/retiring-pinafore/"&gt;unmaintained&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Desktop Clients
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://whalebird.social"&gt;Whalebird&lt;/a&gt;: Slack-like UI for browsing Mastodon.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nicolasconstant.github.io/sengi/"&gt;Sengi&lt;/a&gt;: A multi-account Mastodon desktop client.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/elk-zone/elk-native/releases"&gt;Elk Native&lt;/a&gt;: Still alpha, but a super slick desktop client from the same folks that build the web Elk client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Other Networks
&lt;/h2&gt;

&lt;p&gt;These social networks bring something unique to your social network by focusing on a particular niche. They all integrate with &lt;code&gt;Mastodon&lt;/code&gt; because they all use the same &lt;code&gt;ActivityPub&lt;/code&gt; protocol.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://joinbookwyrm.com"&gt;BookWyrm&lt;/a&gt;: A social network for tracking reading, talking about books, writing reviews, and discovering what to read next. Made by &lt;a href="https://friend.camp/@tripofmice"&gt;@tripofmice&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pixelfed.org"&gt;Pixeled&lt;/a&gt;: A network to explore and share beautiful photos and videos.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://joinpeertube.org"&gt;PeerTube&lt;/a&gt;: PeerTube is a tool for sharing online videos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Tools
&lt;/h2&gt;

&lt;p&gt;For a longer list of (more technical) tools for &lt;code&gt;Mastodon&lt;/code&gt; I have found &lt;a href="https://github.com/hueyy/awesome-mastodon"&gt;this awesome-mastodon fork by hueyy&lt;/a&gt; to be the most up-to-date.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@battenhall?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Battenhall&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/Sv7fZCc3WF8?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mastodon</category>
      <category>fediverse</category>
    </item>
    <item>
      <title>Marketing for Developers</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Sun, 26 Feb 2023 17:54:41 +0000</pubDate>
      <link>https://dev.to/adamghill/marketing-for-developers-435</link>
      <guid>https://dev.to/adamghill/marketing-for-developers-435</guid>
      <description>&lt;p&gt;I can sometimes view the world in idealistic ways. I &lt;em&gt;want&lt;/em&gt; to believe in meritocracy and that marketing is unnecessary, but even for developer tools, marketing is necessary for people to find and use your code. Writing the code and clicking the &lt;code&gt;Public&lt;/code&gt; radio button when creating the repo in GitHub is just the first step.&lt;/p&gt;

&lt;p&gt;Here is a list of other steps to make sure that others discover your code, use it, and contribute back. Not every suggestion is necessary and they are roughly sorted from easiest to hardest to implement.&lt;/p&gt;

&lt;p&gt;I mostly know the Django ecosystem, so that's what I focused on, but I'm sure other programming languages have analogous approaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the repo public
&lt;/h2&gt;

&lt;p&gt;You made a thing! That's awesome! Make sure it's &lt;code&gt;public&lt;/code&gt;, otherwise your code will just sit around being sad and lonely.&lt;/p&gt;

&lt;p&gt;Give your repo a succinct, but descriptive tagline so people know what it is. Then add tags to the repo so your repo can be found easier when searching in GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a &lt;code&gt;README.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;First impressions matter for code just like everything else. Only the most die-hard developer will delve through your code without some basic information. The least you can do is create a &lt;code&gt;README.md&lt;/code&gt; in the root directory and include things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a longer description of what your library does&lt;/li&gt;
&lt;li&gt;a list of features&lt;/li&gt;
&lt;li&gt;installation instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you really want a stellar &lt;code&gt;README.md&lt;/code&gt; take a look at some of the examples in &lt;a href="https://github.com/matiassingers/awesome-readme" rel="noopener noreferrer"&gt;awesome-readme&lt;/a&gt; for inspiration!&lt;/p&gt;

&lt;h2&gt;
  
  
  Submit your library
&lt;/h2&gt;

&lt;p&gt;There are a few websites where you can easily submit your library for others to find.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://djangopackages.org/" rel="noopener noreferrer"&gt;Django Packages&lt;/a&gt;: the O.G. site to find libraries for Django.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://python.libhunt.com/" rel="noopener noreferrer"&gt;libhunt&lt;/a&gt;: a list of Python open-source projects; submitting here will sometimes get you included in the &lt;a href="https://python.libhunt.com/newsletter" rel="noopener noreferrer"&gt;Awesome Python newsletter&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://awesomedjango.org/" rel="noopener noreferrer"&gt;awesome-django&lt;/a&gt;: a curated list of awesome Django projects; submit your own by making a &lt;a href="https://github.com/wsvincent/awesome-django" rel="noopener noreferrer"&gt;PR&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub Discussions
&lt;/h2&gt;

&lt;p&gt;Creating posts in GitHub Discussions are an easy to way to engage with the community. Personally, I have found it's hard to generate a lot engagement in here, but maybe with bigger projects or a more deliberate strategy it could be a valuable tool.&lt;/p&gt;

&lt;p&gt;One thing I &lt;em&gt;have&lt;/em&gt; experimented with in some repositories is disabling the creation of &lt;code&gt;GitHub Issues&lt;/code&gt; and using &lt;code&gt;Discussions&lt;/code&gt; for that instead. One thing I like about this approach is it allows users to engage in a way that feels less aggressive. A user is not creating a problem for you to solve, they are creating a topic to talk through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write blog posts
&lt;/h2&gt;

&lt;p&gt;Writing a blog posts is definitely marketing! Write about why you created your library, the challenges you overcome, lessons you learned, or tutorials for how to use the library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/"&gt;dev.to&lt;/a&gt; and &lt;a href="https://medium.com/" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; are good places to post technical articles which have some built-in search and promotion capabilities so others can find your writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Social media
&lt;/h2&gt;

&lt;p&gt;Twitter and Mastodon have technical audiences, but you will get more traction once you have more followers. Be generally helpful to the community and learn to use these tools that isn't against the grain. Also, use hashtags in Mastodon when posting to increase the odds that others will find it.&lt;/p&gt;

&lt;p&gt;Some sub-reddits also have technical audiences, but reddit seems to be less tolerant of self-promotion. Again, the more active and helpful in these communities you are, the more tolerant they will be. I have also mentioned my tool at the end of an answer for relevant questions -- but always provide help first.&lt;/p&gt;

&lt;p&gt;The official &lt;a href="https://forum.djangoproject.com/" rel="noopener noreferrer"&gt;Django Forums&lt;/a&gt; has a specific &lt;code&gt;Show &amp;amp; Tell&lt;/code&gt; section. I have used it before, but it doesn't seem to be as active as the &lt;a href="https://www.reddit.com/r/django/" rel="noopener noreferrer"&gt;Django sub-reddit&lt;/a&gt; in my opinion.&lt;/p&gt;

&lt;p&gt;GitHub is also technically a "social platform" so other developers will follow you there. It's much more passive, though so you cannot directly message them. They will see notifications when you do certain actions. You &lt;em&gt;can&lt;/em&gt; send updates to GitHub Sponsors via email if that's something you end up doing.&lt;/p&gt;

&lt;p&gt;I've never set up an email newsletter, but that would be another way to "own" the relationship with users without relying on a third-party like Twitter.&lt;/p&gt;

&lt;p&gt;In general, don't be overbearing and do not be spammy and it will usually be ok.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a documentation site
&lt;/h2&gt;

&lt;p&gt;After you have created  a rad &lt;code&gt;README.md&lt;/code&gt; you might notice it gets pretty long with numerous sections. This will be especially true for any library with lots of functionality. Breaking up the &lt;code&gt;README.md&lt;/code&gt; into multiple pages can help with organizing the content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sphinx-doc.org/" rel="noopener noreferrer"&gt;&lt;code&gt;Sphinx&lt;/code&gt;&lt;/a&gt; is the go-to tool for documentation. It took me a while to understand how to use &lt;code&gt;Sphinx&lt;/code&gt;, but I now have a decent workflow with &lt;code&gt;MyST&lt;/code&gt; which allows me to write all the docs in &lt;code&gt;markdown&lt;/code&gt;. My &lt;a href="https://github.com/adamghill/sphinx-markdown-docs" rel="noopener noreferrer"&gt;sphinx-markdown-docs repo&lt;/a&gt; shows an example of what I do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://readthedocs.org/" rel="noopener noreferrer"&gt;ReadTheDocs&lt;/a&gt; is a free way to host your open-source documentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://diataxis.fr/" rel="noopener noreferrer"&gt;diataxis&lt;/a&gt; is a systematic framework for technical documentation authoring which I keep meaning to read through more carefully and implement. It might be useful as you build out your own documentation to keep it in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create screencasts
&lt;/h2&gt;

&lt;p&gt;Some people learn better visually and some libraries greatly benefit from being shown. Animated gifs or short mov files can be especially useful for simple interactions. I use &lt;a href="https://getkap.co/" rel="noopener noreferrer"&gt;Kap&lt;/a&gt; (free) to create these simpler screenshares.&lt;/p&gt;

&lt;p&gt;For longer screencasts I have used &lt;a href="https://www.araelium.com/screenflick-mac-screen-recorder" rel="noopener noreferrer"&gt;Screenflick&lt;/a&gt; (currently $35 one-time fee). It allows you to only capture a portion of the screen or a whole application, record audio, includes some simple editing capabilities, etc. There are some free options (e.g. OBS), but I found them difficult to setup and deal with. &lt;code&gt;Screenflick&lt;/code&gt; was easy to use and totally worth the one-time fee.&lt;/p&gt;

&lt;p&gt;Personally, creating a compelling screencast takes me a long time: deciding what to showcase, creating the sample code, writing a (loose) script. With practice I'm sure this goes faster, or if you are used to podcasting or streaming already.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go on podcasts
&lt;/h2&gt;

&lt;p&gt;Once you've hit influencer status 🫣 you might get invited to go on a podcast to talk about your library. Podcasts are great to "get the word out" and "brand awareness", however most people are listening to podcasts in their car, doing the dishes, etc. Make it compelling and provide easy calls to action for people to re-find you or the library once they get back to a computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give a conference talk
&lt;/h2&gt;

&lt;p&gt;Most conferences will not accept a long talk strictly about your library, however, making a talk about a general problem or a specific technical solution that is applicable is always welcome. Mentioning your library as part of the conference talk will be ok. Some talks can just be about being seen as a go-to resource in the community, as well.&lt;/p&gt;

&lt;p&gt;Creating a conference talk is &lt;em&gt;hard&lt;/em&gt;. My one and only real conference talk took 40-60 hours to create and record. Not for the faint of heart, but it is a good experience to do at least once in my opinion.&lt;/p&gt;

&lt;p&gt;Shorter "lightning" or "attendance" talks are another approach. They are usually in the 5-15 minute range so are much more focused, just stressful, and talking about a specific tool seems ok in this context.&lt;/p&gt;

&lt;h2&gt;
  
  
  In summary
&lt;/h2&gt;

&lt;p&gt;Creating a repository that others know about and will use is way more involved than just writing code and it requires &lt;em&gt;some&lt;/em&gt; marketing from the developer. Hopefully this provided some ideas about how to promote your awesome library and help people find what you make!&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/pt-br/@clemhlrdt?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Clément Hélardot&lt;/a&gt; on &lt;a href="https://unsplash.com/wallpapers/desktop/computer?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Musings on Twitter and replacements for serendipity</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Sun, 06 Nov 2022 12:19:10 +0000</pubDate>
      <link>https://dev.to/adamghill/musings-on-twitter-and-replacements-for-serendipity-4f43</link>
      <guid>https://dev.to/adamghill/musings-on-twitter-and-replacements-for-serendipity-4f43</guid>
      <description>&lt;h2&gt;
  
  
  Navel gazing
&lt;/h2&gt;

&lt;p&gt;I joined Twitter in February 2009 and I'm not exactly sure why. I do remember the &lt;a href="https://www.theatlantic.com/technology/archive/2015/01/the-story-behind-twitters-fail-whale/384313/"&gt;fail whales&lt;/a&gt; and the early tweeting of what people ate for lunch, though. Even though I have accounts on other social media platforms, I rarely engage with them -- over the years the tech community seemed to coalesce around Twitter and so did I. It became a self-reinforcing cycle, especially because my job (and my hobby) is so centered around words on a screen.&lt;/p&gt;

&lt;p&gt;For a long time I only read tweets, but rarely created my own. I could "listen" to personalities that I have never met in person "talk out loud" about topics I was interested in. Brilliant thinkers like &lt;a href="https://www.elidedbranches.com/"&gt;Camille Fournier&lt;/a&gt;, &lt;a href="https://charity.wtf/"&gt;Charity Majors&lt;/a&gt; or &lt;a href="https://adamj.eu/"&gt;Adam Johnson&lt;/a&gt;  didn't need to create a conference talk or a whole article. At its best Twitter allowed me to read what smart people were thinking about and surface other interesting people.&lt;/p&gt;

&lt;p&gt;This created a bubble (in the best sense). I got information from people I trusted (and they surfaced information they trusted). But, there was also a sense of discovery and newness. And I had the tools to silence most tweets that invaded that pleasant experience by aggressively muting, blocking, and unfollowing those who disrupted my bubble.&lt;/p&gt;

&lt;p&gt;Over time I built up a few hundred followers of my own once I started tweeting primarily about tech topics. "Niche-ing down" to what I was most interested in, Python and Django, allowed others who were also interested in those topics to find me and follow me. They knew what to expect and I rarely strayed from that path. Writing an entire article can feel self-indulgent (present article very much included), whereas a tweet could be posted on a whim. I had nice interactions with people who "knew" me and with so few followers there was little incentive for bots or trolls to join in. Twitter was a low friction way for people to reach out to me with questions or comments about what I was putting out into the "public square".&lt;/p&gt;

&lt;h2&gt;
  
  
  Musings on the business of social media
&lt;/h2&gt;

&lt;p&gt;I liked Twitter so much I was one of the suckers who paid $3/month for Twitter Blue. I didn't use any of the features (and the ad-free articles never seemed to work -- R.I.P. &lt;a href="https://en.wikipedia.org/wiki/Scroll_(web_service)"&gt;Scroll&lt;/a&gt;), but I wanted to support the service and do what little I could to keep it around. Honestly, I probably would have paid more.&lt;/p&gt;

&lt;p&gt;I realize I'm idealistic, but I viewed Twitter as a utility -- it was enabling information to be spread in a new way. Trying to balance the common good with the requirements of a VC-backed, stock market-fueled company seems incredibly challenging. Wikipedia is extraordinarily successful in this endeavor and its non-profit status seems to be a key factor. The only business model for social media platforms is to create an ad platform to monetize eyeballs or to sell user's personal data.&lt;/p&gt;

&lt;p&gt;The common good and the requirements of capitalism are in direct opposition. Companies chasing engagement metrics and ad revenue creates incentives where whoever is the most incendiary wins. That isn't a platform that I personally want to engage with or support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now what?
&lt;/h2&gt;

&lt;p&gt;Having a public square where anyone can type into the void and random people can read it created serendipity and chance encounters which would not have happened without Twitter. I don't think I can actually replace it.&lt;/p&gt;

&lt;p&gt;However, for the discovery of new information I'm going to lean even harder on a few things that I already use now: email newsletters and RSS. These decidedly old-school technologies are more focused on longer-form content and that's... probably good for my brain anyway? Preventing the quick-hit endorphin rush of endless scrolling has to be a net societal positive.&lt;/p&gt;

&lt;p&gt;I already subscribe to a few newsletters, but I'm going to create a new email address (thank you email aliases with Fastmail) which will be dedicated to them. That way I will have a dedicated place they will "live" and they aren't interspersed with personal emails.&lt;/p&gt;

&lt;p&gt;For RSS, I'm doubling down on &lt;a href="https://netnewswire.com/"&gt;NetNewsWire&lt;/a&gt; and trying to find more diverse feeds to follow. iCloud integration across my laptops and iPhone keeps what I've read synced across all devices.&lt;/p&gt;

&lt;p&gt;Reddit feels like a completely different model. The niches are there with sub-reddits, but it's less personality-focused which has its pros and cons. It feels stuck in the forums of a by-gone age, but I'll probably be spending more time there than previously.&lt;/p&gt;

&lt;p&gt;I have also set up a &lt;a href="https://indieweb.social/web/@adamghill"&gt;Mastodon&lt;/a&gt; account, but I've been... underwhelmed so far. It isn't fair to compare a brand new social media account to 10+ years of accumulated interactions with another platform. I know I need to follow more people to make it worthwhile, but in the short term having less social media in my life doesn't seem all that bad.&lt;/p&gt;

&lt;p&gt;Maybe eventually I'll swallow my disappointment with Twitter's apparent new direction. Or maybe Twitter will stumble onto a different business model. I'm not holding my breath for Twitter to change, but I'm hopeful that something else arises that provides some of the same benefits &lt;em&gt;and&lt;/em&gt; more closely aligns with my perspectives.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why I built another static site generator: A love story</title>
      <dc:creator>Adam Hill</dc:creator>
      <pubDate>Sun, 20 Mar 2022 03:51:16 +0000</pubDate>
      <link>https://dev.to/adamghill/why-i-built-another-static-site-generator-a-love-story-15mm</link>
      <guid>https://dev.to/adamghill/why-i-built-another-static-site-generator-a-love-story-15mm</guid>
      <description>&lt;p&gt;Does the world &lt;em&gt;need&lt;/em&gt; another static site generator? Honestly... no. But, does it &lt;em&gt;deserve&lt;/em&gt; another one? YES!&lt;/p&gt;

&lt;p&gt;It all started from this tweet:&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%2Fuploads%2Farticles%2F109qqvkhtejfad9uk3al.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%2Fuploads%2Farticles%2F109qqvkhtejfad9uk3al.png" alt="If I wanted to make a static website with just a few dynamic elements that didn't look half bad, what stack should I use?"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have used a few static site generators over the years including &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt;, &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt;, and &lt;a href="https://blog.getpelican.com/" rel="noopener noreferrer"&gt;Pelican&lt;/a&gt; to host my personal blog. And I have experimented with a few others including &lt;a href="https://www.getlektor.com/" rel="noopener noreferrer"&gt;Lektor&lt;/a&gt; and &lt;a href="https://www.gatsbyjs.com/" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All static site generators do basically the same thing, but they each have their own idiosyncrasies. Since I have a lot of experience with Django (and a predilection to want to improve the Django ecosystem) when I saw the tweet above I thought, "why &lt;em&gt;wouldn't&lt;/em&gt; you use Django for this?"&lt;/p&gt;

&lt;p&gt;The reasons &lt;em&gt;to&lt;/em&gt; use Django:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it's already solved a lot of tablestake features:

&lt;ul&gt;
&lt;li&gt;creating RSS feeds&lt;/li&gt;
&lt;li&gt;sitemap.xml generation&lt;/li&gt;
&lt;li&gt;auto-reloading dev server&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;a nice CLI management command system&lt;/li&gt;

&lt;li&gt;Django template language with lots of built-in template tags&lt;/li&gt;

&lt;li&gt;there is a huge ecosystem of other functionality&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The reasons &lt;em&gt;not&lt;/em&gt; to use Django:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it's "big" and "heavyweight"&lt;/li&gt;
&lt;li&gt;it's over-complicated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No programming language or framework is perfect, but the one I have the most experience with is Django. I love most of the Django, but I wanted to see if I could build an opinionated framework on top that hid all of the complexities of a normal Django site (e.g. serving static assets, complicated settings files, setting up URL routes, etc).&lt;/p&gt;

&lt;p&gt;I wanted to make Django even more lovable by creating a simplified content framework that served markdown files in an intuitive way. So, &lt;a href="https://coltrane.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;&lt;code&gt;coltrane&lt;/code&gt;&lt;/a&gt; was born.&lt;/p&gt;

&lt;p&gt;First, I used &lt;a href="https://github.com/wsvincent/django-microframework" rel="noopener noreferrer"&gt;django-microframework&lt;/a&gt; as inspiration for a simple &lt;code&gt;app.py&lt;/code&gt; that could be used instead of the potentially overwhelming files Django normally uses for a site. Then, I added in automatic reading of &lt;code&gt;.env&lt;/code&gt; files to override Django settings that shouldn't be committed. I used &lt;a href="https://github.com/trentm/python-markdown2" rel="noopener noreferrer"&gt;&lt;code&gt;markdown2&lt;/code&gt;&lt;/a&gt; to automatically render markdown files into HTML. And built a way to load data from JSON into templates to be used as variables (since a database is not available when generating a static site).&lt;/p&gt;

&lt;p&gt;Then finally, I built a management command that output the whole site as static HTML. The result is "yet another" static site generator, but a key differentiator is that it tries to be as simple as possible with sane defaults. There are only three commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;poetry run coltrane create&lt;/code&gt; to create a new static site&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;poetry run coltrane play&lt;/code&gt; to start up a development server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;poetry run coltrane record&lt;/code&gt; to output a static site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the site is created, just start adding markdown files into the &lt;code&gt;content&lt;/code&gt; directory and they will be rendered as HTML using filename as the URL slug.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;coltrane&lt;/code&gt; won't ever solve every use-case, but right now it solves two specifically for me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It generates the static site of &lt;a href="https://alldjango.com" rel="noopener noreferrer"&gt;https://alldjango.com&lt;/a&gt; (building automatically on every &lt;code&gt;git push&lt;/code&gt; by render.com).&lt;/li&gt;
&lt;li&gt;It is used to serve &lt;a href="https://devmarks.io/articles" rel="noopener noreferrer"&gt;https://devmarks.io/articles&lt;/a&gt;, as a Django app in an existing Django site&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, maybe the world doesn't &lt;em&gt;need&lt;/em&gt; another static site generator, but hopefully &lt;a href="https://coltrane.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;&lt;code&gt;coltrane&lt;/code&gt;&lt;/a&gt; is a fresh take on static site generators.&lt;/p&gt;

&lt;p&gt;I would love for you to try it out for your next project and star the &lt;a href="https://github.com/adamghill/coltrane" rel="noopener noreferrer"&gt;coltrane repo&lt;/a&gt; if it looks useful!&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>staticsite</category>
      <category>markdown</category>
    </item>
  </channel>
</rss>
