This is what dotnet new web gives you, unchanged. There is no port to read and no host to configure: the start command binds whatever port the platform hands the process, which is the one thing that would otherwise be different here.
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello from .NET on Frontback"); app.Run();
Run it locally first; nothing about it is Frontback specific:
$ dotnet new web -o hello && cd hello && dotnet run Now listening on: http://localhost:5000
Two things, once per machine. The CLI is a single self-contained binary with no runtime and no daemon behind it, and the login is a device code, so it works over SSH and inside a container just as well.
$ brew install frontback/tap/frontback $ frontback login open the URL it prints and enter QK7X-4M2P ✓ logged in as anna@acme.eu (organization acme)
$ brew install frontback/tap/frontback $ frontback login open the URL it prints and enter QK7X-4M2P ✓ logged in as anna@acme.eu (organization acme)
$ go install github.com/frontback/cli/cmd/frontback@latest $ frontback login open the URL it prints and enter QK7X-4M2P ✓ logged in as anna@acme.eu (organization acme)
The Go toolchain route works anywhere, whichever system you are on, and the binary is self-contained either way.
For CI, or anything else running unattended, use an API key instead of a device login: frontback login --with-token, or put FRONTBACK_TOKEN in the environment and store nothing at all. A key can be narrower than the person who made it.
frontback deploy finds your .csproj (or .fsproj, or the solution that holds them), restores in its own cached layer, publishes with dotnet publish -c Release and ships the output on the matching runtime image. No Dockerfile, no YAML; open the URL it prints and your service is answering.
$ frontback deploy created project hello-dotnet service hello-dotnet production > restoring hello.csproj (nuget cache) > publishing -c Release ✓ built (23.4s) ✓ base mcr.microsoft.com/dotnet/aspnet:10.0.10-noble ✓ v1 released to production ✓ live (1.1s) https://hello-dotnet.run.frontback.eu
Only the project files are copied before the restore, which is what makes that layer cacheable: a warm restore is seconds where a cold one is minutes. Every project file in the tree goes in, not just the entry one, so a ProjectReference to a sibling resolves. Paket repositories are handled the same way.
Connect the repository (GitHub App, Gitea, or a deploy key), bind it to your project and track a branch per environment. A push deploys only the services the commit changed, grouped into one release: staging first, production one promote away, rollback one more.
Framework-dependent publish is the default: the output carries your application and the runtime lives in the base image. That keeps the app layer a few megabytes instead of seventy, lets every .NET service on the platform share one runtime layer, and means a runtime CVE is fixed by rebasing rather than by rebuilding your image. The choice is mechanical, and the build plan says which one it made.
aspnet:<version>-noble. The full image, because binding the port the platform hands you needs a shell.runtime:<version>-noble-chiseled-extra. Distroless, non-root, half the size, with ICU and time zone data kept.runtime-deps:<version>-noble-chiseled-extra, with the -aot variant for AOT output. No .NET in the image at all, because your binary brought its own.The deploy above needed zero config. When you outgrow the defaults, frontback.json and your own project file are the whole surface:
"dotnet": "10"net10.0), then global.json, then .tool-versions, then the default. The target framework wins over global.json on purpose: it states which shared framework the output runs on, so it decides the runtime image, while global.json only decides which SDK compiles the tree.<PublishAot>true</PublishAot><InvariantGlobalization>true</InvariantGlobalization>"build" + "start"dist/ lands in the image, and start is shell form, so $PORT interpolates.A solution holding one runnable project resolves to it. Several runnable projects with one web project among them resolve to the web one, and the plan says so. Anything less clear cut is an error that names every candidate, because guessing which of two services to deploy is worse than asking. frontback pack explain prints the whole plan, image tags included, before anything runs.
Make the machine bigger, or run more copies of it. Both are settings on the service in the console, and both take effect through an ordinary release, so staging sees the change before production does and a rollback is one click.
A framework-dependent .NET service wants more headroom per instance than a compiled binary does, so the machine size is usually the first dial you reach for. Native AOT output moves that answer back down a size, which is the other reason to consider it.
No. <code>frontback deploy</code> claims the tree from a <code>.csproj</code>, <code>.fsproj</code>, <code>.vbproj</code> or a solution, restores, publishes and picks the runtime image itself. If you commit your own Dockerfile it wins, because a Dockerfile is an instruction rather than evidence.
.NET 10 by default, the current LTS, on runtime 10.0.10 built by SDK 10.0.302. .NET 9 and 8 are still offered while they are supported. Older majors are deliberately absent: a tree targeting them gets an error listing what is offered, because serving a runtime nobody patches is worse than serving none.
You do not have to answer that. The start command binds the port the platform hands the process, which is also why a web app gets the runtime image that has a shell. Setting <code>ASPNETCORE_URLS</code> yourself still works if you want the control.
If your app avoids runtime reflection, yes: it is the biggest cold start improvement available to a .NET service here. Publish it locally and run the produced binary first, and let the compiler help with <code><IsAotCompatible>true</IsAotCompatible></code>. Trimming removes what no call site names, so a reflection-heavy dependency links cleanly and fails when it runs, and nothing in a build can detect that for you.
Yes. F# projects build exactly like C# ones, including Paket repositories, with one caveat under AOT: printf-style formatting and code quotations go through reflection. A standalone Blazor WebAssembly project is deployed as a static site with the SPA fallback on, so no runtime image is built and no worker ever starts.
One runnable project is chosen and the choice is reported. With several, a single web project among them wins. Anything less clear cut is an error naming every candidate. The language does not enter into it, so a solution with both a .csproj and an .fsproj resolves the same way.
Yes. Postgres and Redis run as services inside your project, and for serverless we recommend the managed libsql database: it speaks the SQLite dialect over HTTP, so there is no connection pool to exhaust and nothing to keep alive between requests.
A framework-dependent service is comfortable on Shared 512 MB at €6 a month always-on, and Native AOT output fits Shared 256 MB at €4. Always-on starts on the Pro plan (€19 a month); the Free plan runs services on demand and stops them when idle.
On our own data servers in the EU. EU hosting is the default on every plan, not a paid add-on, which is usually the answer your compliance team is looking for.
Go beyond what seems possible.