Skip to content

Deploying a .NET App

What is .NET?

.NET is a modern, cross-platform framework for building fast, reliable, and scalable applications. Developed by Microsoft, .NET provides a unified platform for building web applications, APIs, desktop applications, mobile applications, and cloud services.

Key features include:

  • Unified platform for Windows, Linux, and macOS
  • High-performance ASP.NET Core for web and API applications
  • Entity Framework Core for object-relational mapping
  • Strong type system and garbage collection
  • Comprehensive dependency injection support
  • Built-in authentication and authorization with Identity
  • Middleware pipeline for request processing
  • Comprehensive logging and configuration management
  • Health checks and diagnostics
  • Async/await patterns for non-blocking operations
  • LINQ (Language Integrated Query) for data operations
  • Comprehensive testing frameworks (xUnit, NUnit, MSTest)
  • NuGet package management
  • Docker containerization support
  • Cloud-native features (Azure integration, health probes)
  • Real-time communication with SignalR
  • Background jobs and hosted services
  • OpenAPI/Swagger support for API documentation
  • Built-in dependency injection container

.NET is ideal for building RESTful APIs, microservices, enterprise web applications, real-time applications, cloud services, e-commerce platforms, and large-scale business applications.

Prerequisites

Before deploying a .NET application to Klutch.sh, ensure you have:

  • .NET 7.0 SDK or later installed on your local machine
  • C# programming knowledge
  • Git and a GitHub account
  • A Klutch.sh account with dashboard access
  • Optional: PostgreSQL or SQL Server for data persistence
  • Optional: Redis for caching and session management

Getting Started with .NET

Step 1: Create a .NET Web API Project

Using the .NET CLI, create a new minimal API project:

Terminal window
dotnet new webapi -n DotnetApp -f net8.0
cd DotnetApp

This creates a project structure:

DotnetApp/
├── Controllers/
│ └── WeatherForecastController.cs
├── Properties/
│ └── launchSettings.json
├── appsettings.json
├── appsettings.Development.json
├── DotnetApp.csproj
├── Program.cs
└── WeatherForecast.cs

Step 2: Configure the Main Application

Update Program.cs:

using DotnetApp.Models;
using DotnetApp.Repositories;
using DotnetApp.Services;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Database configuration
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? "Host=localhost;Port=5432;Database=dotnet_app;Username=postgres;Password=postgres";
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(connectionString));
// Add custom services
builder.Services.AddScoped<IItemRepository, ItemRepository>();
builder.Services.AddScoped<IItemService, ItemService>();
// Add CORS
builder.Services.AddCors(options => options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Add caching
builder.Services.AddMemoryCache();
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis")
?? "localhost:6379";
});
// Add logging
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("AllowAll");
app.UseAuthorization();
app.MapControllers();
// Health check endpoint
app.MapGet("/health", () => Results.Ok(new { status = "healthy", service = "dotnet-app" }))
.WithName("Health")
.WithOpenApi();
app.Run();

Step 3: Create Entity Models

Create Models/Item.cs:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DotnetApp.Models
{
[Table("items")]
public class Item
{
[Key]
public long Id { get; set; }
[Required]
[StringLength(100)]
[Column("name")]
public string Name { get; set; } = string.Empty;
[StringLength(500)]
[Column("description")]
public string? Description { get; set; }
[Required]
[Range(0, int.MaxValue)]
[Column("price")]
public int Price { get; set; }
[Column("created_at")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Column("updated_at")]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
}

Step 4: Create Database Context

Create Data/ApplicationDbContext.cs:

using DotnetApp.Models;
using Microsoft.EntityFrameworkCore;
namespace DotnetApp.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Item> Items { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Item>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(100);
entity.Property(e => e.Description).HasMaxLength(500);
entity.Property(e => e.Price).IsRequired();
entity.HasIndex(e => e.Name).IsUnique();
});
}
}
}

Step 5: Create Repository Pattern

Create Repositories/IItemRepository.cs:

using DotnetApp.Models;
namespace DotnetApp.Repositories
{
public interface IItemRepository
{
Task<IEnumerable<Item>> GetAllAsync(int page = 0, int pageSize = 10);
Task<Item?> GetByIdAsync(long id);
Task<Item> CreateAsync(Item item);
Task<Item> UpdateAsync(Item item);
Task<bool> DeleteAsync(long id);
Task<long> GetCountAsync();
}
}

Create Repositories/ItemRepository.cs:

