TX Text Control .NET Server

Dockerfile

Topic

A container image for an application with TX Text Control .NET Server is built in two stages: the .NET SDK image restores the packages from the Text Control Private NuGet Feed and publishes the application for linux-x64, and the ASP.NET Core runtime image receives only the published files. The engine, its native libraries, its fonts and the Document Editor backend travel inside the publish output, so the image needs no further installation.

What follows builds such an image for an ASP.NET Core application with document processing, the Document Editor and the Document Viewer, keeps the feed credentials out of every image layer and shows which base images work.

Prerequisites

  • Docker with BuildKit, the builder behind docker buildx build. On Windows, Docker Desktop must run Linux containers.
  • An ASP.NET Core application that uses the Core engine, for example the application of Document Editor: ASP.NET Core. The examples use a .NET 10 project MyApp in the folder MyApp.
  • Your nuget.config with the credentials of the Text Control Private NuGet Feed. nuget.config describes the file.
  • Runtime licenses. Each running container instance needs one. See Runtime Licenses.

Choose the Base Image

The final stage of the Dockerfile starts from one of Microsoft's ASP.NET Core runtime images. For .NET 10, the images are based on Ubuntu 24.04:

ImageTX Text Control
mcr.microsoft.com/dotnet/aspnet:10.0Works without changes. Document processing, the Document Editor and the Document Viewer run in it.
mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extraWorks after you copy the gconv modules into the image. The image contains ICU.
mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseledNeeds the gconv modules as well, and runs .NET in globalization-invariant mode, in which the Document Editor backend logs culture errors. Use the -extra variant instead.
mcr.microsoft.com/dotnet/aspnet:10.0-alpineDoesn't work. Alpine Linux uses musl instead of the GNU C Library, and the application process ends when it creates a document.

For a .NET 8 application, use the 8.0 images, for example mcr.microsoft.com/dotnet/aspnet:8.0. The build stage uses the SDK image of the same .NET version as the runtime image. Linux Requirements explains the libraries behind these results.

Write the Dockerfile

The build context is the folder that contains the project folder:

Dockerfile
.dockerignore
nuget.config
MyApp/
    MyApp.csproj
    Program.cs
    ...

Save the following Dockerfile next to the project folder:

# syntax=docker/dockerfile:1

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# Restore the packages with nuget.config, passed as a build secret
COPY MyApp/MyApp.csproj MyApp/
RUN --mount=type=secret,id=nugetconfig \
    dotnet restore MyApp/MyApp.csproj -r linux-x64 --configfile /run/secrets/nugetconfig

# Publish for Linux without restoring again
COPY MyApp/ MyApp/
RUN dotnet publish MyApp/MyApp.csproj -c Release -r linux-x64 --self-contained false \
    --no-restore -o /app/publish /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
USER $APP_UID
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]

The Dockerfile works as follows:

  • # syntax=docker/dockerfile:1 selects the Dockerfile syntax that supports secret mounts.
  • The build stage copies the project file first and restores the packages. As long as the project file doesn't change, Docker reuses the restored layer.
  • RUN --mount=type=secret,id=nugetconfig makes nuget.config available at /run/secrets/nugetconfig for this one command. The file isn't written to any layer, so the API token of the feed stays out of the build cache and the image. --configfile makes dotnet restore use only this file, which lists both nuget.org and the Text Control feed.
  • -r linux-x64 in the restore is required, because dotnet publish with --no-restore doesn't restore again. Without it, the publish ends with error NETSDK1047. Publishing with --no-restore means that the second dotnet command doesn't need the credentials.
  • dotnet publish puts the application, the TX Text Control assemblies, the native libraries such as tx34core.so, the Fonts folder and the files of the Document Editor backend into /app/publish. /p:UseAppHost=false leaves out the executable, which isn't needed with dotnet MyApp.dll.
  • The final stage starts again from the runtime image and copies only the publish output. USER $APP_UID runs the application as the non-root user app, which the .NET images define. ASP.NET Core in these images listens on port 8080.
  • ENTRYPOINT starts the application with dotnet. The hosted service DocumentEditorWorkerManager starts the Document Editor backend in the same container with the dotnet command, so nothing else needs to run.

