Files
2026-07-22 11:50:54 +07:00

596 lines
49 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-G9G5HP50VK"></script>
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','G-G9G5HP50VK');</script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My PaaS Arsenal — Building a Simple, Modern PaaS for 30+ ASP.NET Core Demos | INDOTALENT</title>
<meta name="description" content="A detailed technical journey of building a lightweight Self-Hosted PaaS infrastructure using Gitea, Portainer, Nginx Proxy Manager, PostgreSQL, MSSQL, CloudBeaver, and Dozzle to host 30+ ASP.NET Core demo applications on a single Linux VPS." />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://www.indotalent.com/my-paas-arsenal.html" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%23f97316'/><text x='16' y='22' font-size='18' font-weight='bold' text-anchor='middle' fill='white'>I</text></svg>" />
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script>tailwind.config={theme:{extend:{colors:{brand:{50:'#fff7ed',100:'#ffedd5',200:'#fed7aa',300:'#fdba74',400:'#fb923c',500:'#f97316',600:'#ea580c',700:'#c2410c',800:'#9a3412',900:'#7c2d12'}},fontFamily:{sans:['Inter','system-ui','-apple-system','sans-serif']}}}}</script>
<style>
[x-cloak]{display:none!important} html{scroll-behavior:smooth}
.nav-blur{backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
.gradient-text{background:linear-gradient(135deg,#f97316 0%,#ea580c 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.btn-primary{display:inline-flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#f97316 0%,#ea580c 100%);color:#fff;font-weight:600;border-radius:12px;padding:12px 24px;transition:all 0.2s;box-shadow:0 4px 14px -4px rgba(249,115,22,0.35)}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 20px -4px rgba(249,115,22,0.45)}
.btn-secondary{display:inline-flex;align-items:center;justify-content:center;background:#fff;color:#374151;font-weight:600;border-radius:12px;padding:12px 24px;border:1px solid #e5e7eb;transition:all 0.2s}
.btn-secondary:hover{border-color:#f97316;color:#f97316}
.fab-back-to-top{position:fixed;bottom:28px;right:28px;z-index:50;width:48px;height:48px;background:linear-gradient(135deg,#f97316,#ea580c);color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px rgba(249,115,22,0.35);transition:all 0.3s;opacity:0;pointer-events:none;transform:translateY(20px);text-decoration:none}
.fab-back-to-top.visible{opacity:1;pointer-events:auto;transform:translateY(0)}
.fab-back-to-top:hover{transform:translateY(-3px);box-shadow:0 8px 24px rgba(249,115,22,0.5)}
nav a.nav-item.active{color:#f97316!important;border-bottom:2px solid #f97316;padding-bottom:2px}
.tool-card{transition:all 0.3s ease}
.tool-card:hover{transform:translateY(-3px);box-shadow:0 12px 30px -8px rgba(249,115,22,0.12)}
.diagram-arrow{color:#f97316}
</style>
</head>
<body class="bg-white text-gray-900 antialiased">
<!-- BREADCRUMB -->
<nav aria-label="Breadcrumb" class="pt-20 bg-white border-b border-gray-100">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2.5">
<ol class="flex items-center gap-1.5 text-xs text-gray-400">
<li><a href="index.html" class="hover:text-brand-500 transition-colors">Home</a></li>
<li><span class="text-gray-300">/</span></li>
<li><span class="text-gray-600 font-medium">My PaaS Arsenal</span></li>
</ol>
</div>
</nav>
<!-- NAVBAR -->
<nav class="fixed top-0 left-0 right-0 z-50 nav-blur bg-white/85 border-b border-gray-200/60" x-data="{ mobileOpen: false }">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16 lg:h-18">
<a href="index.html" class="flex items-center gap-2.5"><img src="img/logo-indotalent-dark.svg" alt="Indotalent" class="h-8 w-auto" /></a>
<div class="hidden lg:flex items-center gap-7">
<a href="index.html" class="nav-item text-sm font-medium text-gray-600 hover:text-brand-500 transition-colors">Home</a>
<a href="index.html#products" class="nav-item text-sm font-medium text-gray-600 hover:text-brand-500 transition-colors">Products</a>
<a href="blog.html" class="nav-item text-sm font-medium text-gray-600 hover:text-brand-500 transition-colors">Blog</a>
<a href="about-the-developer.html" class="nav-item text-sm font-medium text-gray-600 hover:text-brand-500 transition-colors">About</a>
</div>
<div class="hidden lg:flex items-center gap-3"><a href="index.html#products" class="btn-primary text-sm py-2.5 px-5">Get Started</a></div>
<button @click="mobileOpen = !mobileOpen" class="lg:hidden text-gray-500 hover:text-gray-700 p-2">
<svg x-show="!mobileOpen" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16"/></svg>
<svg x-show="mobileOpen" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
</div>
<div x-show="mobileOpen" x-cloak class="lg:hidden bg-white/98 border-b border-gray-200 px-4 pb-6"><div class="flex flex-col gap-4 pt-2"><a href="index.html" @click="mobileOpen=false" class="text-sm font-medium text-gray-600">Home</a><a href="index.html#products" @click="mobileOpen=false" class="text-sm font-medium text-gray-600">Products</a><a href="blog.html" @click="mobileOpen=false" class="text-sm font-medium text-gray-600">Blog</a><a href="about-the-developer.html" @click="mobileOpen=false" class="text-sm font-medium text-gray-600">About</a><a href="index.html#products" class="btn-primary text-sm py-2.5 px-5 text-center">Get Started</a></div></div>
</nav>
<!-- HERO -->
<section class="relative pt-12 pb-16 lg:pb-20 overflow-hidden">
<div class="absolute inset-0 opacity-[0.015]" style="background-image:radial-gradient(circle at 1px 1px,#f97316 1px,transparent 0);background-size:40px 40px"></div>
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="text-center" id="heroContent">
<div class="inline-flex items-center gap-1.5 bg-orange-50 border border-orange-200 rounded-full px-3.5 py-1 mb-4">
<span class="text-brand-700 text-xs font-medium tracking-wide">Infrastructure</span>
</div>
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-gray-900 mb-4 tracking-tight">
My PaaS <span class="gradient-text">Arsenal</span>
</h1>
<p class="text-lg sm:text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed mb-3">
Building a Simple, Modern, Yet Powerful PaaS Infrastructure for Hosting 30+ ASP.NET Core Demo Applications
</p>
<p class="text-sm text-gray-400">A technical deep-dive into self-hosted, container-based deployment</p>
</div>
</div>
</section>
<!-- INTRO / THE QUESTION -->
<section class="py-12 lg:py-16 bg-gray-50/80 border-y border-gray-100">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-white border border-gray-200 rounded-2xl p-6 sm:p-8 tool-card">
<p class="text-gray-700 leading-relaxed mb-4">
One of the questions I receive most often is surprisingly not about C#, ASP.NET Core, or Clean Architecture.
</p>
<p class="text-gray-700 leading-relaxed mb-4">Instead, it's this:</p>
<blockquote class="border-l-4 border-brand-400 bg-orange-50/50 rounded-lg pl-5 pr-4 py-3 my-5 text-center">
<p class="text-base sm:text-lg text-gray-700 font-semibold italic leading-relaxed">
&ldquo;How do you host all of your live demo applications?&rdquo;
</p>
</blockquote>
<p class="text-gray-700 leading-relaxed mb-4">
Today, I maintain dozens of web application demos&mdash;CRM systems, POS applications, ERP modules, HR software, inventory systems, and various reusable templates.
</p>
<p class="text-gray-700 leading-relaxed mb-2 font-semibold">Each application needs to be:</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 mb-4">
<div class="flex items-center gap-2 text-sm text-gray-600"><svg class="w-4 h-4 text-emerald-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg>Available 24/7</div>
<div class="flex items-center gap-2 text-sm text-gray-600"><svg class="w-4 h-4 text-emerald-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg>Easy to update</div>
<div class="flex items-center gap-2 text-sm text-gray-600"><svg class="w-4 h-4 text-emerald-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg>Isolated from other applications</div>
<div class="flex items-center gap-2 text-sm text-gray-600"><svg class="w-4 h-4 text-emerald-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg>Protected with HTTPS</div>
<div class="flex items-center gap-2 text-sm text-gray-600"><svg class="w-4 h-4 text-emerald-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg>Connected to its own database</div>
<div class="flex items-center gap-2 text-sm text-gray-600"><svg class="w-4 h-4 text-emerald-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg>Cost-effective to operate</div>
</div>
<p class="text-gray-700 leading-relaxed">
Years ago, the answer was easy. Windows Server. Microsoft IIS. Publish. Done. That changed when I migrated my entire infrastructure to Linux. Ironically, the migration wasn't driven by technology&mdash;it was driven by economics.
</p>
</div>
</div>
</section>
<!-- WHY LINUX -->
<section class="py-16 lg:py-20">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-10">
<div class="inline-flex items-center gap-1.5 bg-red-50 border border-red-200 rounded-full px-3.5 py-1 mb-4">
<span class="text-red-700 text-xs font-medium tracking-wide">The Shift</span>
</div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900">Why I Left <span class="gradient-text">Windows Server</span></h2>
</div>
<div class="bg-white border border-gray-200 rounded-2xl p-6 sm:p-8 tool-card">
<p class="text-gray-700 leading-relaxed mb-4">
Running dozens of demo applications on Windows Server works perfectly fine. The problem wasn't capability. It was cost.
</p>
<p class="text-gray-700 leading-relaxed mb-4">
A VPS running Windows Server Datacenter with enough CPU and RAM to host many ASP.NET Core applications is <strong class="text-gray-900">significantly more expensive</strong> than an equivalent Linux VPS. Once your infrastructure begins growing beyond a few applications, licensing costs become difficult to ignore.
</p>
<p class="text-gray-700 leading-relaxed mb-4 font-semibold">I wanted something that was:</p>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2 mb-6">
<span class="inline-flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 rounded-lg px-3 py-1.5 text-sm font-medium text-emerald-700">cheaper</span>
<span class="inline-flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 rounded-lg px-3 py-1.5 text-sm font-medium text-emerald-700">easier to automate</span>
<span class="inline-flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 rounded-lg px-3 py-1.5 text-sm font-medium text-emerald-700">container-friendly</span>
<span class="inline-flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 rounded-lg px-3 py-1.5 text-sm font-medium text-emerald-700">cloud-native</span>
<span class="inline-flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 rounded-lg px-3 py-1.5 text-sm font-medium text-emerald-700">capable of CI/CD</span>
<span class="inline-flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 rounded-lg px-3 py-1.5 text-sm font-medium text-emerald-700">maintainable by one person</span>
</div>
<p class="text-gray-700 leading-relaxed mb-4">
Linux turned out to be the perfect fit. Today, the entire platform runs comfortably on a single VPS with:
</p>
<div class="flex flex-wrap gap-4 mb-4 justify-center">
<div class="bg-orange-50 rounded-xl px-6 py-3 border border-orange-200 text-center">
<p class="text-2xl font-bold text-brand-600">8</p>
<p class="text-xs text-gray-500">vCPU</p>
</div>
<div class="bg-orange-50 rounded-xl px-6 py-3 border border-orange-200 text-center">
<p class="text-2xl font-bold text-brand-600">24 GB</p>
<p class="text-xs text-gray-500">RAM</p>
</div>
</div>
<p class="text-gray-700 leading-relaxed">
Despite hosting more than thirty applications, the server remains responsive thanks to containerization and careful resource management. The operating system is almost invisible. Docker became the real operating system.
</p>
</div>
</div>
</section>
<!-- PHILOSOPHY -->
<section class="py-16 lg:py-20 bg-gray-50/80">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-10">
<div class="inline-flex items-center gap-1.5 bg-blue-50 border border-blue-200 rounded-full px-3.5 py-1 mb-4">
<span class="text-blue-700 text-xs font-medium tracking-wide">Philosophy</span>
</div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900">My <span class="gradient-text">Philosophy</span></h2>
</div>
<div class="bg-white border border-gray-200 rounded-2xl p-6 sm:p-8 tool-card text-center">
<p class="text-gray-600 leading-relaxed mb-4">
People often imagine Platform as a Service (PaaS) as something incredibly complex. Kubernetes. Service Mesh. Helm Charts. Hundreds of YAML files. For large organizations, those technologies make sense. For an independent software developer? Probably not.
</p>
<blockquote class="border-l-4 border-brand-400 bg-orange-50/50 rounded-lg pl-5 pr-4 py-3 my-5 max-w-xl mx-auto">
<p class="text-base text-gray-700 font-semibold italic leading-relaxed">
&ldquo;Deploy a production-ready application with a single Git push.&rdquo;
</p>
</blockquote>
<p class="text-gray-700 leading-relaxed">
That's it. Everything in this infrastructure exists to support that single objective.
</p>
</div>
</div>
</section>
<!-- THE ARSENAL HEADER -->
<section class="py-16 lg:py-20">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-10">
<div class="inline-flex items-center gap-1.5 bg-amber-50 border border-amber-200 rounded-full px-3.5 py-1 mb-4">
<span class="text-amber-700 text-xs font-medium tracking-wide">The Arsenal</span>
</div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900">The <span class="gradient-text">Arsenal</span></h2>
<p class="text-gray-500 mt-3 max-w-2xl mx-auto">Instead of relying on one massive platform, I assembled several focused open-source tools. Each tool does one thing exceptionally well. Together, they form a lightweight but surprisingly capable PaaS.</p>
</div>
</div>
</section>
<!-- DIAGRAM CARD -->
<section class="py-8 lg:py-12 bg-gray-50/80">
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
<h3 class="text-lg font-bold text-gray-900 text-center mb-6">Infrastructure Components Overview</h3>
<div class="grid grid-cols-2 sm:grid-cols-4">
<div class="border-b border-r border-gray-200 px-4 py-8 text-center text-gray-700 font-semibold text-xs sm:text-sm">Developer</div>
<div class="border-b border-r border-gray-200 px-4 py-8 text-center text-gray-700 font-semibold text-xs sm:text-sm">Git Push</div>
<div class="border-b border-r border-gray-200 px-4 py-8 text-center text-gray-700 font-semibold text-xs sm:text-sm">Gitea</div>
<div class="border-b border-gray-200 px-4 py-8 text-center text-gray-700 font-semibold text-xs sm:text-sm">Portainer</div>
<div class="border-r border-gray-200 px-4 py-8 text-center text-gray-700 font-semibold text-xs sm:text-sm">Docker Build</div>
<div class="border-r border-gray-200 px-4 py-8 text-center text-gray-700 font-semibold text-xs sm:text-sm">Deploy</div>
<div class="border-r border-gray-200 px-4 py-8 text-center text-gray-700 font-semibold text-xs sm:text-sm">Nginx Proxy</div>
<div class="px-4 py-8 text-center text-gray-700 font-semibold text-xs sm:text-sm">HTTPS Live</div>
</div>
</div>
</section>
<!-- 1. GITEA -->
<section class="py-16 lg:py-20">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
<div>
<div class="inline-flex items-center gap-1.5 bg-gray-100 border border-gray-200 rounded-lg px-3 py-1 mb-3">
<span class="text-gray-600 text-xs font-semibold tracking-wide">Tool #1</span>
</div>
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">Gitea</h2>
<p class="text-sm text-gray-500 mb-1">Self-Hosted Git Repository</p>
<a href="https://about.gitea.com/" target="_blank" rel="noopener" class="text-xs text-brand-600 hover:text-brand-700 font-medium">about.gitea.com &rarr;</a>
<div class="mt-5 text-gray-700 leading-relaxed text-sm space-y-3">
<p>Every application begins here. Gitea stores all source code repositories for my commercial and internal projects.</p>
<p>I chose Gitea because it is lightweight, fast, self-hosted, open source, and extremely easy to maintain. Running my own Git server also means complete ownership of the development workflow.</p>
<p class="font-semibold text-gray-800">Every deployment begins with a Git commit.</p>
</div>
</div>
<div>
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200">
<img src="img_paas/landing-gitea.png" alt="Gitea landing page" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Gitea &mdash; lightweight self-hosted Git service</p>
</div>
</div>
</div>
</section>
<!-- GITEA REAL USAGE -->
<section class="py-8 bg-gray-50/80">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200 max-w-2xl mx-auto">
<img src="img_paas/real-world-usage-gitea.png" alt="Gitea real-world usage" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Real-world Gitea dashboard &mdash; managing multiple repositories for demo applications</p>
</div>
</section>
<!-- 2. PORTAINER -->
<section class="py-16 lg:py-20">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
<div>
<div class="inline-flex items-center gap-1.5 bg-gray-100 border border-gray-200 rounded-lg px-3 py-1 mb-3">
<span class="text-gray-600 text-xs font-semibold tracking-wide">Tool #2</span>
</div>
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">Portainer</h2>
<p class="text-sm text-gray-500 mb-1">Docker & Deployment Management</p>
<a href="https://www.portainer.io/" target="_blank" rel="noopener" class="text-xs text-brand-600 hover:text-brand-700 font-medium">portainer.io &rarr;</a>
<div class="mt-5 text-gray-700 leading-relaxed text-sm space-y-3">
<p>Portainer is the heart of the platform. Many people think Portainer is simply a Docker dashboard. It is much more than that.</p>
<p>Every application in my infrastructure is defined by a Docker Compose file stored inside its Git repository. Each stack in Portainer continuously monitors its corresponding repository. Whenever new commits arrive, Portainer automatically:</p>
<ul class="space-y-1.5 pl-2">
<li class="flex items-start gap-2"><svg class="w-3.5 h-3.5 text-brand-500 mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg><span>pulls the latest source code</span></li>
<li class="flex items-start gap-2"><svg class="w-3.5 h-3.5 text-brand-500 mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg><span>rebuilds the Docker image</span></li>
<li class="flex items-start gap-2"><svg class="w-3.5 h-3.5 text-brand-500 mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg><span>recreates the container</span></li>
<li class="flex items-start gap-2"><svg class="w-3.5 h-3.5 text-brand-500 mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z"/></svg><span>deploys the updated application</span></li>
</ul>
<p>No SSH. No manual publishing. No copying files. No Remote Desktop. Deployment becomes almost effortless. From my perspective, updating production often feels no different from committing code.</p>
</div>
</div>
<div>
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200">
<img src="img_paas/landing-portainer.png" alt="Portainer landing page" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Portainer &mdash; Docker management and CI/CD automation</p>
</div>
</div>
</div>
</section>
<!-- PORTAINER REAL USAGE -->
<section class="py-8 bg-gray-50/80">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200 max-w-3xl mx-auto">
<img src="img_paas/real-world-usage-portainer.png" alt="Portainer real-world usage" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Real-world Portainer dashboard &mdash; managing 30+ running containers</p>
</div>
</section>
<!-- 3. NGINX PROXY MANAGER -->
<section class="py-16 lg:py-20">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
<div>
<div class="inline-flex items-center gap-1.5 bg-gray-100 border border-gray-200 rounded-lg px-3 py-1 mb-3">
<span class="text-gray-600 text-xs font-semibold tracking-wide">Tool #3</span>
</div>
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">Nginx Proxy Manager</h2>
<p class="text-sm text-gray-500 mb-1">Reverse Proxy Made Simple</p>
<a href="https://nginxproxymanager.com/" target="_blank" rel="noopener" class="text-xs text-brand-600 hover:text-brand-700 font-medium">nginxproxymanager.com &rarr;</a>
<div class="mt-5 text-gray-700 leading-relaxed text-sm space-y-3">
<p>If Portainer is the heart, Nginx Proxy Manager is the traffic controller. Every application runs inside Docker. None of them expose ports directly to the internet. Instead, every container communicates through Docker's internal network.</p>
<p>This design provides several advantages. Applications can securely communicate with PostgreSQL, Microsoft SQL Server, CloudBeaver, internal APIs, and other containers without exposing database ports publicly.</p>
<p>Nginx Proxy Manager simply routes incoming HTTPS traffic to the correct container. It also automates one of the most annoying parts of hosting websites: SSL certificates. Let's Encrypt certificates are issued and renewed automatically. No manual renewal. No certificate expiration reminders. No downtime.</p>
</div>
</div>
<div>
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200">
<img src="img_paas/landing-nginx-proxy-manager.png" alt="Nginx Proxy Manager landing page" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Nginx Proxy Manager &mdash; reverse proxy with automated SSL</p>
</div>
</div>
</div>
</section>
<!-- NPM REAL USAGE -->
<section class="py-8 bg-gray-50/80">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200 max-w-3xl mx-auto">
<img src="img_paas/real-world-nginx-proxy-manager.png" alt="Nginx Proxy Manager real-world" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Real-world Nginx Proxy Manager &mdash; routing traffic to dozens of containers</p>
</div>
</section>
<!-- 4. DATABASES -->
<section class="py-16 lg:py-20">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-10">
<div class="inline-flex items-center gap-1.5 bg-violet-50 border border-violet-200 rounded-full px-3.5 py-1 mb-4">
<span class="text-violet-700 text-xs font-medium tracking-wide">Data Layer</span>
</div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900">PostgreSQL & <span class="gradient-text">Microsoft SQL Server</span></h2>
</div>
<div class="bg-white border border-gray-200 rounded-2xl p-6 sm:p-8 tool-card">
<p class="text-gray-700 leading-relaxed mb-4">
Different projects have different requirements. Some customers prefer PostgreSQL. Others require Microsoft SQL Server.
</p>
<p class="text-gray-700 leading-relaxed">
Both database servers run as Docker containers inside the same private network. Applications connect using container names rather than IP addresses. Everything remains isolated from the public internet. Simple. Secure. Reliable.
</p>
</div>
</div>
</section>
<!-- 5. CLOUDBEAVER -->
<section class="py-16 lg:py-20 bg-gray-50/80">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
<div>
<div class="inline-flex items-center gap-1.5 bg-gray-100 border border-gray-200 rounded-lg px-3 py-1 mb-3">
<span class="text-gray-600 text-xs font-semibold tracking-wide">Tool #4</span>
</div>
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">CloudBeaver</h2>
<p class="text-sm text-gray-500 mb-1">Browser-Based Database Management</p>
<a href="https://dbeaver.com/cloudbeaver-enterprise/" target="_blank" rel="noopener" class="text-xs text-brand-600 hover:text-brand-700 font-medium">dbeaver.com/cloudbeaver &rarr;</a>
<div class="mt-5 text-gray-700 leading-relaxed text-sm space-y-3">
<p>Managing databases through Remote Desktop or command-line tools quickly becomes inconvenient. CloudBeaver provides a modern browser-based database management interface.</p>
<p>Because it resides inside the same Docker network, it communicates directly with every database container. No SQL Server ports need to be exposed publicly. No PostgreSQL ports either.</p>
<p>Database administration can be performed securely from anywhere using only HTTPS. For me, this is one of those tools that quietly saves hours every month.</p>
</div>
</div>
<div>
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200">
<img src="img_paas/landing-cloudbeaver.png" alt="CloudBeaver landing page" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">CloudBeaver &mdash; browser-based database management</p>
</div>
</div>
</div>
</section>
<!-- CLOUDBEAVER REAL USAGE -->
<section class="py-8">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200 max-w-3xl mx-auto">
<img src="img_paas/real-world-cloudbeaver.png" alt="CloudBeaver real-world usage" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Real-world CloudBeaver &mdash; managing databases securely via browser</p>
</div>
</section>
<!-- 6. DOZZLE -->
<section class="py-16 lg:py-20 bg-gray-50/80">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
<div>
<div class="inline-flex items-center gap-1.5 bg-gray-100 border border-gray-200 rounded-lg px-3 py-1 mb-3">
<span class="text-gray-600 text-xs font-semibold tracking-wide">Tool #5</span>
</div>
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">Dozzle</h2>
<p class="text-sm text-gray-500 mb-1">Real-Time Docker Logs & Monitoring</p>
<a href="https://dozzle.dev/" target="_blank" rel="noopener" class="text-xs text-brand-600 hover:text-brand-700 font-medium">dozzle.dev &rarr;</a>
<div class="mt-5 text-gray-700 leading-relaxed text-sm space-y-3">
<p>One of my favorite discoveries. Dozzle streams Docker logs in real time through an incredibly clean web interface. It also provides quick insight into container activity and overall resource usage.</p>
<p>When one application suddenly consumes more memory than expected, or starts producing unexpected logs, I usually discover it within seconds.</p>
<p>Dozzle intentionally keeps authentication simple. Rather than exposing it publicly, I place it behind Nginx Proxy Manager and enable HTTP Basic Authentication there. This approach keeps the service private while avoiding unnecessary complexity.</p>
</div>
</div>
<div>
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200">
<img src="img_paas/landing-dozzle.png" alt="Dozzle landing page" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Dozzle &mdash; real-time Docker log streaming</p>
</div>
</div>
</div>
</section>
<!-- DOZZLE REAL USAGE -->
<section class="py-8">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="rounded-xl overflow-hidden shadow-md border border-gray-200 max-w-3xl mx-auto">
<img src="img_paas/real-world-dozzle.png" alt="Dozzle real-world usage" class="w-full" loading="lazy" onerror="this.style.display='none'" />
</div>
<p class="text-[11px] text-gray-400 mt-1.5 text-center italic">Real-world Dozzle &mdash; monitoring container logs and resource usage</p>
</div>
</section>
<!-- PUTTING EVERYTHING TOGETHER -->
<section class="py-16 lg:py-20">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-10">
<div class="inline-flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 rounded-full px-3.5 py-1 mb-4">
<span class="text-emerald-700 text-xs font-medium tracking-wide">Integration</span>
</div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900">Putting Everything <span class="gradient-text">Together</span></h2>
</div>
<div class="bg-white border-2 border-brand-200 rounded-2xl p-6 sm:p-8 tool-card">
<p class="text-gray-700 leading-relaxed mb-6">Individually, each tool solves a small problem. Together, they create a surprisingly capable developer platform. The workflow looks like this:</p>
<div class="flex flex-col items-center gap-0 text-sm max-w-sm mx-auto">
<div class="bg-gray-900 text-white rounded-xl px-5 py-2.5 font-semibold w-full text-center">Developer</div>
<svg class="w-6 h-6 text-brand-500 my-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
<div class="bg-gray-900 text-white rounded-xl px-5 py-2.5 font-semibold w-full text-center">Git Push</div>
<svg class="w-6 h-6 text-brand-500 my-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
<div class="bg-blue-600 text-white rounded-xl px-5 py-2.5 font-semibold w-full text-center">Gitea Repository</div>
<svg class="w-6 h-6 text-brand-500 my-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
<div class="bg-emerald-600 text-white rounded-xl px-5 py-2.5 font-semibold w-full text-center">Portainer detects changes</div>
<svg class="w-6 h-6 text-brand-500 my-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
<div class="bg-cyan-600 text-white rounded-xl px-5 py-2.5 font-semibold w-full text-center">Docker Compose Build</div>
<svg class="w-6 h-6 text-brand-500 my-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
<div class="bg-cyan-700 text-white rounded-xl px-5 py-2.5 font-semibold w-full text-center">Container Deployment</div>
<svg class="w-6 h-6 text-brand-500 my-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
<div class="bg-purple-600 text-white rounded-xl px-5 py-2.5 font-semibold w-full text-center">Nginx Proxy Manager</div>
<svg class="w-6 h-6 text-brand-500 my-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
<div class="bg-brand-600 text-white rounded-xl px-5 py-2.5 font-semibold w-full text-center">HTTPS Live Demo</div>
</div>
<p class="text-gray-600 text-center mt-6">Updating an application often takes less than a minute. The deployment pipeline is almost invisible. Which is exactly how infrastructure should feel.</p>
</div>
</div>
</section>
<!-- WHY THIS WORKS -->
<section class="py-16 lg:py-20 bg-gray-50/80">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-10">
<div class="inline-flex items-center gap-1.5 bg-cyan-50 border border-cyan-200 rounded-full px-3.5 py-1 mb-4">
<span class="text-cyan-700 text-xs font-medium tracking-wide">Why It Works</span>
</div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900">Why This Works <span class="gradient-text">So Well</span></h2>
</div>
<div class="bg-white border border-gray-200 rounded-2xl p-6 sm:p-8 tool-card">
<p class="text-gray-700 leading-relaxed mb-4">
This platform wasn't designed to impress DevOps engineers. It was designed to remove friction.
</p>
<p class="text-gray-700 leading-relaxed mb-4">
As a solo developer, context switching is expensive. Every minute spent manually deploying software is a minute not spent writing software. Automation isn't about replacing people. It's about eliminating repetitive work.
</p>
<p class="text-gray-700 leading-relaxed">
Today I spend far less time thinking about deployment than I did several years ago. That allows me to focus on what actually creates value: building applications.
</p>
</div>
</div>
</section>
<!-- LESSONS LEARNED -->
<section class="py-16 lg:py-20">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-10">
<div class="inline-flex items-center gap-1.5 bg-rose-50 border border-rose-200 rounded-full px-3.5 py-1 mb-4">
<span class="text-rose-700 text-xs font-medium tracking-wide">Lessons</span>
</div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900">Lessons <span class="gradient-text">Learned</span></h2>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
<div class="bg-white border border-gray-200 rounded-xl p-5 tool-card">
<div class="w-10 h-10 rounded-lg bg-blue-50 flex items-center justify-center mb-3">
<svg class="w-5 h-5 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5m8.25 3v6.75m0 0l-3-3m3 3l3-3M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z"/></svg>
</div>
<h3 class="text-base font-semibold text-gray-900 mb-1">Containers Simplify</h3>
<p class="text-sm text-gray-500">Every application carries its own runtime, dependencies, and configuration. There are no "works on my machine" surprises anymore.</p>
</div>
<div class="bg-white border border-gray-200 rounded-xl p-5 tool-card">
<div class="w-10 h-10 rounded-lg bg-purple-50 flex items-center justify-center mb-3">
<svg class="w-5 h-5 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M12 21a9 9 0 01-9-9m9 9a9 9 0 009-9m-9 9V3m0 0a9 9 0 00-5.657 2.343M12 3a9 9 0 015.657 2.343"/></svg>
</div>
<h3 class="text-base font-semibold text-gray-900 mb-1">Private Networking</h3>
<p class="text-sm text-gray-500">Almost every service communicates entirely through Docker's internal network. Very few ports are exposed to the public internet. Smaller attack surface, less configuration, greater peace of mind.</p>
</div>
<div class="bg-white border border-gray-200 rounded-xl p-5 tool-card">
<div class="w-10 h-10 rounded-lg bg-emerald-50 flex items-center justify-center mb-3">
<svg class="w-5 h-5 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M3.75 13.5l10.5-11.25L12 3.75 3.75 13.5zm0 0L12 21.75l8.25-10.5M12 21.75V13.5"/></svg>
</div>
<h3 class="text-base font-semibold text-gray-900 mb-1">CI/CD Without K8s</h3>
<p class="text-sm text-gray-500">Modern deployment pipelines can be remarkably simple. Docker Compose plus Portainer provides more than enough power without introducing unnecessary operational complexity.</p>
</div>
<div class="bg-white border border-gray-200 rounded-xl p-5 tool-card">
<div class="w-10 h-10 rounded-lg bg-amber-50 flex items-center justify-center mb-3">
<svg class="w-5 h-5 text-amber-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 01-1.161.886l-.143.048a1.107 1.107 0 00-.57 1.664c.369.532.405 1.203.094 1.775a2.25 2.25 0 01-1.513 1.054l-.18.03a2.25 2.25 0 00-1.283.958l-.512.768a2.25 2.25 0 01-2.023 1.095l-.171-.01a2.25 2.25 0 00-1.716.632l-.417.418a2.25 2.25 0 00-.577 1.06l-.163.652a2.25 2.25 0 01-1.467 1.525l-1.08.36a2.25 2.25 0 01-1.401-.015l-.414-.138a2.25 2.25 0 00-1.948.23l-1.513.987"/></svg>
</div>
<h3 class="text-base font-semibold text-gray-900 mb-1">Open Source Power</h3>
<p class="text-sm text-gray-500">Every major component comes from passionate open-source communities. Their work enables independent developers to build infrastructure that would have required an entire operations team just a few years ago.</p>
</div>
</div>
</div>
</section>
<!-- FINAL THOUGHTS -->
<section class="py-16 lg:py-20 bg-gradient-to-br from-orange-50 via-white to-orange-50/80">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<div class="bg-white border border-orange-200 rounded-3xl p-8 sm:p-12 shadow-sm">
<div class="inline-flex items-center gap-1.5 bg-orange-50 border border-orange-200 rounded-full px-3.5 py-1 mb-5">
<span class="text-brand-700 text-xs font-medium tracking-wide">Final Thoughts</span>
</div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-4">Final <span class="gradient-text">Thoughts</span></h2>
<p class="text-gray-600 leading-relaxed max-w-2xl mx-auto mb-4">
People often ask what cloud platform I use. The answer surprises them.
</p>
<p class="text-gray-600 leading-relaxed max-w-2xl mx-auto mb-4">
There isn't one. Instead, I built a small, focused Platform as a Service tailored specifically to my workflow.
</p>
<p class="text-gray-600 leading-relaxed max-w-2xl mx-auto mb-4">
It isn't the biggest. It isn't the most complicated. It certainly isn't the most expensive.
</p>
<p class="text-gray-600 leading-relaxed max-w-2xl mx-auto">
But it quietly hosts more than thirty production demo applications, supports smooth CI/CD deployments, minimizes operational overhead, and lets me concentrate on building software instead of maintaining infrastructure.
</p>
<blockquote class="border-l-4 border-brand-400 bg-orange-50/50 rounded-lg pl-5 pr-4 py-3 my-6 max-w-xl mx-auto">
<p class="text-base text-gray-700 font-semibold italic leading-relaxed">
&ldquo;Sometimes, the best engineering solution isn't the most sophisticated one. It's the one that stays out of your way and lets you keep creating.&rdquo;
</p>
</blockquote>
</div>
</div>
</section>
<!-- FOOTER -->
<footer class="bg-white border-t border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div class="grid grid-cols-2 md:grid-cols-4 gap-8">
<div class="col-span-2 md:col-span-1"><a href="index.html" class="flex items-center gap-2 mb-3"><img src="img/logo-indotalent-dark.svg" alt="Indotalent" class="h-6 w-auto" /></a><p class="text-xs text-gray-500 leading-relaxed">Enterprise .NET 10 & Blazor complete source code. Back End + Front End in one VSA monolith. Thank you for your support.</p></div>
<div><h5 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Products</h5><ul class="space-y-2"><li><a href="index.html#products" class="text-sm text-gray-500 hover:text-brand-500">CRM</a></li><li><a href="index.html#products" class="text-sm text-gray-500 hover:text-brand-500">CMS</a></li><li><a href="index.html#products" class="text-sm text-gray-500 hover:text-brand-500">HRM</a></li><li><a href="index.html#products" class="text-sm text-gray-500 hover:text-brand-500">SCM</a></li><li><a href="index.html#products" class="text-sm text-gray-500 hover:text-brand-500">WMS</a></li></ul></div>
<div><h5 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Resources</h5><ul class="space-y-2"><li><a href="https://github.com/go2ismail" target="_blank" rel="noopener" class="text-sm text-gray-500 hover:text-brand-500">GitHub</a></li><li><a href="blog.html" class="text-sm text-gray-500 hover:text-brand-500">Blog</a></li><li><a href="support.html" class="text-sm text-gray-500 hover:text-brand-500">Support</a></li><li><a href="my-paas-arsenal.html" class="text-sm text-brand-600 hover:text-brand-500 font-medium">My PaaS Arsenal</a></li></ul></div>
<div><h5 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Company</h5><ul class="space-y-2"><li><a href="about-the-developer.html" class="text-sm text-gray-500 hover:text-brand-500">About the Developer</a></li><li><a href="privacy-policy.html" class="text-sm text-gray-500 hover:text-brand-500">Privacy</a></li><li><a href="no-refund-policy.html" class="text-sm text-gray-500 hover:text-brand-500">No Refund</a></li><li><a href="license.html" class="text-sm text-gray-500 hover:text-brand-500">License</a></li><li><a href="mailto:hi@indotalent.com" class="text-sm text-gray-500 hover:text-brand-500">Contact</a></li></ul></div>
</div>
<div class="border-t border-gray-200 mt-8 pt-6 flex flex-col md:flex-row items-center justify-between gap-4"><p class="text-xs text-gray-500">&copy; 2024-2026 Indotalent. All rights reserved.</p><p class="text-xs text-gray-500">Built with <span class="text-brand-500">&hearts;</span> by go2ismail</p></div>
</div>
</footer>
<!-- FAB Back to Top -->
<a href="#" class="fab-back-to-top" id="backToTop" title="Back to top">
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5"/></svg>
</a>
<script>
document.addEventListener('DOMContentLoaded',function(){
gsap.registerPlugin(ScrollTrigger);
gsap.fromTo('#heroContent',{autoAlpha:0,y:40},{autoAlpha:1,y:0,duration:.8});
// Animate tool cards on scroll
document.querySelectorAll('.tool-card').forEach(card => {
gsap.fromTo(card,{autoAlpha:0,y:30},{autoAlpha:1,y:0,duration:.6,scrollTrigger:{trigger:card,start:'top 85%'}});
});
// Back to top FAB
const fab=document.getElementById('backToTop');
window.addEventListener('scroll',function(){
if(window.scrollY>500)fab.classList.add('visible');else fab.classList.remove('visible');
});
fab.addEventListener('click',function(e){e.preventDefault();window.scrollTo({top:0,behavior:'smooth'})});
});
</script>
</body>
</html>