Building a Blog Platform with Docker #2: Tailwind CSS
Quick one-liner: Upgrade your Flask app to Tailwind CSS via CDN — dark theme with teal branding, no build step required.
Why This Matters
Last time, you got a basic Flask app running with a separate CSS file. It worked — dark background, teal heading, centered layout.
But let's be honest — it's nothing like a blog yet. One centered heading. One paragraph. Nothing a reader would take seriously.
Today, we're making it look a lot more like a blog. We're adding Tailwind CSS.
Why Tailwind? You could write custom CSS. Or use Bootstrap. Or Bulma. But Tailwind is fast, modern, and easy to customise. Plus, it's what I'll use for the final blog.dtio.app, so you're learning the actual stack.
No build step. I'm not making you install Node.js, npm, and a whole build pipeline just for CSS. We're using Tailwind via CDN. It's not production-optimal, but for learning? Perfect.
By the end of this post, you'll have:
- Tailwind CSS loaded via CDN
- Google Fonts (DM Sans + Instrument Serif)
- A teal navigation bar
- A centered hero section
- An editorial post list
- A teal footer
Starting Point
You should have this from last time:
tiohub-blog/
├── app.py
├── static/
│ └── css/
│ └── style.css
└── templates/
└── index.html
Your index.html currently links to static/css/style.css via a <link> tag in the <head>.
If you don't have this, go back and build Episode 1 first. This one builds on it.
Step 1: Add Tailwind CDN and Google Fonts
Open templates/index.html. You currently have something like this:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to David Tio's Blog</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h1>Welcome to David Tio's Blog</h1>
<p>Building a blog platform with Docker.</p>
</body>
</html>
Add the Google Fonts and Tailwind CDN in the <head>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>David Tio's Blog</title>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<h1>Welcome to David Tio's Blog</h1>
<p>Building a blog platform with Docker.</p>
</body>
</html>
That's it. Tailwind is now loaded. We'll add the config file in Step 2.
CDN trade-off: It's bigger than a bundled build. But for a personal blog? Nobody's going to notice. And you save hours of build setup.
Step 2: Configure Tailwind
Tailwind's default colors and fonts are a good start, but we want our own brand colors and the fonts we loaded. This can be configured using JavaScript. Let's create a directory for it:
$ mkdir -p static/js
static/js/tailwind.config.js:
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
500: '#14B8A6', // teal accent
600: '#0F766E', // primary teal
700: '#0D5F57', // darker teal
}
},
fontFamily: {
sans: ['DM Sans', 'system-ui', 'sans-serif'],
serif: ['Instrument Serif', 'Georgia', 'serif'],
}
}
}
}
Load it in index.html right after the Tailwind CDN script:
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="{{ url_for('static', filename='js/tailwind.config.js') }}"></script>
Now you can use bg-brand-700, text-brand-500, font-serif, etc. in your classes.
Step 3: Build the Navigation Bar
Open templates/index.html. Add this right after the opening <body> tag:
<nav class="bg-brand-700 border-b border-brand-600">
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
<a href="{{ url_for('index') }}" class="font-serif text-xl text-white">
David Tio's Blog
</a>
<div class="flex items-center space-x-6">
<a href="/" class="text-teal-100 hover:text-white font-medium text-sm transition duration-200">Home</a>
<a href="#" class="text-teal-100 hover:text-white font-medium text-sm transition duration-200">Series</a>
<a href="#" class="text-teal-100 hover:text-white font-medium text-sm transition duration-200">About</a>
</div>
</div>
</nav>
This gives you:
- Darker teal navbar (
brand-700) with a subtle border line underneath - Blog name on the left in Instrument Serif, linked back to your Flask
indexroute viaurl_for - Three navigation links on the right with white hover transitions
-
max-w-6xl mx-autocenters the nav content at a comfortable width
Mobile note: This navbar isn't responsive yet. We'll fix that later. For now, it works on desktop.
Placeholder links: Series and About point to # for now. They're dead links until we build those pages in a future episode. Home links to your Flask index route, which is already live.
Step 4: Build the Hero and Post List
Now replace the entire <body> section with this:
<body class="bg-slate-950 text-gray-100 font-sans min-h-screen flex flex-col">
<!-- Navbar from Step 3 -->
<!-- Hero -->
<div class="max-w-3xl mx-auto px-6 pt-20 pb-12 text-center">
<span class="inline-block text-brand-500 text-xs font-semibold tracking-widest uppercase mb-5">Docker · Linux · Open Source</span>
<h1 class="font-serif text-5xl text-white leading-tight mb-5">Practical guides for engineers.</h1>
<p class="text-gray-400 text-lg leading-relaxed">Building with containers, Linux, and open source tools — one post at a time.</p>
</div>
<!-- Posts -->
<main class="max-w-3xl mx-auto px-6 pb-20 flex-1 w-full">
<div class="border-t border-slate-800 mb-10"></div>
<h2 class="text-xs font-semibold text-gray-500 uppercase tracking-widest mb-8">Latest Posts</h2>
<div class="flex flex-col">
<article class="border-l-2 border-slate-800 hover:border-brand-500 pl-6 py-5 transition-all duration-300 group cursor-pointer">
<p class="text-gray-600 text-xs mb-2">29 Mar 2026 · Blog Platform</p>
<h3 class="text-gray-100 font-semibold text-lg mb-2 group-hover:text-brand-500 transition-colors duration-200">
<a href="#">Building a Blog Platform #1: Flask Setup</a>
</h3>
<p class="text-gray-500 text-sm leading-relaxed">Get a basic Flask app running with separate CSS — no Docker yet, just Python and a stylesheet.</p>
</article>
<div class="border-t border-slate-800 ml-6"></div>
</div>
</main>
<!-- Footer from Step 5 -->
</body>
What's happening here:
-
bg-slate-950gives the whole page a dark slate background -
min-h-screen flex flex-colmakes the body stretch to full viewport height and stacks nav, hero, posts, and footer vertically -
max-w-3xl mx-autocenters the content at a readable width — just like a real blog - The hero has a small teal label ("Docker · Linux · Open Source") and a large serif heading
- The post card has a left border that turns teal when you hover over it — try it
- The
url_forin the navbar links back to your Flaskindexroute, so nothing is hardcoded
Step 5: Add a Footer
Before the closing </body> tag, add:
<footer class="bg-brand-700 border-t border-brand-600">
<div class="max-w-6xl mx-auto px-6 py-6 flex items-center justify-between">
<p class="text-teal-100 text-sm">© 2026 David Tio.</p>
<div class="flex space-x-6">
<a href="#" class="text-teal-100 hover:text-white text-sm transition-colors duration-200">LinkedIn</a>
<a href="#" class="text-teal-100 hover:text-white text-sm transition-colors duration-200">Twitter</a>
<a href="#" class="text-teal-100 hover:text-white text-sm transition-colors duration-200">GitHub</a>
</div>
</div>
</footer>
Matches the nav — teal background, copyright left, social links right.
Step 6: Clean Up Your CSS
Now that Tailwind is doing all the work, your old style.css is redundant. Remove the <link> tag from the <head> of index.html:
<!-- Delete this line -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
Then delete the file itself:
$ rm static/css/style.css
Step 7: Test It
If you closed the terminal since Episode 1, reactivate the venv first:
$ source venv/bin/activate
Run your Flask app:
$ python app.py
Visit http://localhost:8000. You should see:
- Teal navbar with your name in Instrument Serif
- Dark background with a centered hero
- Editorial post list with teal hover effects
- Matching teal footer
It should look like an actual blog now.
What You've Built
You now have:
- Tailwind CSS via CDN (no build step)
- Google Fonts: DM Sans body, Instrument Serif for headings
- Custom brand colors (teal-500 accent, teal-700 nav/footer)
- Dark slate background (
slate-950) - Teal nav and footer branding
- Centered hero with serif heading
- Editorial post list with teal left-border hover
Coming Up
Right now, your posts are hardcoded HTML. You want to write Markdown files and have them render automatically. Next time: Markdown support. You'll write .md files with frontmatter, and Flask will parse them into HTML.
No more HTML templates for every blog post. Just write Markdown and go.
Found this helpful? Share it with your network or drop a comment below.

Top comments (0)