using DotnetApp.Data;
using DotnetApp.Models;
using Microsoft.EntityFrameworkCore;
namespace DotnetApp.Repositories
{
public class ItemRepository : IItemRepository
{
private readonly ApplicationDbContext _context;
public ItemRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Item>> GetAllAsync(int page = 0, int pageSize = 10)
{
return await _context.Items
.Skip(page * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<Item?> GetByIdAsync(long id)
{
return await _context.Items.FindAsync(id);
}
public async Task<Item> CreateAsync(Item item)
{
item.CreatedAt = DateTime.UtcNow;
item.UpdatedAt = DateTime.UtcNow;
_context.Items.Add(item);
await _context.SaveChangesAsync();
return item;
}
public async Task<Item> UpdateAsync(Item item)
{
item.UpdatedAt = DateTime.UtcNow;
_context.Items.Update(item);
await _context.SaveChangesAsync();
return item;
}
public async Task<bool> DeleteAsync(long id)
{
var item = await _context.Items.FindAsync(id);
if (item == null) return false;
_context.Items.Remove(item);
await _context.SaveChangesAsync();
return true;
}
public async Task<long> GetCountAsync()
{
return await _context.Items.CountAsync();
}
}
}

Step 6: Create Service Layer

Create Services/IItemService.cs:

using DotnetApp.Models;
namespace DotnetApp.Services
{
public interface IItemService
{
Task<IEnumerable<Item>> GetAllItemsAsync(int page = 0, int pageSize = 10);
Task<Item?> GetItemByIdAsync(long id);
Task<Item> CreateItemAsync(Item item);
Task<Item> UpdateItemAsync(long id, Item item);
Task<bool> DeleteItemAsync(long id);
Task<Dictionary<string, object>> GetStatisticsAsync();
}
}

Create Services/ItemService.cs:

using DotnetApp.Models;
using DotnetApp.Repositories;
using Microsoft.Extensions.Caching.Distributed;
namespace DotnetApp.Services
{
public class ItemService : IItemService
{
private readonly IItemRepository _repository;
private readonly IDistributedCache _cache;
private readonly ILogger<ItemService> _logger;
public ItemService(IItemRepository repository, IDistributedCache cache, ILogger<ItemService> logger)
{
_repository = repository;
_cache = cache;
_logger = logger;
}
public async Task<IEnumerable<Item>> GetAllItemsAsync(int page = 0, int pageSize = 10)
{
_logger.LogInformation("Fetching all items (page: {Page}, pageSize: {PageSize})", page, pageSize);
return await _repository.GetAllAsync(page, pageSize);
}
public async Task<Item?> GetItemByIdAsync(long id)
{
_logger.LogInformation("Fetching item with id: {Id}", id);
return await _repository.GetByIdAsync(id);
}
public async Task<Item> CreateItemAsync(Item item)
{
_logger.LogInformation("Creating item: {Name}", item.Name);
var createdItem = await _repository.CreateAsync(item);
await InvalidateCacheAsync();
return createdItem;
}
public async Task<Item> UpdateItemAsync(long id, Item item)
{
_logger.LogInformation("Updating item with id: {Id}", id);
var existingItem = await _repository.GetByIdAsync(id);
if (existingItem == null)
throw new KeyNotFoundException($"Item with id {id} not found");
existingItem.Name = item.Name;
existingItem.Description = item.Description;
existingItem.Price = item.Price;
var updatedItem = await _repository.UpdateAsync(existingItem);
await InvalidateCacheAsync();
return updatedItem;
}
public async Task<bool> DeleteItemAsync(long id)
{
_logger.LogInformation("Deleting item with id: {Id}", id);
var result = await _repository.DeleteAsync(id);
if (result) await InvalidateCacheAsync();
return result;
}
public async Task<Dictionary<string, object>> GetStatisticsAsync()
{
var count = await _repository.GetCountAsync();
return new Dictionary<string, object>
{
{ "total_items", count },
{ "timestamp", DateTime.UtcNow }
};
}
private async Task InvalidateCacheAsync()
{
await _cache.RemoveAsync("items_cache");
}
}
}

Step 7: Create REST API Controller

Create Controllers/ItemsController.cs:

using DotnetApp.Models;
using DotnetApp.Services;
using Microsoft.AspNetCore.Mvc;
namespace DotnetApp.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ItemsController : ControllerBase
{
private readonly IItemService _itemService;
private readonly ILogger<ItemsController> _logger;
public ItemsController(IItemService itemService, ILogger<ItemsController> logger)
{
_itemService = itemService;
_logger = logger;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Item>>> GetItems(
[FromQuery] int page = 0,
[FromQuery] int pageSize = 10)
{
_logger.LogInformation("Getting items (page: {Page})", page);
var items = await _itemService.GetAllItemsAsync(page, pageSize);
return Ok(items);
}
[HttpGet("{id}")]
public async Task<ActionResult<Item>> GetItem(long id)
{
_logger.LogInformation("Getting item {Id}", id);
var item = await _itemService.GetItemByIdAsync(id);
if (item == null)
return NotFound();
return Ok(item);
}
[HttpPost]
public async Task<ActionResult<Item>> PostItem(Item item)
{
_logger.LogInformation("Creating new item: {Name}", item.Name);
var createdItem = await _itemService.CreateItemAsync(item);
return CreatedAtAction(nameof(GetItem), new { id = createdItem.Id }, createdItem);
}
[HttpPut("{id}")]
public async Task<IActionResult> PutItem(long id, Item item)
{
_logger.LogInformation("Updating item {Id}", id);
try
{
var updatedItem = await _itemService.UpdateItemAsync(id, item);
return Ok(updatedItem);
}
catch (KeyNotFoundException)
{
return NotFound();
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteItem(long id)
{
_logger.LogInformation("Deleting item {Id}", id);
var result = await _itemService.DeleteItemAsync(id);
if (!result)
return NotFound();
return NoContent();
}
[HttpGet("stats")]
public async Task<ActionResult<Dictionary<string, object>>> GetStatistics()
{
_logger.LogInformation("Getting statistics");
var stats = await _itemService.GetStatisticsAsync();
return Ok(stats);
}
}
}

Step 8: Configure Application Settings

Update appsettings.json:

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=dotnet_app;Username=postgres;Password=postgres",
"Redis": "localhost:6379"
},
"AllowedHosts": "*"
}

Create appsettings.Production.json:

{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server={DATABASE_URL}",
"Redis": "{REDIS_URL}"
}
}

Step 9: Add Required NuGet Packages

Terminal window
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package StackExchange.Redis
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

Step 10: Build and Test Locally

Terminal window
# Restore dependencies
dotnet restore
# Build the application
dotnet build
# Create database (requires PostgreSQL running locally)
dotnet ef database update
# Run the application
dotnet run
# Access health endpoint
curl http://localhost:5000/health

Deploying Without a Dockerfile

Klutch.sh uses Nixpacks to automatically detect and build your .NET application from your source code.

Prepare Your Repository

