Deploying .NET Core Applications
This comprehensive guide covers everything you need to know about deploying .NET Core applications via Deploy with Git, from lightweight Web APIs to full ASP.NET Core applications with Blazor.
Overview
Deploy with Git automatically detects .NET Core applications by looking for:
.csproj(C# project file).fsproj(F# project file).vbproj(VB.NET project file).sln(Solution file)- Framework detection via SDK references in project files
Basic .NET Deployment
Simple Web API
docker run -d \
--name dotnet-api \
-e GIT_REPO_URL=https://github.com/your-username/dotnet-api \
-e APP_PORT=5000 \
-p 5000:5000 \
runonflux/orbit:latest
What Happens Automatically
- Detection: Finds
.csprojor.slnand identifies as .NET Core project - Version Selection:
- Checks
DOTNET_VERSIONenvironment variable - Checks
global.jsonfor SDK version - Checks
TargetFrameworkin.csproj(net6.0, net7.0, net8.0, net9.0) - Falls back to .NET 8.0 LTS
- Checks
- Runtime Installation (First deployment only, ~30-70s):
- Installs required libraries: ICU (globalization), Kerberos (enterprise auth)
- Downloads and installs .NET SDK using Microsoft's official script
- Installs to
/opt/flux-tools/dotnet - Future deployments skip this step (~5-10s updates)
- Dependencies: Runs
dotnet restore(uses--locked-modeifpackages.lock.jsonexists) - Build: Framework-specific (publish for web apps, build for console/worker)
- Start: Executes appropriate command (dotnet run, dotnet ./publish/app.dll)
Framework-Specific Guides
ASP.NET Core Web API
ASP.NET Core is a cross-platform framework for building modern web APIs. Deploy with Git automatically detects and optimizes ASP.NET Core applications:
docker run -d \
--name aspnet-api \
-e GIT_REPO_URL=https://github.com/your-username/aspnet-api \
-e APP_PORT=5000 \
-e DOTNET_VERSION=8.0 \
-p 5000:5000 \
runonflux/orbit:latest
What Deploy with Git Does Automatically:
- Framework Detection: Identifies ASP.NET Core via
Microsoft.AspNetCoreorMicrosoft.NET.Sdk.Webreferences - Production Build: Runs
dotnet publish -c Release -o ./publish - URL Configuration: Sets
ASPNETCORE_URLS=http://0.0.0.0:5000for container access - Environment Setup: Configures
ASPNETCORE_ENVIRONMENT=Production - Server Start: Uses
dotnet ./publish/YourApp.dll --urls "http://0.0.0.0:$APP_PORT"
Example Program.cs (Minimal API):
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapGet("/", () => new { message = "ASP.NET Core API", version = "1.0.0" });
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));
app.Run();
Important Environment Variables:
docker run -d \
--name aspnet-api \
-e GIT_REPO_URL=https://github.com/your-username/aspnet-api \
-e APP_PORT=5000 \
-e DOTNET_VERSION=8.0 \
-e ASPNETCORE_ENVIRONMENT=Production \
-e ConnectionStrings__DefaultConnection="Server=db;Database=mydb;User=sa;Password=YourPassword" \
-p 5000:5000 \
runonflux/orbit:latest
Blazor Server
Blazor Server is a framework for building interactive web UIs using C# instead of JavaScript:
docker run -d \
--name blazor-server \
-e GIT_REPO_URL=https://github.com/your-username/blazor-server \
-e APP_PORT=5000 \
-p 5000:5000 \
runonflux/orbit:latest
What Deploy with Git Does:
- Detection: Identifies Blazor Server via ASP.NET Core detection
- Production Build: Runs
dotnet publish -c Release -o ./publish - Server Start: Same as ASP.NET Core (published DLL)
Blazor WebAssembly
Blazor WebAssembly runs .NET code in the browser using WebAssembly:
docker run -d \
--name blazor-wasm \
-e GIT_REPO_URL=https://github.com/your-username/blazor-wasm \
-e APP_PORT=5000 \
-p 5000:5000 \
runonflux/orbit:latest
What Deploy with Git Does:
- Detection: Identifies Blazor WASM via
Microsoft.AspNetCore.Components.WebAssemblyreference - Production Build: Runs
dotnet publish -c Release -o ./publish - Static File Serving: Serves
wwwrootdirectory using Node.js static server - Server Start: Uses built-in static file server for
publish/wwwroot
Worker Service
Worker Services are background services for long-running tasks:
docker run -d \
--name worker-service \
-e GIT_REPO_URL=https://github.com/your-username/worker-service \
-e APP_PORT=5000 \
-p 5000:5000 \
runonflux/orbit:latest
Example Program.cs:
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
host.Run();
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(10000, stoppingToken);
}
}
}
What Deploy with Git Does:
- Detection: Identifies Worker Service via
Microsoft.Extensions.Hostingreference - Build: Runs
dotnet build -c Release(publish not needed for workers) - Server Start: Uses
dotnet run --no-buildordotnet ./publish/Worker.dll
gRPC Services
gRPC is a high-performance RPC framework:
docker run -d \
--name grpc-service \
-e GIT_REPO_URL=https://github.com/your-username/grpc-service \
-e APP_PORT=5000 \
-p 5000:5000 \
runonflux/orbit:latest
What Deploy with Git Does:
- Detection: Identifies gRPC via
Grpc.AspNetCorereference - Production Build: Runs
dotnet publish -c Release -o ./publish - Server Start: Same as ASP.NET Core
Console Applications
Simple console applications for CLI tools or scripts:
docker run -d \
--name console-app \
-e GIT_REPO_URL=https://github.com/your-username/console-app \
-e APP_PORT=5000 \
-p 5000:5000 \
runonflux/orbit:latest
What Deploy with Git Does:
- Detection: Default if no framework-specific references found
- Build: Runs
dotnet build -c Release - Server Start: Uses
dotnet run --no-buildor published executable
Version Management
Specify .NET SDK Version
Option 1: Environment Variable
docker run -d \
-e GIT_REPO_URL=https://github.com/your-username/dotnet-app \
-e APP_PORT=5000 \
-e DOTNET_VERSION=9.0 \
-p 5000:5000 \
runonflux/orbit:latest
Option 2: global.json
{
"sdk": {
"version": "8.0.100"
}
}
Option 3: .csproj TargetFramework
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
Supported Versions: .NET 6.0, 7.0, 8.0 (LTS), 9.0
Performance Optimization
Memory Configuration
Deploy with Git automatically configures .NET for production:
Memory Limit: Auto-configured to 75% of container memory
- Minimum: 512MB
- Maximum: 8GB
- Set via
DOTNET_GCHeapHardLimitenvironment variable
Container Settings (automatically enabled):
DOTNET_RUNNING_IN_CONTAINER=true
DOTNET_EnableDiagnostics=0
DOTNET_TieredPGO=1
DOTNET_ReadyToRun=1
Example for Large Applications:
# On Flux Cloud, increase container RAM:
RAM: 4096 MB # .NET will use ~3GB (75%)
Environment Variables:
GIT_REPO_URL: https://github.com/your-username/large-dotnet-app
APP_PORT: 5000
DOTNET_VERSION: 8.0
NuGet Package Caching
Dependencies are cached based on lock file hash:
- First deployment: Restores all packages (~20-40s)
- Subsequent deployments: Skips restore if
packages.lock.jsonunchanged - Force reinstall: Use
FORCE_INSTALL=trueenvironment variable
Enable lock file in your project:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
</Project>
Then run dotnet restore locally to generate packages.lock.json.
Build Optimization
Production builds use Release configuration:
dotnet publish -c Release -o ./publish
This enables:
- Code optimization
- Trimming (if configured)
- ReadyToRun compilation
- AOT compilation (if configured in .NET 7+)
Environment Variables
.NET-Specific Variables
| Variable | Description | Default |
|---|---|---|
DOTNET_VERSION | .NET SDK version (6.0-9.0) | Auto-detected from global.json or .csproj |
ASPNETCORE_ENVIRONMENT | ASP.NET Core environment | Production |
ASPNETCORE_URLS | URL bindings | http://0.0.0.0:$APP_PORT |
Common Application Variables
docker run -d \
--name dotnet-app \
-e GIT_REPO_URL=https://github.com/your-username/dotnet-app \
-e APP_PORT=5000 \
-e DOTNET_VERSION=8.0 \
-e ASPNETCORE_ENVIRONMENT=Production \
-e ConnectionStrings__DefaultConnection="Server=db;Database=mydb" \
-e Logging__LogLevel__Default=Information \
-p 5000:5000 \
runonflux/orbit:latest
Environment Variable Mapping:
.NET uses double underscore (__) for nested configuration:
# Environment variable
ConnectionStrings__DefaultConnection="Server=db;Database=mydb"
# Maps to appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=db;Database=mydb"
}
}
Advanced Scenarios
Monorepo - Deploy Specific Service
docker run -d \
--name dotnet-api-service \
-e GIT_REPO_URL=https://github.com/your-username/monorepo \
-e PROJECT_PATH=services/api \
-e APP_PORT=5000 \
-p 5000:5000 \
runonflux/orbit:latest
Private Repository
docker run -d \
--name private-dotnet-app \
-e GIT_REPO_URL=https://github.com/your-username/private-app \
-e GIT_TOKEN=ghp_your_personal_access_token \
-e APP_PORT=5000 \
-p 5000:5000 \
runonflux/orbit:latest
Private NuGet Feed
For private NuGet packages, add nuget.config to your repository:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="mycompany" value="https://pkgs.dev.azure.com/mycompany/_packaging/myfeed/nuget/v3/index.json" />
</packageSources>
<packageSourceCredentials>
<mycompany>
<add key="Username" value="az" />
<add key="ClearTextPassword" value="%NUGET_AUTH_TOKEN%" />
</mycompany>
</packageSourceCredentials>
</configuration>
Then set the token as environment variable:
docker run -d \
--name dotnet-app \
-e GIT_REPO_URL=https://github.com/your-username/dotnet-app \
-e APP_PORT=5000 \
-e NUGET_AUTH_TOKEN=your-azure-devops-pat \
-p 5000:5000 \
runonflux/orbit:latest
With Deployment Hooks
Create pre-deploy.sh in your repository root:
#!/bin/bash
# Run database migrations before deployment
echo "Running database migrations..."
dotnet ef database update --no-build
Create post-deploy.sh:
#!/bin/bash
# Warm up the application cache
echo "Warming up application cache..."
curl -s http://localhost:$APP_PORT/health > /dev/null
docker run -d \
--name dotnet-with-hooks \
-e GIT_REPO_URL=https://github.com/your-username/dotnet-app \
-e APP_PORT=5000 \
-e ConnectionStrings__DefaultConnection="Server=db;Database=mydb" \
-p 5000:5000 \
runonflux/orbit:latest
Install EF Core tools for migrations:
Add to your .csproj:
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
</ItemGroup>
Or add to .config/dotnet-tools.json:
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.0",
"commands": ["dotnet-ef"]
}
}
}
Deploy with Git automatically runs dotnet tool restore if this file exists.
CI/CD Integration
Automatic Deployment with Webhooks
docker run -d \
--name dotnet-api \
-e GIT_REPO_URL=https://github.com/your-username/dotnet-api \
-e APP_PORT=5000 \
-e WEBHOOK_SECRET=my-secret-phrase \
-p 5000:5000 \
-p 9001:9001 \
runonflux/orbit:latest
Configure GitHub Webhook:
- Payload URL:
https://your-app-9001.app.runonflux.io/webhook - Content type:
application/json - Secret:
my-secret-phrase - Events: Just the
pushevent
Polling for Updates
docker run -d \
--name dotnet-api \
-e GIT_REPO_URL=https://github.com/your-username/dotnet-api \
-e APP_PORT=5000 \
-e POLLING_INTERVAL=300 \
-p 5000:5000 \
runonflux/orbit:latest
Troubleshooting
NuGet Restore Fails
Problem: "Unable to find package" or "Unable to resolve dependency"
Solution: Check .NET SDK version compatibility:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
</ItemGroup>
</Project>
Ensure DOTNET_VERSION env matches TargetFramework.
Build Fails - SDK Not Found
Problem: "The current .NET SDK does not support targeting .NET 8.0"
Solution: Specify SDK version explicitly:
-e DOTNET_VERSION=8.0
Or add global.json:
{
"sdk": {
"version": "8.0.100"
}
}
Application Not Accessible
Problem: Can't access the application on configured port
Solution: ASP.NET Core must bind to 0.0.0.0 (not localhost):
Deploy with Git automatically sets ASPNETCORE_URLS=http://0.0.0.0:$APP_PORT.
If using custom Kestrel configuration:
// Good: Accessible from outside container
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5000);
});
// Bad: Only accessible from inside container
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenLocalhost(5000);
});
High Memory Usage
Problem: .NET process using too much memory
Solution: Memory is auto-configured, but you can adjust by increasing container RAM:
# On Flux Cloud
RAM: 4096 MB # .NET will use ~3GB (75%)
Or set custom limit:
-e DOTNET_GCHeapHardLimit=2147483648 # 2GB in bytes
Slow NuGet Restore
Problem: dotnet restore takes too long
Solution:
-
Use dependency caching - enable
packages.lock.json:<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup> -
Remove unnecessary package references
-
Use local NuGet cache (automatically configured at
/app/.nuget/packages)
Entity Framework Migrations Fail
Problem: "Unable to create an object of type 'ApplicationDbContext'"
Solution: Install EF Core tools and set connection string:
docker run -d \
-e GIT_REPO_URL=https://github.com/your-username/dotnet-app \
-e APP_PORT=5000 \
-e ConnectionStrings__DefaultConnection="Server=db;Database=mydb;User=sa;Password=YourPassword" \
-p 5000:5000 \
runonflux/orbit:latest
Performance Tips
- Use Dependency Caching: Enable
packages.lock.json- saves 20-40s per deployment - Tiered Compilation: Automatically enabled with
DOTNET_TieredPGO=1 - ReadyToRun: Automatically enabled for faster startup
- Production Environment: Set
ASPNETCORE_ENVIRONMENT=Production - Health Checks: Implement
/healthendpoint for faster deployment verification - Response Compression: Enable in ASP.NET Core for better performance
- Output Caching: Use .NET 7+ output caching for API responses
- First Deployment: Takes 30-60s (.NET installation), subsequent deploys are 10-20s
Deployment Timeline
First Deployment (~30-60 seconds):
- Git clone: 5-10s
- .NET SDK installation: 15-30s (one-time, persists in container)
- NuGet restore: 10-20s
- Build/publish: 5-15s
- Application start: 2-5s
Subsequent Deployments (~10-20 seconds):
- Git pull: 2-3s
- .NET check:
<1s(already installed) - NuGet restore:
<1s(cached if packages.lock.json unchanged) - Build/publish: 5-10s
- Application restart: 2-3s
Example Repository Structure
my-dotnet-api/
├── MyApi.sln # Solution file
├── MyApi.csproj # Project file with dependencies
├── packages.lock.json # Locked package versions (optional)
├── global.json # SDK version specification (optional)
├── nuget.config # NuGet feed configuration (optional)
├── pre-deploy.sh # Optional: Pre-deployment hook
├── post-deploy.sh # Optional: Post-deployment hook
├── Program.cs # Application entry point
├── appsettings.json # Application configuration
├── appsettings.Production.json
├── Controllers/
│ └── WeatherController.cs
├── Models/
│ └── WeatherForecast.cs
├── Services/
│ └── IWeatherService.cs
└── Data/
├── ApplicationDbContext.cs
└── Migrations/