Why I Run Django on Kubernetes as a One-Man SaaS
March 31, 2021 · 7 minute read
When I first published my post on my tech stack as a one-man SaaS I got wildly different reactions.
Some told me they loved it and had success with such a stack too, while others politely pointed out it’s too much complexity and I should just use a managed platform like Heroku or Render to run my business instead.
I don't think there’s a right or wrong way to go about it. Everyone looks at the world from a slightly different lens, and has their own goals in mind, so I’ll elaborate on my own.
An elephant in the room
I use Kubernetes as a one-man operation, and embrace the simplicity (or complexity, depending on how you see it) that comes with it.
I won’t pretend it’s all sunshine and roses. It has taken many years of lessons learned and production fires to get to a point where I feel I better understand when Kubernetes works amazingly well, and when it fails catastrophically, and can bring down an entire company to its knees.
There’s a reason there’s countless memes about Kubernetes failure stories. That big boat that recently got stuck in the Suez Canal? Yup, there’s a meme for it.
My setup would be overkill if I just ran this low-volume blog on it. But it has been highly valuable for my second SaaS, which from a technical point-of-view is in the business of processing a large amount of requests per second from anywhere in the world, and storing them in an efficient format for real-time querying.
My side-business is bootstrapped and self-funded, meaning it has to be very cost effective too. I don’t need Google-scale infrastructure, just a scalable-enough approach that makes it easy for me to leverage what I already know best and focus on shipping stuff.
Do I use Kubernetes for everything? No - I don’t believe in the one-size-fits-all mentality. Different projects, different needs.
Some examples: this blog is a static site hosted with Vercel, I also use single VMs on DigitalOcean/Linode for some experiments, and even AWS Lambda when it makes sense. I stick to what works most of the time, but I sometimes like spicing things up too.
There’s no holy grail
Over the past years, I’ve used a handful of platforms to run countless apps in a team setting. From obscure in-house deployment models to pretty standard CloudFormation templates to fully managed serverless providers. Not to mention the various programming languages, concurrency models, frameworks, and databases that got thrown into every project.
During this time I have learned that there’s no holy grail, and there will always be tradeoffs to be made. This helped me recognize that I must decide for myself which pain points I care about, and which ones I’m willing to let go.
So why am I telling you this? No choice of tools is a one-size-fits-all solution. If you take away one thing out of this post, let it be that.
Why I use Kubernetes
Kubernetes sits at a higher level of abstraction over your compute resources, it’s understandably complex if you look at it from a lower-level.
This reminds me of the Blub Paradox:
“As long as our hypothetical Blub programmer is looking down the power continuum, he knows he's looking down. Languages less powerful than Blub are obviously less powerful, because they're missing some feature he's used to. But when our hypothetical Blub programmer looks in the other direction, up the power continuum, he doesn't realize he's looking up. What he sees are merely weird languages. He probably considers them about equivalent in power to Blub, but with all this other hairy stuff thrown in as well. Blub is good enough for him, because he thinks in Blub.” - Paul Graham
I built a small, but complete stack for running all my projects on top of Kubernetes. It’s like my own portable mini-cloud that I can use for every new project without having to reinvent the wheel each time.
Everything is automated: end-to-end testing, incremental rollouts and rollbacks, horizontal auto-scaling, monitoring (both logging and app metrics), TLS certificates, encrypted secrets and configuration, backups to S3 and whatnot. I can launch production grade apps very quickly and re-use the same tooling across all projects, while keeping the financial burden low.
Some of you are probably wondering: why not just use Heroku instead? Why spend all that time on infra instead of running a business?
The truth is: I don’t spend much time managing infrastructure. My initial setup took about a week of part-time work, and since then it’s been maybe an hour of maintenance per month, if at all.
I also benefit from significantly lower costs while still offloading the critical pieces to my cloud provider (the Kubernetes cluster, databases, encrypted backups, and so on). Deploying new projects and testing environments doesn’t really increase my costs, and I am free to run anything I want in my clusters.
The ecosystem around Kubernetes is massive, and that’s a good thing. The community is friendly, there’s lots of documentation, and every major cloud vendor offers hosted Kubernetes clusters at this point. They’ve gotten better over time too.
No, you don’t need every single plugin and integration you see on the Internet. That’s the beauty of it, you can customize Kubernetes as much or as little as you want.
I do believe the time invested has paid off for many projects: Kubernetes makes the simple stuff more complex, but it also makes the complex stuff much simpler.
Why I use Django
Let me tell you a little secret: in the age of microservices, nanoservices, serverless vs everything else, you can still get pretty far with an old-school monolithic framework on a single VPS.
“Wait, what? Did you just spend an entire section talking about Kubernetes to now tell me that?” - Kubernetes works great for me, but it’s certainly not needed for running a SaaS. I couldn’t miss the opportunity to reiterate that.
For contrast let me throw in a link to Pieter Levels. His success with a single PHP file is proof that you can get really far with much less. I say this, because I want to emphasize that I don’t think there’s only one true way to solve things: it all comes down to trade offs, and your own personal circumstances.
Standing on the shoulders of giants
For me, one of the advantages of Django is that there’s lots of documentation online, and people who can help me out when I get stuck in some rare edge case. There’s already lots of answers on StackOverflow to practically every question I might have.
A battle-tested, monolithic framework like Django (or Rails or Laravel) has batteries included too: migrations, common middleware, database connection pooling, a caching framework, and possibly an admin panel. Having built these from scratch in the past, I can say it was a fun learning experience, but I’m grateful to be able to leverage the amazing work that the community around the frameworks have already put into them.
In terms of performance, Django is more than enough for my needs. At my day-job, we have already scaled Django to thousands of requests per second with commodity hardware on AWS. No specific optimizations, just moving CPU-bound tasks out of the request loop to background workers when possible, and heavily caching stuff with Redis and in-memory LRU caches to reduce calls to external services.
Of course this highly depends on your workload, and understanding whether you are I/O or CPU bound helps too. Sure, I could handle 10x more throughput in another language for some very specific workloads, but that’s not my goal here and will likely never be.
In short, just like Kubernetes I use Django because it’s what I currently know best, has a vibrant community, a wealth of documentation, and already solves common use cases for my needs. It’s a fantastic tool that I can bend in any direction I need.
What to make of this
Should you follow my choices? Taken out of context, probably not. I can tell you the tools I use make sense for me, but your own circumstances might turn the advantages into disadvantages. You might end up with all of the downsides, with none of the upsides.
Don’t just blindly follow anyone’s advice for that matter, your own context matters.