Keep nuget.config Out of the Image

Add a .dockerignore file next to the Dockerfile, so that COPY never takes nuget.config or local build output into the build context:

nuget.config
**/bin
**/obj

Keep nuget.config out of the project folder as well. A web project copies .config files from its folder into the build and publish output, so a nuget.config next to MyApp.csproj would end up in /app/publish and in the final image, together with its API token.

On a build server, the pipeline writes nuget.config from secrets before the build. CI/CD Authentication shows this step for GitHub Actions, GitLab, Azure DevOps and other systems. Pass the generated file to docker buildx build with --secret.

Build and Run the Image

Build the image in the folder with the Dockerfile and pass nuget.config as the secret:

docker buildx build --secret id=nugetconfig,src=nuget.config -t myapp .

Run a container and map port 8080 of the container to port 8080 of your machine:

docker run --rm -p 8080:8080 myapp

The container output shows the Document Editor backend starting before the application listens on port 8080:

info: TXTextControl.Web.DocumentEditor.Backend.DocumentEditorWorkerManager[0]
      Started TXTextControl.Web.Server.Core with Process ID: 23
info: TXTextControl.Web.DocumentEditor.Backend.DocumentEditorWorkerManager[0]
      [TXTextControl.Web.Server.Core] Successfully read config file 'TXTextControl.Web.Server.Core.config.json'.
info: TXTextControl.Web.DocumentEditor.Backend.DocumentEditorWorkerManager[0]
      [TXTextControl.Web.Server.Core] Starting TCP listener on port 4283 for IPv4 and IPv6.
info: TXTextControl.Web.DocumentEditor.Backend.DocumentEditorWorkerManager[0]
      [TXTextControl.Web.Server.Core] Logging initialized. LogLevel=Info, LogTarget=Console
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:8080

Open http://localhost:8080 in a browser. The Document Editor loads, and each editor that connects adds the line "Starting web editor worker process with connection ID" to the output. Port 4283 of the backend is used only inside the container, so don't publish it with -p.

Use a Chiseled Image

Chiseled images contain only the files .NET needs, have no shell or package manager and run as a non-root user by default. They leave out the gconv modules of the GNU C Library, which TX Text Control uses to convert character sets. Without them, the application process ends as soon as it creates a PDF document.

Copy the modules from the regular ASP.NET Core image of the same Ubuntu release. Replace the final stage of the Dockerfile with these stages:

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS gconv

FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra AS final
WORKDIR /app

# TX Text Control needs the gconv modules, which chiseled images leave out
COPY --from=gconv /usr/lib/x86_64-linux-gnu/gconv /usr/lib/x86_64-linux-gnu/gconv

COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]

The -extra variant contains ICU; System Files in Linux Requirements explains why the plain 10.0-noble-chiseled image isn't recommended. The blog article Using TX Text Control with Ultra-Minimal Chiseled Linux Containers shows the same approach for the .NET 8 image 8.0-jammy-chiseled.

Create the Dockerfile With Visual Studio

A project created in Visual Studio with Enable container support gets a Dockerfile with a base, a build, a publish and a final stage, and the Container (Dockerfile) startup profile runs it in a Linux container on your machine without changes. Development Environments describes these options, and "Run the Application in a Linux Container" in Document Editor: ASP.NET Core shows the steps for the application of the Getting Started tutorial.

The blog article Document Editor with ASP.NET Core and Docker Support with Linux Containers using Visual Studio 2026 and .NET 10 copies the backend files by hand, as articles for 33.0 did; the TXTextControl.Web.DocumentEditor.Backend package replaces that step. The generated file restores the packages without credentials for the private feed; add the secret mount and the publish options shown above before you build it for production.

Next Steps