> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the docs for Sevalla, a platform for deploying applications, databases, static sites, object storage and load balancers. The dashboard is at https://app.sevalla.com.
> Bold text in steps is an exact dashboard label.
> To act on a Sevalla account, prefer the Sevalla MCP server (https://mcp.sevalla.com/mcp, see https://docs.sevalla.com/ai-agents/overview.md), then the Sevalla CLI (https://docs.sevalla.com/developer-tools/cli.md), then the REST API (https://api-docs.sevalla.com, OpenAPI spec at https://api.sevalla.com/v3/openapi.json).
> Every page index is at https://docs.sevalla.com/llms.txt.

# Deploy .NET applications

> How .NET applications build and run on Sevalla with Ingot: project detection, .NET versions, the PORT variable, proxy headers, migrations and Dockerfiles.

A .NET application runs on Sevalla as an [application](https://docs.sevalla.com/applications/overview.md): Sevalla builds an image from your Git repository with the .NET SDK, then runs the published assemblies as processes. The web process serves HTTP with ASP.NET Core's Kestrel server, background workers run hosted services or queue consumers, and jobs run Entity Framework Core migrations before each deployment. C#, F# and Visual Basic projects all build the same way.

## Framework guides

> **[ASP.NET Core](https://docs.sevalla.com/guides/dotnet/aspnet-core.md)**
>
> Deploy a minimal API on .NET 10 with PostgreSQL, an EF Core migrations bundle and a health check.

> **[Blazor](https://docs.sevalla.com/guides/dotnet/blazor.md)**
>
> Deploy a Blazor Web App with interactive server rendering and Data Protection keys in PostgreSQL.

## How .NET applications run on Sevalla

| Part | On Sevalla |
| - | - |
| Build | [Ingot](https://docs.sevalla.com/applications/builds/ingot.md), Sevalla's builder, by default. It recognizes a .NET application by a project file (`.csproj`, `.fsproj` or `.vbproj`) at the root of the build path, a solution file there that lists one, or projects in `src/<name>/`. With several projects, it builds the one whose assembly the start command runs, otherwise the only web project; test projects don't count. |
| Package manager | NuGet. Ingot restores from the project files alone, so the restore layer is reused while only code changes, and adds `--locked-mode` when the project has a `packages.lock.json`. A project with a lock file and Razor components restores from the whole source instead: the SDK adds the package of Blazor's scripts, which the lock file lists, only when it sees `.razor` files. |
| .NET version | The `TargetFramework` of the project, such as `net10.0`, or .NET 10, Ingot's default line, when it can't read one. See [Pin the .NET version](#pin-the-net-version). |
| Publish | `dotnet publish -c Release -p:UseAppHost=false`: portable assemblies that the image runs with `dotnet`. Ingot turns off `PublishAot`, `PublishReadyToRun`, `PublishSingleFile`, `PublishTrimmed` and `SelfContained` and says so in the build log. To publish otherwise, set `build` in `ingot.json` to a command that publishes to `/app/publish`. |
| Images | The .NET SDK image (`mcr.microsoft.com/dotnet/sdk:10.0-noble`) builds; the ASP.NET Core image (`aspnet:10.0-noble`) runs a web project, and the smaller .NET runtime image (`runtime:10.0-noble`) runs anything else, such as a worker service. Both include ICU and time zone data, so cultures and time zones work. |
| Start command | `dotnet /app/<AssemblyName>.dll`, shown in the build plan's **Start** row. To run something else, add a `web` entry to a `Procfile`, set `start` in `ingot.json`, or set the web process's **Custom start command** on the **Processes** tab. See [Set the start command](https://docs.sevalla.com/applications/builds/ingot.md#set-the-start-command). |
| Port | Ingot points `ASPNETCORE_HTTP_PORTS` at the `PORT` environment variable when the application starts, so Kestrel listens on all interfaces on the web process's port, `8080` by default. `urls`, `http_ports`, `https_ports` or `Kestrel:Endpoints` in `appsettings.json` override it, and the build log warns about them. |
| Proxy headers | Ingot sets `ASPNETCORE_FORWARDEDHEADERS_ENABLED=true`, so ASP.NET Core takes the scheme and the client's address from the `X-Forwarded-Proto` and `X-Forwarded-For` headers of Sevalla's proxy: `Request.Scheme` is `https`, and HTTPS redirection and HSTS work. |
| Environment | `Production`, because `ASPNETCORE_ENVIRONMENT` isn't set. Every environment variable is also in the application's configuration; `ConnectionStrings__Default` sets `ConnectionStrings:Default`. |
| Concurrency | Kestrel serves requests on the .NET thread pool, and .NET sizes its garbage collector and thread pool to the instance's memory and CPU, so there's no worker count to set. |
| Migrations | A job with the start policy **Before deployment** that runs an [EF Core migrations bundle](https://docs.sevalla.com/guides/dotnet/aspnet-core.md#prepare-the-project-for-sevalla), for example `/app/efbundle`. If it fails, the deployment fails and the previous version keeps running. Ingot never runs migrations itself. |
| Background work | A background worker process that runs a worker service or a queue consumer from the same image, with its own start command. |
| Scheduled tasks | A cron job whose start command runs a console entry point of the application. |
| Databases | PostgreSQL, MySQL, MariaDB, Redis or Valkey on Sevalla, reached over an [internal connection](https://docs.sevalla.com/databases/connect.md#connect-an-application) that adds the connection details as environment variables. Npgsql and the MySQL providers take connection strings of keys and values, so build them from `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME` and `DB_PASSWORD` rather than `DB_URL`. |
| Files | The filesystem resets on every deployment, and that includes ASP.NET Core's Data Protection keys: [keep them in the database](https://docs.sevalla.com/guides/dotnet/blazor.md#keep-the-data-protection-keys-in-postgresql) when the application uses antiforgery tokens or cookies. Keep uploads in [object storage](https://docs.sevalla.com/object-storage/overview.md). |
| Other build strategies | A [Dockerfile](https://docs.sevalla.com/applications/builds/dockerfile.md), or another [build strategy](https://docs.sevalla.com/applications/builds/overview.md), on the application's **Settings** tab |

Every process (web, worker, cron job and job) runs from the same image, each with its own start command. See [Processes](https://docs.sevalla.com/applications/processes.md).

## Pin the .NET version

The project's `TargetFramework` is the version pin: a project that targets `net10.0` builds with the .NET 10 SDK and runs on the .NET 10 runtime, because .NET runs an application only on the major version it targets. Ingot reads the version from the first of these that's set:

1. The `INGOT_DOTNET_VERSION` environment variable, or `versions.dotnet` in `ingot.json`.
2. `sdk.version` in `global.json`, with its `rollForward` policy.
3. The newest `TargetFramework` (or `TargetFrameworks`) among the project and the projects it references.
4. .NET 10, Ingot's default line, when nothing is set.

Ingot builds .NET 10, 9 and 8. A framework it has no image for fails the build, for example `Storefront.csproj targets net11.0, which this Ingot release has no image for`. The SDK can be newer than the framework a project targets, never older, and the application always runs on the runtime of its `TargetFramework`. A line such as `10` builds on Microsoft's `10.0` images, which move to each patch release, so every new build picks up security fixes. The build plan's **.NET** row shows the version and where it came from.

You don't need a `global.json`. If you have one, Ingot applies it as the `dotnet` command does. Microsoft's SDK images carry the newest feature band of each line, such as the .NET 10 SDK 10.0.403. When the `rollForward` policy of `global.json` doesn't allow that SDK, Ingot downloads the newest SDK Microsoft still services that the policy allows, checks its SHA-512 checksum, and builds with it. With the default policy, this `global.json` allows the `10.0.1xx` SDKs:

```json global.json theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
{
  "sdk": {
    "version": "10.0.100"
  }
}
```

The build plan shows the SDK in its own row:

```text Build log theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
╭─ ■ ASP.NET Core · .NET 10 ────────────────────────────────────╮
│ .NET       10                          global.json "10.0.100" │
│ SDK        10.0.114                    global.json "10.0.100" │
│ Project    Storefront.csproj           the only project       │
│ Framework  ASP.NET Core                Storefront.csproj      │
│ Start      dotnet /app/Storefront.dll  Storefront.csproj      │
├─ Image ───────────────────────────────────────────────────────┤
│ Build      mcr.microsoft.com/dotnet/sdk:10.0-noble            │
│ Runtime    mcr.microsoft.com/dotnet/aspnet:10.0-noble         │
│ User       1000:1000                                          │
│ Init       tini                                               │
│ Port       $PORT, default 8080                                │
╰──────────────────────────────────────────────── Ingot v0.4.1 ─╯
```

When no SDK that Microsoft services matches, the build fails, for example with `global.json asks for SDK 10.0.200, and its rollForward policy doesn't allow 10.0.403 or 10.0.114, the .NET 10 SDKs Microsoft services`. Set `rollForward` to `latestFeature`, or `sdk.version` to one of the SDKs the message lists.

If `global.json` sets `sdk.paths`, include `"$host$"`, so the SDK in the image can be used. See [Set the language version](https://docs.sevalla.com/applications/builds/ingot.md#set-the-language-version).

## Build with a Dockerfile

A Dockerfile for a .NET application on Sevalla builds in two stages: it restores and publishes with Microsoft's SDK image, then copies only the published output into the ASP.NET Core runtime image of the same `10.0` line, which has the `/bin/sh` that start commands and the web terminal need. It runs as the image's numeric `app` user (`USER 1654`), sets `ASPNETCORE_FORWARDEDHEADERS_ENABLED=true`, and points `ASPNETCORE_HTTP_PORTS` at the `PORT` variable in its command before it hands process 1 to `dotnet`, which handles `SIGTERM` itself. The guides show it whole: [ASP.NET Core](https://docs.sevalla.com/guides/dotnet/aspnet-core.md#deploy-with-a-dockerfile) and [Blazor](https://docs.sevalla.com/guides/dotnet/blazor.md#deploy-with-a-dockerfile).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.