  1. Initialize a Git repository and commit your code:
Terminal window
git init
git add .
git commit -m "Initial .NET app commit"
  1. Create a .gitignore file:
bin/
obj/
*.csproj.user
.vs/
.vscode/
*.db
*.sqlite
appsettings.*.json
!appsettings.Development.json
!appsettings.Production.json
logs/
*.log
  1. Ensure your project structure includes:

    • .csproj file in the root directory
    • Program.cs with proper configuration
    • appsettings.json for configuration
  2. Push to GitHub:

Terminal window
git remote add origin https://github.com/YOUR_USERNAME/dotnet-app.git
git branch -M main
git push -u origin main

Deploy to Klutch.sh

  1. Log in to Klutch.sh dashboard.

  2. Click “Create a new project” and provide a project name.

  3. Inside your project, click “Create a new app”.

  4. Repository Configuration:

    • Select your GitHub repository containing the .NET app
    • Select the branch to deploy (typically main)
  5. Traffic Settings:

    • Select “HTTP” as the traffic type
  6. Port Configuration:

    • Set the internal port to 8080 (Nixpacks defaults to port 8080 for .NET apps)
  7. Environment Variables: Set the following environment variables in the Klutch.sh dashboard:

    • ASPNETCORE_ENVIRONMENT: Set to Production for production deployments
    • ConnectionStrings__DefaultConnection: Your PostgreSQL connection string
    • ConnectionStrings__Redis: Your Redis connection URL (if using Redis)
    • ASPNETCORE_URLS: http://+:8080 (sets the binding address)
  8. Build and Start Commands (Optional): If you need to customize the build or start command, set these environment variables:

    • BUILD_COMMAND: Default runs dotnet publish -c Release -o /out
    • START_COMMAND: Default is /out/DotnetApp (replace with your project name)
  9. Region, Compute, and Instances:

    • Choose your desired region for optimal latency
    • Select compute resources (Starter for prototypes, Pro/Premium for production)
    • Set the number of instances (start with 1-2, scale as needed based on traffic)
  10. Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.

