Skip to content

Update single-file docs #34445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 10, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 37 additions & 42 deletions docs/core/deploying/single-file/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ ms.date: 06/21/2022
ms.custom: kr2b-contr-experiment
---

# Single-file deployment and executable
# Single-file deployment

Bundling all application-dependent files into a single binary provides an application developer with the attractive option to deploy and distribute the application as a single file. Single-file deployment is available for both the [framework-dependent deployment model](../index.md#publish-framework-dependent) and [self-contained applications](../index.md#publish-self-contained).

This deployment model has been available since .NET Core 3.0 and has been enhanced in .NET 5. Previously in .NET Core 3.0, when a user runs your single file app, .NET Core host first extracts all files to a directory before running the application. .NET 5 improves this experience by directly running the code without the need to extract the files from the app.

The size of the single file in a self-contained application is large since it includes the runtime and the framework libraries. In .NET 6, you can [publish trimmed](../trimming/trim-self-contained.md) to reduce the total size of trim-compatible applications. The single file deployment option can be combined with [ReadyToRun](../ready-to-run.md) and [Trim](../trimming/trim-self-contained.md) publish options.

Single file deployment isn't compatible with Windows 7.
Expand All @@ -30,7 +28,6 @@ Here's a sample project file that specifies single file publishing:
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>

</Project>
Expand All @@ -41,7 +38,6 @@ These properties have the following functions:
- `PublishSingleFile`. Enables single file publishing. Also enables single file warnings during `dotnet build`.
- `SelfContained`. Determines whether the app is self-contained or framework-dependent.
- `RuntimeIdentifier`. Specifies the [OS and CPU type](../../rid-catalog.md) you're targeting. Also sets `<SelfContained>true</SelfContained>` by default.
- `PublishReadyToRun`. Enables [ahead-of-time (AOT) compilation](../ready-to-run.md).

Single file apps are always OS and architecture specific. You need to publish for each configuration, such as Linux x64, Linux Arm64, Windows x64, and so forth.

Expand Down Expand Up @@ -158,13 +154,33 @@ Single file applications have all related PDB files alongside the application, n

Managed C++ components aren't well suited for single file deployment. We recommend that you write applications in C# or another non-managed C++ language to be single file compatible.

### Output differences from .NET 3.x
### Native libraries

In .NET Core 3.x, publishing as a single file produced one file, consisting of the app itself, dependencies, and any other files in the folder during publish. When the app starts, the single file app was extracted to a folder and run from there.
Only managed DLLs are bundled with the app into a single executable. When the app starts, the managed DLLs are extracted and loaded in memory, avoiding the extraction to a folder. With this approach, the managed binaries are embedded in the single file bundle, but the native binaries of the core runtime itself are separate files.

Starting with .NET 5, only managed DLLs are bundled with the app into a single executable. When the app starts, the managed DLLs are extracted and loaded in memory, avoiding the extraction to a folder. On Windows, this approach means that the managed binaries are embedded in the single file bundle, but the native binaries of the core runtime itself are separate files.
To embed those files for extraction and get one output file, set the property `IncludeNativeLibrariesForSelfExtract` to `true`.

To embed those files for extraction and get one output file, like in .NET Core 3.x, set the property `IncludeNativeLibrariesForSelfExtract` to `true`. For more information about extraction, see [Including native libraries](#include-native-libraries).
Specifying `IncludeAllContentForSelfExtract` extracts all files, including the managed assemblies, before running the executable. This may be helpful for rare application compatibility problems.

> [!IMPORTANT]
> If extraction is used, the files are extracted to disk before the app starts:
>
> - If the `DOTNET_BUNDLE_EXTRACT_BASE_DIR` environment variable is set to a path, the files are extracted to a directory under that path.
> - Otherwise, if running on Linux or macOS, the files are extracted to a directory under `$HOME/.net`.
> - If running on Windows, the files are extracted to a directory under `%TEMP%/.net`.
>
> To prevent tampering, these directories shouldn't be writable by users or services with different privileges. Don't use _/tmp_ or _/var/tmp_ on most Linux and macOS systems.

> [!NOTE]
> In some Linux environments, such as under `systemd`, the default extraction doesn't work because `$HOME` isn't defined. In such cases, it's recommended that you set `$DOTNET_BUNDLE_EXTRACT_BASE_DIR` explicitly.
>
> For `systemd`, a good alternative is to define `DOTNET_BUNDLE_EXTRACT_BASE_DIR` in your service's unit file as `%h/.net`, which `systemd` expands correctly to `$HOME/.net` for the account running the service.
>
> ```text
> [Service]
> Environment="DOTNET_BUNDLE_EXTRACT_BASE_DIR=%h/.net"
> ```
>

### API incompatibility

Expand Down Expand Up @@ -193,48 +209,27 @@ We have some recommendations for fixing common scenarios:

- To avoid shipping loose files entirely, consider using [embedded resources](../../extensions/create-resource-files.md).

### Attach a debugger

On Linux, the only debugger that can attach to self-contained single file processes or debug crash dumps is [SOS with LLDB](../../diagnostics/dotnet-sos.md).

On Windows and Mac, Visual Studio and VS Code can be used to debug crash dumps. Attaching to a running self-contained single file executable requires an extra file: _mscordbi.{dll,so}_.
### Post-processing binaries before bundling

Without this file, Visual Studio might produce the error: "Unable to attach to the process. A debug component is not installed." VS Code might produce the error: "Failed to attach to process: Unknown Error: 0x80131c3c."
Some workflows require post-processing of binaries before bundling. A common example is signing. The dotnet SDK provides MSBuild extension points to allow processing binaries just before single-file bundling. The available APIs are:

To fix these errors, _mscordbi_ needs to be copied next to the executable. _mscordbi_ is `publish`ed by default in the subdirectory with the application's runtime ID. So, for example, if you publish a self-contained single file executable using the `dotnet` CLI for Windows using the parameters `-r win-x64`, the executable would be placed in _bin/Debug/net5.0/win-x64/publish_. A copy of _mscordbi.dll_ would be present in _bin/Debug/net5.0/win-x64_.
- A target `PrepareForBundle` that will be called before `GenerateSingleFileBundle`
- An `<ItemGroup><FilesToBundle /></ItemGroup>` containing all files that will be bundled
- A Property `AppHostFile` that will specify the apphost template. Post-processing might want to exclude the apphost from processing.

### Include native libraries
To plug into this involves creating a target that will be executed between `PrepareForBundle` and `GenerateSingleFileBundle`.

Single file deployment doesn't bundle native libraries by default. On Linux, the runtime is prelinked into the bundle and only application native libraries are deployed to the same directory as the single file app. On Windows, only the hosting code is prelinked and both the runtime and application native libraries are deployed to the same directory as the single file app. This approach is to ensure a good debugging experience, which requires native files to be excluded from the single file.
Consider the following .NET project `Target` node example:

Starting with .NET 6, the runtime is prelinked into the bundle on all platforms.

You can set a flag, `IncludeNativeLibrariesForSelfExtract`, to include native libraries in the single file bundle. These files are extracted to a directory in the client machine when the single file application is run.

Specifying `IncludeAllContentForSelfExtract` extracts all files, including the managed assemblies, before running the executable. This approach preserves the original .NET Core single file deployment behavior.

> [!NOTE]
> If extraction is used, the files are extracted to disk before the app starts:
>
> - If environment variable `DOTNET_BUNDLE_EXTRACT_BASE_DIR` is set to a path, the files are extracted to a directory under that path.
> - Otherwise, if running on Linux or MacOS, the files are extracted to a directory under `$HOME/.net`.
> - If running on Windows, the files are extracted to a directory under `%TEMP%/.net`.
>
> To prevent tampering, these directories should not be writable by users or services with different privileges. Don't use _/tmp_ or _/var/tmp_ on most Linux and MacOS systems.
```xml
<Target Name="MySignedBundledFile" BeforeTargets="GenerateSingleFileBundle" DependsOnTargets="PrepareForBundle">
```

> [!NOTE]
> In some Linux environments, such as under `systemd`, the default extraction does not work because `$HOME` is not defined. In such cases, we recommend that you set `$DOTNET_BUNDLE_EXTRACT_BASE_DIR` explicitly.
>
> For `systemd`, a good alternative seems to be defining `DOTNET_BUNDLE_EXTRACT_BASE_DIR` in your service's unit file as `%h/.net`, which `systemd` expands correctly to `$HOME/.net` for the account running the service.
>
> ```text
> [Service]
> Environment="DOTNET_BUNDLE_EXTRACT_BASE_DIR=%h/.net"
> ```
It's possible that tooling will need to copy files in the process of signing. That could happen if the original file is a shared item not owned by the build, for example, the file comes from a NuGet cache. In such a case, it's expected that the tool will modify the path of the corresponding `FilesToBundle` item to point to the modified copy.

### Compress assemblies in single-file apps

Starting with .NET 6, single file apps can be created with compression enabled on the embedded assemblies. Set the `EnableCompressionInSingleFile` property to `true`. The single file that's produced will have all of the embedded assemblies compressed, which can significantly reduce the size of the executable.
Single-file apps can be created with compression enabled on the embedded assemblies. Set the `EnableCompressionInSingleFile` property to `true`. The single file that's produced will have all of the embedded assemblies compressed, which can significantly reduce the size of the executable.

Compression comes with a performance cost. On application start, the assemblies must be decompressed into memory, which takes some time. We recommend that you measure both the size change and startup cost of enabling compression before using it. The impact can vary significantly between different applications.

Expand Down