Skip to main content

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

  1. Detection: Finds .csproj or .sln and identifies as .NET Core project
  2. Version Selection:
    • Checks DOTNET_VERSION environment variable
    • Checks global.json for SDK version
    • Checks TargetFramework in .csproj (net6.0, net7.0, net8.0, net9.0)
    • Falls back to .NET 8.0 LTS
  3. 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)
  4. Dependencies: Runs dotnet restore (uses --locked-mode if packages.lock.json exists)
  5. Build: Framework-specific (publish for web apps, build for console/worker)
  6. 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:

  1. Framework Detection: Identifies ASP.NET Core via Microsoft.AspNetCore or Microsoft.NET.Sdk.Web references
  2. Production Build: Runs dotnet publish -c Release -o ./publish
  3. URL Configuration: Sets ASPNETCORE_URLS=http://0.0.0.0:5000 for container access
  4. Environment Setup: Configures ASPNETCORE_ENVIRONMENT=Production
  5. 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:

  1. Detection: Identifies Blazor Server via ASP.NET Core detection
  2. Production Build: Runs dotnet publish -c Release -o ./publish
  3. 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:

  1. Detection: Identifies Blazor WASM via Microsoft.AspNetCore.Components.WebAssembly reference
  2. Production Build: Runs dotnet publish -c Release -o ./publish
  3. Static File Serving: Serves wwwroot directory using Node.js static server
  4. 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:

  1. Detection: Identifies Worker Service via Microsoft.Extensions.Hosting reference
  2. Build: Runs dotnet build -c Release (publish not needed for workers)
  3. Server Start: Uses dotnet run --no-build or dotnet ./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:

  1. Detection: Identifies gRPC via Grpc.AspNetCore reference
  2. Production Build: Runs dotnet publish -c Release -o ./publish
  3. 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:

  1. Detection: Default if no framework-specific references found
  2. Build: Runs dotnet build -c Release
  3. Server Start: Uses dotnet run --no-build or 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_GCHeapHardLimit environment 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.json unchanged
  • Force reinstall: Use FORCE_INSTALL=true environment 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

VariableDescriptionDefault
DOTNET_VERSION.NET SDK version (6.0-9.0)Auto-detected from global.json or .csproj
ASPNETCORE_ENVIRONMENTASP.NET Core environmentProduction
ASPNETCORE_URLSURL bindingshttp://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 push event

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:

  1. Use dependency caching - enable packages.lock.json:

    <PropertyGroup>
    <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
    </PropertyGroup>
  2. Remove unnecessary package references

  3. 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

  1. Use Dependency Caching: Enable packages.lock.json - saves 20-40s per deployment
  2. Tiered Compilation: Automatically enabled with DOTNET_TieredPGO=1
  3. ReadyToRun: Automatically enabled for faster startup
  4. Production Environment: Set ASPNETCORE_ENVIRONMENT=Production
  5. Health Checks: Implement /health endpoint for faster deployment verification
  6. Response Compression: Enable in ASP.NET Core for better performance
  7. Output Caching: Use .NET 7+ output caching for API responses
  8. First Deployment: Takes 30-60s (.NET installation), subsequent deploys are 10-20s

Deployment Timeline

First Deployment (~30-60 seconds):

  1. Git clone: 5-10s
  2. .NET SDK installation: 15-30s (one-time, persists in container)
  3. NuGet restore: 10-20s
  4. Build/publish: 5-15s
  5. Application start: 2-5s

Subsequent Deployments (~10-20 seconds):

  1. Git pull: 2-3s
  2. .NET check: <1s (already installed)
  3. NuGet restore: <1s (cached if packages.lock.json unchanged)
  4. Build/publish: 5-10s
  5. 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/

Next Steps