  11. Once deployment completes, your app will be accessible at example-app.klutch.sh.

Verifying the Deployment

Test your deployed app:

Terminal window
curl https://example-app.klutch.sh/health

You should receive:

{
"status": "healthy",
"service": "dotnet-app"
}

Access Swagger UI:

https://example-app.klutch.sh/swagger/index.html

Deploying With a Dockerfile

If you prefer more control over your build environment, you can provide a custom Dockerfile. Klutch.sh automatically detects and uses a Dockerfile in your repository’s root directory.

Create a Multi-Stage Dockerfile

Create a Dockerfile in your project root:

# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 as builder
WORKDIR /src
# Copy project file
COPY ["DotnetApp.csproj", "./"]
# Restore dependencies
RUN dotnet restore "DotnetApp.csproj"
# Copy source code
COPY . .
# Build application
RUN dotnet publish -c Release -o /app/publish
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Copy published app from builder
COPY --from=builder /app/publish .
# Create non-root user for security
RUN useradd -m -u 1000 dotnetapp && chown -R dotnetapp:dotnetapp /app
USER dotnetapp
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# Expose port
EXPOSE 8080
# Set environment variables
ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production
# Start the application
ENTRYPOINT ["dotnet", "DotnetApp.dll"]

Deploy the Dockerfile Version

  1. Push your code with the Dockerfile to GitHub:
Terminal window
git add Dockerfile
git commit -m "Add Dockerfile for custom build"
git push
  1. Log in to Klutch.sh dashboard.

  2. Create a new app:

    • Select your GitHub repository and branch
    • Set traffic type to “HTTP”
    • Set the internal port to 8080
    • Add environment variables
    • Click “Create”
  3. Klutch.sh will automatically detect your Dockerfile and use it for building and deployment.


Database Configuration

PostgreSQL Setup

PostgreSQL is the recommended database for .NET applications. To use a PostgreSQL instance:

  1. Deploy a PostgreSQL instance on Klutch.sh (from the marketplace)
  2. Get the connection details from the PostgreSQL dashboard
  3. Set the ConnectionStrings__DefaultConnection environment variable:
    Server=postgres-host;Port=5432;Database=dotnet_app;User Id=postgres;Password=your_password;

Database Migrations

Create migrations with Entity Framework Core:

Terminal window
# Create a new migration
dotnet ef migrations add InitialCreate
# Apply migrations to production database
dotnet ef database update

Or execute migrations programmatically in Program.cs:

using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
dbContext.Database.Migrate();
}

Authentication and Authorization

Identity Configuration

Add authentication to Program.cs:

builder.Services.AddIdentityApiEndpoints<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAuthorization();
var app = builder.Build();
// Map identity endpoints
app.MapIdentityApi<IdentityUser>();
app.MapControllers();
app.Run();

Protecting Endpoints

Use the [Authorize] attribute on controllers:

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class ItemsController : ControllerBase
{
// Protected endpoints
}

Caching Configuration

Redis Integration

Configure Redis in Program.cs:

builder.Services.AddStackExchangeRedisCache(options =>
{
var redisUrl = builder.Configuration.GetConnectionString("Redis") ?? "localhost:6379";
options.Configuration = redisUrl;
});

Use caching in services:

public async Task<IEnumerable<Item>> GetAllItemsAsync(int page, int pageSize)
{
var cacheKey = $"items_page_{page}_{pageSize}";
if (!await _cache.GetAsync(cacheKey, out byte[] cachedData))
{
var items = await _repository.GetAllAsync(page, pageSize);
await _cache.SetAsync(cacheKey, JsonSerializer.SerializeToUtf8Bytes(items),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) });
return items;
}
return JsonSerializer.Deserialize<IEnumerable<Item>>(cachedData) ?? new List<Item>();
}

Logging and Monitoring

Structured Logging

Configure Serilog for structured logging in Program.cs:

builder.Logging.ClearProviders();
builder.Logging
.AddConsole()
.AddDebug();
var logger = builder.Services.BuildServiceProvider()
.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Application starting...");

Use logging in services:

_logger.LogInformation("Processing request for item {ItemId}", itemId);
_logger.LogError(exception, "Error processing request");
_logger.LogDebug("Cache hit for key {CacheKey}", cacheKey);

Health Checks

Add health checks to Program.cs:

builder.Services.AddHealthChecks()
.AddDbContextCheck<ApplicationDbContext>()
.AddRedis(builder.Configuration.GetConnectionString("Redis") ?? "localhost:6379");
app.MapHealthChecks("/health");

Persistent Storage for Logs and Data

Adding Persistent Volume

  1. In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
  2. Click “Add Volume”
  3. Set the mount path: /app/logs (for application logs) or /app/data (for data files)
  4. Set the size based on your needs (e.g., 10 GB for logs, 20 GB for data)
  5. Save and redeploy

