Skip to content
AgileLogics
7 min read

Shipping F# to Production

Prerequisites: Testing with Expecto and xUnit

Publishing

dotnet publish produces everything needed to run your app:

dotnet publish -c Release -o out
dotnet publish -c Release -r linux-x64 --self-contained -o out-linux
dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true
  • Framework-dependent (the default): small output; the server needs the .NET runtime installed.
  • Self-contained (--self-contained): includes the runtime, so it runs on a machine with no .NET installed.
  • Single file: one executable to copy around.
  • ReadyToRun (-p:PublishReadyToRun=true) precompiles code for faster startup. Native AOT is available for .NET, but F# support is limited: reflection-heavy features such as %A formatting and some serializers don't work with it.

Docker

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish src/Shop.Api -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "Shop.Api.dll"]

A multi-stage build keeps the SDK out of the final image. The same image runs on any container host: Azure App Service, AWS, Kubernetes, or a plain Linux server.

Code quality

  • Fantomas — format everything the same way; check it in CI with dotnet fantomas --check ..
  • FSharpLint and Ionide F# analyzers — catch style issues and common mistakes.
  • <TreatWarningsAsErrors>true</TreatWarningsAsErrors> in the project — especially for incomplete pattern matches (FS0025).
  • <WarnOn>3390</WarnOn> checks XML doc comments; <Nullable>enable</Nullable> turns on nullness checking (F# 9+).

Configuration, logging, and errors

  • Read settings from environment variables and appsettings.json through ASP.NET Core's configuration system — never hard-code secrets.
  • Log with Microsoft.Extensions.Logging (or Serilog) using structured messages: logger.LogInformation("Order {OrderId} placed", id).
  • Convert Result errors to proper HTTP status codes at the edge, and add a global exception handler for the truly unexpected.

Security

  • Validate all external input by parsing it into domain types (smart constructors) — never trust JSON, query strings, or files.
  • Use parameterized queries (Dapper, Npgsql, or SqlClient with parameters) — never build SQL with string concatenation.
  • Keep packages updated: dotnet list package --vulnerable reports known vulnerabilities.
  • Be careful with type providers and scripts that fetch code or schemas from the network at build time.

Continuous integration

A typical pipeline, on GitHub Actions or any CI system:

dotnet tool restore
dotnet fantomas --check .
dotnet build -c Release
dotnet test -c Release --no-build
dotnet publish src/Shop.Api -c Release -o out

Where F# runs in production

  • Finance — trading, risk, and pricing systems at banks and hedge funds.
  • Energy and insurance — modeling and calculation engines.
  • Web services — APIs and back ends built with Giraffe or ASP.NET Core.
  • Developer tools — the F# compiler itself, Fantomas, and many analyzers are written in F#.

What's next

Try the Advanced Projects, then read the final lesson for a summary and ideas for where to go from here.