The $0 DevOps Revolution
In 2025, enterprise-grade CI/CD pipelines are no longer the domain of billion-dollar budgets.
What if you could build, test, deploy, and auto-heal a production web application β with real user data, health checks, and persistence β for $0?
This article shows you exactly how to do it.
Weβll build a professional insurance quote form in .NET 8, deploy it via Azure DevOps using Zip Deploy, and run it on Azure App Service (Free Tier) β all with zero cost, zero paid tools, and 100% automation.
No Kudu. No FTP. No CLI. No manual uploads.
Just git push β automated deploy β live app β health check β auto-restart.
This is modern DevOps β and itβs completely free.
Build the Insurance Quote App β .NET 8 MVC (Zero Dependencies)
Tech Stack (All Free & Open Source)
Component | Why it matters |
---|
.NET 8 ASP.NET Core MVC | Fast, secure, cross-platform, Microsoftβs flagship web framework |
SQLite | File-based database β no server, no license, no cost |
Bootstrap 5 + Font Awesome | Professional, responsive UI β CDN-hosted, zero install |
GitHub | Free private/public repos β version control, CI trigger |
Azure DevOps | Free 1800 pipeline minutes/month β perfect for small apps |
System design (HLD)
![hld]()
Source Code
InsuranceQuoteApp.csproj
β Minimal & Clean
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
Run dotnet restore
to pull in EF Core SQLite.
Models/InsuranceQuote.cs
β Data Model with Validation
using System.ComponentModel.DataAnnotations;
namespace InsuranceQuoteApp.Models
{
public class InsuranceQuote
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "Name is required")]
[Display(Name = "Full Name")]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "Age is required")]
[Range(18, 120, ErrorMessage = "Age must be between 18 and 120")]
public int Age { get; set; }
[Required(ErrorMessage = "Vehicle type is required")]
[Display(Name = "Vehicle Type")]
public string VehicleType { get; set; } = string.Empty;
[Required(ErrorMessage = "Driving experience is required")]
[Range(0, 60, ErrorMessage = "Experience must be between 0 and 60 years")]
public int ExperienceYears { get; set; }
public decimal QuoteAmount { get; set; }
public DateTime QuoteDate { get; set; } = DateTime.Now;
}
}
Server-side validation built-in. No JavaScript needed.
Models/QuoteContext.cs
β SQLite Database Context
using Microsoft.EntityFrameworkCore;
namespace InsuranceQuoteApp.Models
{
public class QuoteContext : DbContext
{
public QuoteContext(DbContextOptions<QuoteContext> options) : base(options) { }
public DbSet<InsuranceQuote> InsuranceQuotes { get; set; }
}
}
SQLite creates insurancequotes.db
automatically on first run.
Controllers/QuoteController.cs
β Logic & Pricing Engine
using Microsoft.AspNetCore.Mvc;
using InsuranceQuoteApp.Models;
namespace InsuranceQuoteApp.Controllers
{
public class QuoteController : Controller
{
private readonly QuoteContext _context;
public QuoteController(QuoteContext context)
{
_context = context;
}
public IActionResult Index() => View();
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Calculate(InsuranceQuote quote)
{
if (!ModelState.IsValid)
return View("Index", quote);
// Realistic pricing logic
decimal basePrice = 500m;
if (quote.Age < 25) basePrice += 200m; // Young driver
if (quote.VehicleType == "Sports Car") basePrice += 300m; // High risk
if (quote.ExperienceYears > 10) basePrice -= 100m; // Loyalty discount
if (quote.ExperienceYears == 0) basePrice += 150m; // New driver
quote.QuoteAmount = basePrice;
_context.InsuranceQuotes.Add(quote);
_context.SaveChanges();
return View("Result", quote);
}
}
}
Views/Quote/Index.cshtml
β Clean, Professional Form
@model InsuranceQuoteApp.Models.InsuranceQuote
@{
ViewData["Title"] = "Free Insurance Quote";
}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow-lg">
<div class="card-header bg-primary text-white text-center">
<h2><i class="fas fa-shield-alt"></i> Get Your Free Insurance Quote</h2>
</div>
<div class="card-body">
<form asp-action="Calculate" method="post">
<div asp-validation-summary="All" class="text-danger mb-3"></div>
<div class="form-group mb-3">
<label asp-for="Name" class="form-label"></label>
<input asp-for="Name" class="form-control" placeholder="Enter your full name" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group mb-3">
<label asp-for="Age" class="form-label"></label>
<input asp-for="Age" class="form-control" type="number" min="18" max="120" placeholder="Enter your age" />
<span asp-validation-for="Age" class="text-danger"></span>
</div>
<div class="form-group mb-3">
<label asp-for="VehicleType" class="form-label"></label>
<select asp-for="VehicleType" class="form-select">
<option value="">-- Select Vehicle Type --</option>
<option value="Sedan">Sedan</option>
<option value="SUV">SUV</option>
<option value="Truck">Truck</option>
<option value="Sports Car">Sports Car</option>
</select>
<span asp-validation-for="VehicleType" class="text-danger"></span>
</div>
<div class="form-group mb-3">
<label asp-for="ExperienceYears" class="form-label"></label>
<input asp-for="ExperienceYears" class="form-control" type="number" min="0" max="60" placeholder="Years of driving experience" />
<span asp-validation-for="ExperienceYears" class="text-danger"></span>
</div>
<div class="text-center mt-4">
<button type="submit" class="btn btn-primary btn-lg w-100">
<i class="fas fa-calculator"></i> Get My Free Quote
</button>
</div>
</form>
</div>
<div class="card-footer text-center text-muted small">
<p><i class="fas fa-lock"></i> No credit card required. Instant quote.</p>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Views/Quote/Result.cshtml
β Beautiful Quote Display
@model InsuranceQuoteApp.Models.InsuranceQuote
@{
ViewData["Title"] = "Your Insurance Quote";
}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow-lg">
<div class="card-header bg-success text-white text-center">
<h2><i class="fas fa-check-circle"></i> Your Insurance Quote</h2>
</div>
<div class="card-body">
<div class="text-center mb-4">
<div class="badge bg-primary fs-5 px-4 py-2 rounded-pill">
<strong>Quote Generated</strong>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<tbody>
<tr><th scope="row">Name</th><td>@Model.Name</td></tr>
<tr><th scope="row">Age</th><td>@Model.Age</td></tr>
<tr><th scope="row">Vehicle Type</th><td>@Model.VehicleType</td></tr>
<tr><th scope="row">Driving Experience</th><td>@Model.ExperienceYears years</td></tr>
<tr class="bg-light">
<th scope="row"><strong>Total Annual Premium</strong></th>
<td class="text-success h4"><strong>[email protected]("F2")</strong></td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-info text-center mt-4">
<i class="fas fa-info-circle"></i> Your quote is saved. No obligation to purchase.
</div>
<div class="text-center mt-4">
<a href="/Quote" class="btn btn-outline-primary btn-lg">
<i class="fas fa-arrow-left"></i> Get Another Quote
</a>
</div>
</div>
<div class="card-footer text-center text-muted small">
<p>Generated on: @Model.QuoteDate.ToString("MMMM dd, yyyy 'at' h:mm tt")</p>
</div>
</div>
</div>
</div>
</div>
Views/Shared/_Layout.cshtml
β Unified UI Theme
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Insurance Quote App</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
<link rel="stylesheet" href="~/css/style.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-shield-alt"></i> Insurance Quote App
</a>
</div>
</nav>
</header>
<div class="container mt-4">
@RenderBody()
</div>
<footer class="footer bg-light text-center text-muted py-3 mt-5">
<div class="container">
<p>© 2025 Insurance Quote App β Free & No Registration Required</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
wwwroot/css/style.css
β Polished, Responsive Design
.card { border-radius: 12px; overflow: hidden; }
.card-header { font-weight: 600; font-size: 1.5rem; }
.btn-primary { background-color: #0d6efd; border-color: #0d6efd; }
.btn-primary:hover { background-color: #0b5ed7; }
.form-label { font-weight: 600; color: #333; }
.table th, .table td { vertical-align: middle; }
.table-bordered { border: 1px solid #dee2e6; }
.table-striped tbody tr:nth-of-type(odd) { background-color: rgba(0,0,0,0.05); }
.footer { margin-top: 3rem; padding: 1.5rem 0; }
i { margin-right: 8px; }
.text-center { text-align: center !important; }
.bg-light { background-color: #f8f9fa !important; }
.alert { border-radius: 8px; font-weight: 500; }
@media (max-width: 768px) {
.card { margin: 0 1rem; }
.btn-lg { font-size: 1rem; padding: 0.75rem 1rem; }
}
appsettings.json
β Minimal Config
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Program.cs
β The Heart of the App
using Microsoft.EntityFrameworkCore;
using InsuranceQuoteApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<QuoteContext>(options =>
options.UseSqlite("Data Source=insurancequotes.db"));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Quote}/{action=Index}/{id?}");
// Auto-create DB on startup
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<QuoteContext>();
context.Database.EnsureCreated();
}
app.Run();
SQLite database is created automatically on first request β no migration needed.
Zero-Cost Deployment β Azure DevOps + Zip Deploy (CI/CD)
Now that we have a production-ready app, letβs deploy it automatically β with zero human intervention.
Weβll use:
GitHub β Code repo
Azure DevOps β CI/CD pipeline (free tier)
Azure App Service (F1 Free Tier) β Hosting
Zip Deploy API β Modern, secure, fast deployment
Health Check β Auto-restart if app crashes
Step 1. Create Azure App Service (Free Tier)
Go to https://portal.azure.com
Click Create a resource β Search βWeb Appβ
Fill in:
Subscription: Free Trial
Resource Group: InsuranceQuoteRG
Name: myinsurancequoteapp
(must be unique)
Publish: Code
Runtime stack: .NET 8 (LTS)
OS: Windows
Plan: Free (F1)
β CRITICAL
Click Review + Create β Create
β³ Wait 3β5 minutes.
Once done, visit: https://myinsurancequoteapp.azurewebsites.net
Youβll see a 404 β thatβs normal. Weβll deploy code next.
Step 2. Push Code to GitHub
Go to https://github.com
Create new repo: insurancequoteapp
Initialize with README
In your local project folder:
git init
git add .
git commit -m "Initial commit: Insurance Quote App .NET 8"
git remote add origin https://github.com/yourusername/insurancequoteapp.git
git branch -M main
git push -u origin main
Your code url link should look like(sample link):
π https://github.com/yourusername/insurancequoteapp
Step 3. Create Azure DevOps Pipeline (Free)
Go to https://dev.azure.com
Sign in with a Microsoft/GitHub account
Create New Project: InsuranceQuoteProject
Go to Pipelines β New Pipeline
Connect to GitHub β Select insurancequoteapp
Choose βAzure Pipelines YAMLβ
Replace the default YAML with this exact configuration:
# azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'windows-latest'
variables:
buildConfiguration: 'Release'
zipPackage: '$(Build.ArtifactStagingDirectory)/deploy.zip'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '8.x'
- script: |
dotnet restore
dotnet build --configuration $(buildConfiguration)
dotnet publish --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)
displayName: 'dotnet restore, build, publish'
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(zipPackage)'
replaceExistingArchive: true
displayName: 'Archive files to deploy.zip'
- task: AzureRmWebAppDeployment@4
inputs:
ConnectionType: 'AzureRM'
azureSubscription: 'AzureFreeSubscription'
Action: 'Deploy Azure App Service'
AppType: 'webApp'
WebAppName: 'myinsurancequoteapp'
Package: '$(zipPackage)'
UseWebDeploy: false
EnableCustomDeployment: true
DeploymentType: 'zipDeploy'
TakeAppOfflineFlag: true
RenameFilesFlag: true
DeploymentStatus: true
Save and Run β It will fail. Why? We havenβt created the Service Connection.
Step 4. Create Azure Service Connection (Critical!)
In Azure DevOps β Go to Project Settings β Service Connections
Click New service connection β Azure Resource Manager
Configure:
Connection name: AzureFreeSubscription
Authentication method: Service Principal (Automatic)
Subscription: Your Free Trial
Resource group: InsuranceQuoteRG
Click Verify and Save
Success message: Service connection 'AzureFreeSubscription' created successfully
Go back to Pipeline β Click Run
Watch the magic:
Build β 45s
Publish β 20s
Zip β 5s
Deploy β 15s
DONE.
Open URL similar to : https://myinsurancequoteapp.azurewebsites.net (Sample URL)
β Insurance quote form is LIVE!
Step 5. Enable Health Check (Auto-Recovery)
Go to Azure Portal β Resource Group β InsuranceQuoteRG
Click your app β Monitoring β Health Check
Set
Status: ON
Path: /
Interval: 5 minutes
Unhealthy threshold: 2
Click Save
Why this matters:
If your app crashes due to SQLite permissions, memory, or config β Azure will auto-restart it every 5 minutes.
No downtime. No alerts. No manual intervention.
This is production-grade resilience β on Free Tier.
Step 6. View & Backup SQLite DB (Kudu Console)
Go to: https://myinsurancequoteapp.scm.azurewebsites.net/DebugConsole
Navigate to: site/wwwroot/
Find: insurancequotes.db
Right-click β Download
β
Your usersβ quotes are stored locally β even on Free Tier.
Use DB Browser for SQLite to inspect data.
Final UI: What Users See
Screen 1
![screen1]()
Screen 2
![screen2]()
Cost Breakdown: $0.00 Total
Component | Cost |
---|
.NET 8 SDK | Free |
SQLite Database | Free |
Azure App Service (F1) | Free (12 months) |
Azure DevOps Pipelines | Free (1800 mins/month) |
GitHub | Free |
Zip Deploy API | Free |
Health Check | Free |
Font Awesome + Bootstrap CDN | Free |
Total | $0.00 |
You just built a production app with enterprise DevOps β for $0.
You built a complete DevOps pipeline:
Code β Commit β Build β Test β Deploy β Monitor β Self-Heal
All with zero cost.
Resources
This project is a microcosm of modern software delivery:
You automated the boring stuff β so you can focus on solving real problems.
You proved that enterprise-grade automation is accessible β even to students, freelancers, and indie devs.
You broke the myth β that βgood DevOps costs money.β
You embraced the Microsoft stack β not because itβs the only option, but because itβs now the best free option.
This is what DevOps looks like in 2025:
One git commit β Global deployment β Zero human touch β Self-healing system