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.