User Secrets in Docker-based .NET Core Worker Applications

As part of the recent Message Endpoints in Azure series, I wanted to check out the new .NET Core 3.0 Worker templates to see how the templates have improved the situation (actually, a lot), but there are still some things missing from the Worker SDK versus the Web SDK.

.NET Core Workers, introduced initially as background services in .NET Core 2.x, are now a top-level dotnet and Visual Studio template:

When you create the worker template, you're also given the option to enable Docker support:

In that series, I connect to my instance of Azure Service Bus in the cloud. However, I don't want to commit the connection string to source control, so I use User Secrets to store the connection string locally. Out-of-the-box, this SDK will include a user secrets ID in the project file, however, if you actually try to use the secrets when running, it won't work!

The first reason is because there was an issue in the Worker MSBuild task pipeline that looked for either the Web SDK or an explicit reference to the User Secrets NuGet package to decide to include the User Secrets in the configuration sources - but that's been fixed (workaround here).

However, it still doesn't work if you run a worker in a Docker container inside Visual Studio. The reason here is because when you run in a Docker container, the Visual Studio tooling does a lot of work to make running the container easier - including mapping a bunch of volumes. On that list is User Secrets - however - user secrets are only mapped for Web SDK!

Running our worker app inside a Docker container during development means we don't have any user secrets - and the solutions aren't great. Environment variables, Docker secrets, all are more annoying than just using the original secrets.

Mapping the volume

Instead of mucking around with custom solutions, ideally we can just map the user secrets volume ourselves. Luckily, there's an easy way to do so - we can specify custom Docker command line arguments:

<PropertyGroup>
  <TargetFramework>netcoreapp3.1</TargetFramework>
  <UserSecretsId>89595f67-a846-41e5-a74e-f876488ea8be</UserSecretsId>
  <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
  <DockerfileRunArguments>-v "$(AppData)/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro"</DockerfileRunArguments>
</PropertyGroup>

The DockerfileRunArguments element we use to pass through our additional volume mapping, which you can verify with a normal Web SDK running under Docker to see what it passes through.

I've opened a GitHub issue with the SDK tools, but in the meantime, we can use this workaround. With this in place, our Worker application can now use User Secrets whether it's running on our host or in a container.