Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. means ASP.NET Core created its data protection keys inside the container's own filesystem. Replace the container and the keys go with it, so every antiforgery token handed out before fails. Persist the keys to a volume.

Add AddDataProtection().PersistKeysToFileSystem(...) with a folder that is a Docker volume, set an application name, and run the container with -v <volume>:/keys.

The error

warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
      Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. For more information go to https://aka.ms/aspnet/dataprotectionwarning
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {2a1f7bc1-dd1f-41c6-97e0-868e91f36019} may be persisted to storage in unencrypted form.

These are the first lines of docker logs for a Blazor app in mcr.microsoft.com/dotnet/aspnet:10.0, which runs as root unless the Dockerfile says otherwise. With USER $APP_UID the path in the warning becomes /home/app/.aspnet/DataProtection-Keys.

Why it happens

Without configuration, data protection keeps its key ring in the user profile, here a folder in the container's writable layer. The app then encrypts its antiforgery cookie and the hidden form token with that key. To see the consequence, the test fetched a page with a form, saved the cookie and token with curl, removed the container, started a new one from the same image and posted the saved values:

HTTP/1.1 400 Bad Request
Content-Length: 0

An empty 400, and nothing in the log at the default levels. A small diagnostic middleware placed after app.UseAntiforgery(); showed the reason:

app.Use(async (context, next) =>
{
    var antiforgery = context.Features.Get<IAntiforgeryValidationFeature>();
    if (antiforgery is { IsValid: false })
        app.Logger.LogWarning(antiforgery.Error, "Antiforgery validation failed");
    await next();
});
      Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery token could not be decrypted.
       ---> System.Security.Cryptography.CryptographicException: The key {94dcf5cf-02ee-4254-83fd-3585e3c00bb3} was not found in the key ring. For more information go to https://aka.ms/aspnet/dataprotectionwarning

The new container generated a new key; the token was protected with the old one. A user who opened the form before a redeploy and submits it afterwards gets exactly this.

The fix

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("/keys"))
    .SetApplicationName("FormLab");
docker run -d --name fix6-dp -p 127.0.0.1:15480:8080 -v fix6-dpkeys:/keys fix6-formlab:2

The same experiment, recreating the container between the GET and the POST, now returned HTTP/1.1 200 OK and "Booked for Maria Garcia.", and the second container logged no warning because the key was already in the volume. The container, image and volume names are the test's own. SetApplicationName fixes the name the keys are isolated by; without it the name comes from the content root path, and the documentation asks for a value that matches across deployments.

Two notes. The No XML encryptor configured warning still appears when the first key is created: the key file sits in the volume as plain XML, so protect the volume or add one of the ProtectKeysWith... options. And if the image runs as USER $APP_UID, a fresh named volume at /keys is owned by root; the app failed with Access to the path '/keys/...tmp' is denied and the form page returned 500. Creating the folder in the image with RUN mkdir /keys && chown $APP_UID /keys before the USER line fixed it: a new named volume starts from the image's folder, and the key file it then held belonged to uid 1654.

How it was reproduced

A Blazor Web App from dotnet new blazor -n FormLab -int None on .NET SDK 10.0.402, with one page holding an EditForm, published with dotnet publish -c Release and copied into mcr.microsoft.com/dotnet/aspnet:10.0 (ASP.NET Core 10.0.13) by a four-line Dockerfile, on Docker Engine 29.9.2. The container was published on 127.0.0.1:15480 only; every container was removed with docker rm -f -v after the test.

Frequently asked

How do I persist data protection keys in Docker for ASP.NET Core?
Call builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo("/keys")) with SetApplicationName, and run the container with a volume mounted at /keys, for example docker run -v myapp-keys:/keys. The keys then survive container replacement.
Why do I get antiforgery token could not be decrypted after a redeploy?
The new container created a new data protection key, and the token was encrypted with the old one that disappeared with the old container. Persist the keys to a volume so every container uses the same key ring.
Is the Storing keys in a directory warning safe to ignore?
Only if nothing protected with the keys has to outlive a container. Antiforgery tokens do: in the test, a form fetched before the container was replaced was rejected with a 400 when it was posted afterwards.

More decoded errors in the Fixes category. For building the image itself, see Building the .NET Image: Dockerfile or dotnet publish.