Every project is a series of decisions made with incomplete information. Here's the full stack behind this blog, the reasoning behind each choice, what each one actually taught me, and — with the benefit of hindsight — what I'd change if I were starting today.
Flask — backend framework
Why I chose it: I picked Flask over Django because I wanted to understand how web applications fit together, not have the structure decided for me. Flask gives you routing, request handling, and templating — the rest is your decision.
What I actually learned: Building with Flask from scratch forced me to understand things Django would have handled automatically — how the application context works, how SQLAlchemy integrates, how Jinja2 renders templates, what WSGI actually is. Every time something broke, I had to understand it to fix it. That's a slower way to build but a faster way to learn.
What I'd change: Nothing here. Flask was the right choice for a learning project. For a production app with a team, I'd reconsider — but for understanding web development from the ground up, Flask is hard to beat.
Aiven MySQL — database
Why I chose it: Free, managed, persistent MySQL in the cloud. No credit card required. No local database server to maintain on the hosting side.
What I actually learned: More about SSL
configuration than I expected. PyMySQL's SSL parameters are
different from the MySQL CLI's — a distinction that cost me
a few hours. Also learned about connection pooling:
pool_pre_ping and pool_recycle
are essential when your database can pause on inactivity,
or you'll get "Lost connection" errors on the first query
after a sleep period.
What I'd change: The free tier auto-pauses on inactivity. For a personal blog with low traffic, this is fine — but it means the first request after a long gap is slow. For anything with consistent traffic, I'd pay for a tier that stays running.
Cloudinary — image storage
Why I chose it: Two alternatives existed: store images in the database (slow, expensive in storage) or store them on the server filesystem (doesn't survive redeployments on Render). Cloudinary solves both — upload once, get a permanent URL, store only the URL in the database.
What I actually learned: The
cloudinary.config() function takes lowercase
parameter names (cloud_name,
api_key, api_secret), not the
uppercase environment variable names
(CLOUDINARY_API_KEY). Subtle distinction
that caused a "Must supply api_key" error I spent longer
than I should have debugging.
What I'd change: Nothing. Cloudinary's free tier is generous, the API is straightforward, and the integration with Flask is clean.
Resend — transactional email
Why I chose it: I didn't — at first. I started with Flask-Mail and Gmail SMTP. That worked locally and broke immediately on Render (SMTP blocked on free tier, OSError 101). Resend was the replacement.
What I actually learned: SMTP is a legacy protocol that many modern hosting platforms restrict or block entirely on lower-tier plans. HTTP-based email APIs (Resend, SendGrid, Brevo) are more reliable in these environments because they use HTTPS — a port that's almost never blocked.
What I'd change: Start with Resend. Skip Flask-Mail entirely for anything that will be deployed. SMTP works great locally and poorly in production — which is exactly backwards from what you want.
Render — hosting
Why I chose it: Free tier, auto-deploys from GitHub, supports Python natively, Singapore region available (lowest latency for India-based visitors).
What I actually learned: Free tier has real constraints: SMTP blocked, services spin down after 15 minutes of inactivity, cold starts take 30-60 seconds. None of these are dealbreakers for a personal project, but all of them require workarounds (Resend for email, UptimeRobot for cold starts).
What I'd change: For a production application with real users, I'd pay for the Starter tier ($7/month) to eliminate cold starts and SMTP restrictions. For a personal blog or portfolio project, the free tier with workarounds is completely workable.
The thing I'd change that isn't about a specific tool
I ran pip freeze > requirements.txt before
deploying. My global Python environment had 100+ packages
installed from various projects — PyTorch, Django,
Selenium, OpenAI, all of it ended up in the file. Render
tried to install all of them. The build was slow and
nearly failed.
The fix: use a virtual environment from day one.
python -m venv venv, activate it, install
only what the project needs. pip freeze then
gives you 10 packages instead of 100.
This is a basic Python practice that I knew about and didn't do consistently. The deployment made me do it properly. Sometimes you need to feel the consequence before the habit sticks.
Final thought
Every tool on this list has a free tier, and the whole stack costs ₹0/month to run. For a fresher building a portfolio project, that matters. But more than cost, what this stack gave me was a set of real production problems to solve — SSL configuration, connection pooling, email delivery, crawler configuration. Those problems are worth more than the stack itself.
Build something. Deploy it. See what breaks. That's the education.