Logging to Persistent Storage

Configure logging file path:

{
"Logging": {
"LogLevel": {
"Default": "Information"
},
"File": {
"Path": "/app/logs/dotnet-app.log"
}
}
}

Custom Domains

To serve your .NET application from a custom domain:

  1. In the Klutch.sh app dashboard, navigate to “Custom Domains”
  2. Click “Add Custom Domain”
  3. Enter your domain (e.g., api.example.com)
  4. Follow the DNS configuration instructions provided

Example DNS configuration:

api.example.com CNAME example-app.klutch.sh

Troubleshooting

Issue 1: Port Already in Use

Problem: Application fails to start with “port 8080 already in use” error.

Solution:

  • Ensure ASPNETCORE_URLS environment variable is set correctly
  • Verify no other application is using port 8080
  • Check for multiple app instances consuming the same port
  • Use netstat to find which process is using the port

Issue 2: Database Connection Failures

Problem: “Unable to connect to database” errors on startup.

Solution:

  • Verify PostgreSQL connection string in environment variables
  • Test connection string format: Server=host;Port=5432;Database=name;User Id=user;Password=pass;
  • Ensure database user has proper permissions
  • Check firewall rules allow the connection
  • Verify PostgreSQL instance is running and accessible

Issue 3: Application Crashes on Startup

Problem: Application exits immediately after starting.

Solution:

  • Check application logs for errors
  • Verify all environment variables are set correctly
  • Ensure database migrations have been applied
  • Check that appsettings.Production.json is valid JSON
  • Review startup exceptions in logs

Issue 4: Out of Memory Errors

Problem: Application crashes with OutOfMemoryException.

Solution:

  • Increase instance compute resources
  • Optimize database queries and add indexing
  • Implement caching for frequently accessed data
  • Configure connection pool limits
  • Monitor memory usage via dashboard metrics

Issue 5: Slow Application Response

Problem: API endpoints respond slowly.

Solution:

  • Add database query optimization and indexes
  • Enable response compression in application
  • Implement Redis caching for frequently accessed data
  • Use async/await patterns in all I/O operations
  • Monitor performance metrics in dashboard
  • Consider scaling to additional instances

Best Practices for Production Deployment

  1. Use Dependency Injection: Leverage built-in DI container

    builder.Services.AddScoped<IItemRepository, ItemRepository>();
  2. Implement Async/Await: Always use async operations

    public async Task<Item> GetItemAsync(long id) => await _repository.GetByIdAsync(id);
  3. Configure Logging: Use structured logging with appropriate levels

    _logger.LogInformation("Item created: {@Item}", item);
  4. Database Connection Pooling: Configure optimal pool size

    optionsBuilder.UseNpgsql(connectionString, options =>
    options.MaxPoolSize(20).MinPoolSize(5));
  5. Enable HTTPS: Enforce HTTPS in production

    if (!app.Environment.IsDevelopment())
    app.UseHttpsRedirection();
  6. Input Validation: Validate all user inputs

    [Required]
    [StringLength(100)]
    public string Name { get; set; }
  7. Error Handling: Implement global exception handling

    app.UseExceptionHandler("/error");
  8. Security Headers: Add security headers middleware

    app.Use(async (context, next) =>
    {
    context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
    await next();
    });
  9. Graceful Shutdown: Configure shutdown timeout

    var cts = new CancellationTokenSource();
    app.Lifetime.ApplicationStopping.Register(() => cts.Cancel());
  10. Rate Limiting: Implement rate limiting for APIs

    builder.Services.AddRateLimiter(options =>
    options.AddFixedWindowLimiter("fixed", policy =>
    policy.PermitLimit = 100;

Resources


Conclusion

Deploying .NET applications to Klutch.sh provides a robust, scalable platform for building and running modern web applications and APIs. .NET’s cross-platform support, performance, and comprehensive ecosystem make it ideal for enterprise-grade applications.

Key takeaways:

  • Use Nixpacks for quick deployments with automatic .NET detection
  • Use Docker for complete control over dependencies and runtime
  • Leverage Entity Framework Core for efficient data persistence
  • Implement proper logging and monitoring with structured logging
  • Use the built-in dependency injection container for loosely coupled code
  • Enable caching strategies for improved performance
  • Implement authentication and authorization with Identity
  • Configure persistent storage for logs and application data
  • Monitor application health via health check endpoints
  • Keep dependencies updated for security patches

For additional help, refer to the .NET documentation or Klutch.sh support resources.