Skip to content

[docs] Document default fsGroup for Kubernetes persistent volumes - #1502

Open
aspire-repo-bot[bot] wants to merge 1 commit into
release/13.5from
docs/pr-19374-31857468803-1-fb65f718bc5fd443
Open

[docs] Document default fsGroup for Kubernetes persistent volumes#1502
aspire-repo-bot[bot] wants to merge 1 commit into
release/13.5from
docs/pr-19374-31857468803-1-fb65f718bc5fd443

Conversation

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

Documents changes from microsoft/aspire#19374

@mitchdenny

Targeting release/13.5 — the latest release branch on microsoft/aspire.dev — because release/13.6 (from the source PR milestone 13.6) does not exist there.

Why

Workloads bound through either WithPersistentVolume(...) overload on Aspire.Hosting.Kubernetes now automatically receive a pod security context (fsGroup: 2000, fsGroupChangePolicy: OnRootMismatch) so first writes to a mounted volume don't fail with a permission error when the container runs as non-root. This is a new default behavior for a documented, first-class API (WithPersistentVolume), so the existing persistent-volumes page needed to describe it, including how to override or remove it via PublishAsKubernetesService.

What changed

  • src/frontend/src/content/docs/deployment/kubernetes/persistent-volumes.mdx: added a new "Default pod security context" section explaining the fsGroup/fsGroupChangePolicy default, why 2000 and OnRootMismatch are used, how to override the group or remove the security context entirely via a PublishAsKubernetesService callback, and caveats about storage-driver/CSI support and admission policies.

Page updated (no new pages created).

Generated by PR Documentation Check for #19374 · auto · 41.8 AIC · ⌖ 15.6 AIC · ⊞ 19.6K ·

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@aspire-repo-bot aspire-repo-bot Bot added the docs-from-code Copilot initiated issue from dotnet/aspire repo label Aug 15, 2026
@aspire-repo-bot

Copy link
Copy Markdown
Contributor Author

Frontend HTML artifact ready

The latest frontend build uploaded the frontend-dist artifact for PR #1502. Use the VS Code button below to open this PR with GitHub Artifacts Explorer and browse the built HTML locally.

VS Code: Open PR #1502 artifacts

This comment updates automatically when a new frontend build artifact is uploaded.

@IEvangelist
David Pine (IEvangelist) marked this pull request as ready for review August 15, 2026 02:15
Copilot AI lite review requested due to automatic review settings August 15, 2026 02:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Kubernetes persistent volumes documentation to describe the new default pod-level securityContext applied by WithPersistentVolume(...) so non-root workloads can write to newly provisioned volumes without hitting permission errors.

Changes:

  • Added a new “Default pod security context” section documenting the default fsGroup: 2000 and fsGroupChangePolicy: OnRootMismatch.
  • Documented how to override the default fsGroup or remove the generated pod security context via PublishAsKubernetesService.
  • Added cautions about CSI/storage driver support and cluster admission policy constraints.
Suppressed comments (1)

src/frontend/src/content/docs/deployment/kubernetes/persistent-volumes.mdx:239

  • This second example has the same issues as the previous one: it references an undefined data variable and uses resource.Workload?.PodTemplate.Spec. Consider casting to StatefulSet (since PV-bound workloads are promoted) and clearing Spec.Template.Spec.SecurityContext there, while reusing the volume variable from the preceding example (media).
builder.AddProject<Projects.WebFrontend>("webfrontend")
    .WithPersistentVolume(data, "/data")
    .PublishAsKubernetesService(resource =>
    {
        var podSpec = resource.Workload?.PodTemplate.Spec

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +212 to +221
builder.AddProject<Projects.WebFrontend>("webfrontend")
.WithPersistentVolume(data, "/data")
.PublishAsKubernetesService(resource =>
{
var podSpec = resource.Workload?.PodTemplate.Spec
?? throw new InvalidOperationException("The Kubernetes workload was not generated.");

podSpec.SecurityContext ??= new();
podSpec.SecurityContext.FsGroup = 3000;
});

@IEvangelist David Pine (IEvangelist) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated docs-accuracy review

Phase A source of truth: microsoft/aspire release/13.5 @ 8ab6999850d96e0023670799edbd4bdc245ad63c (branch matched to this PR's target, aspire.dev release/13.5). Version context read from microsoft/aspire main @ 002abcf250.

Claims extracted: 12 non-narrative (plus several narrative statements, not blocking).

Verdict Count Claims
✅ verified 6 overload count, PublishAsKubernetesService, Workload?.PodTemplate.Spec, SecurityContext, FsGroup, YAML aliases
⚠️ verified-with-nuance 0
❔ unverifiable 6 the automatic fsGroup: 2000 / OnRootMismatch default and everything that depends on it
❌ contradicted 0

Phase B (doc-tester): 1 route exercised — /deployment/kubernetes/persistent-volumes/ (#default-pod-security-context). Served from PR head e2d11bd2 via pnpm dev at http://localhost:4321/. 0 critical, 1 warning, plus knowledge gaps. Page renders cleanly (0 expressive-code errors, 0 console errors).

Verdict: COMMENT. No claim is contradicted and Phase B found no critical issues, but the headline behavior is unverifiable on the branch this PR targets (see the inline comment on the "…add the following pod security context…automatically" line), and there's a language-tab consistency warning.


Phase A — Claim verification

The six unverifiable claims all share one root cause: the automatic pod-security-context default is a 13.6 feature (source PR microsoft/aspire#19374, milestone 13.6) that is not present on release/13.5, which is the source-of-truth branch for this PR. See the inline comment for the evidence and the affected lines (L192, L196‑197, L200, L202, L206, L227).

verified claims (6) — the override/removal API surface is valid on release/13.5
# Claim Evidence (microsoft/aspire release/13.5)
C2 There are two WithPersistentVolume(...) overloads KubernetesPersistentVolumeExtensions.cs:253 (volume) and :293 (volume, mountPath, isReadOnly = false)
C7 PublishAsKubernetesService takes an Action<KubernetesResource> callback KubernetesServiceExtensions.cs:34
C8 resource.Workload?.PodTemplate.Spec resolves to PodSpecV1 KubernetesResource.Workload is Workload? (KubernetesResource.cs:60); Workload.PodTemplate is abstract PodTemplateSpecV1 (Workload.cs); PodTemplateSpecV1.Spec is PodSpecV1 (PodTemplateSpecV1.cs:33); same chain used by the publisher (KubernetesPublishingContext.cs:406)
C9 podSpec.SecurityContext is nullable get/set; ??= new() and = null are valid PodSpecV1.SecurityContext is PodSecurityContextV1? (PodSpecV1.cs:301); PodSecurityContextV1 is a sealed class with an implicit parameterless ctor
C10 podSpec.SecurityContext.FsGroup = 3000 is assignable PodSecurityContextV1.FsGroup is long? (PodSecurityContextV1.cs:52)
C12 FsGroup/FsGroupChangePolicy serialize to YAML fsGroup/fsGroupChangePolicy [YamlMember(Alias = "fsGroup")] (PodSecurityContextV1.cs:51), [YamlMember(Alias = "fsGroupChangePolicy")] (:116)
unverifiable claims (6) — absent on release/13.5, present on main (13.6)
# Claim Finding
C1 Both overloads add fsGroup: 2000 + fsGroupChangePolicy: OnRootMismatch automatically On release/13.5 both overloads add only annotations (KubernetesPersistentVolumeExtensions.cs:253,293); no PodSecurityContextV1 is constructed anywhere in src/Aspire.Hosting.Kubernetes; the literal 2000 is absent; OnRootMismatch appears only in a doc comment (PodSecurityContextV1.cs:114). Present on main: KubernetesResource.cs:24‑25.
C3 Aspire always uses the fixed group id 2000 No such constant on release/13.5. main: DefaultPersistentVolumeFsGroup = 2000.
C4 Default change policy is OnRootMismatch Not applied as a default on release/13.5. main: DefaultPersistentVolumeFsGroupChangePolicy = "OnRootMismatch".
C5 The default only applies to WithPersistentVolume-bound workloads; default-storage PVCs unaffected Depends on the default, which is absent on release/13.5.
C6 The default is applied before the PublishAsKubernetesService callback runs No default is applied on release/13.5, so the ordering can't be evidenced there.
C11 The generated OnRootMismatch policy is retained unless the callback replaces it Depends on the default, which is absent on release/13.5.

Narrative statements (general Kubernetes background — access mode vs. filesystem permissions, root:root ownership, non‑root first‑write failures, CSI/admission caveats) are noted but not blocking.


Phase B — Doc-tester results

Blind-user perspective — the rendered docs site was the only window into behavior; no source code was consulted for this phase.

Route exercised: /deployment/kubernetes/persistent-volumes/ → section #default-pod-security-context (the only route touched by this PR's diff). Served from PR head e2d11bd2 via pnpm dev (http://localhost:4321/), navigated with Playwright.

Summary

Category Passed Failed Warnings
Content accuracy (as presented) 1 0 0
Rendering (page/section/code/asides) 5 0 0
Code examples (readability) 2 0 0
Language-tab consistency 0 0 1
Links n/a 0 0

Critical issues

None.

Warnings

W1 — Language-tab inconsistency in the override examples.
Location: #default-pod-security-context → "Overriding or removing the default". The two <Tabs syncKey="aspire-lang"> groups in the new section expose only a C# tab, whereas the four other <Tabs> groups on this same page each offer C# and TypeScript. Because all groups share the aspire-lang sync key, a reader who selected TypeScript earlier on the page arrives at these examples and sees a C#-only block with no TypeScript equivalent and no explanation. Observed tab labels on the page: [C#, TypeScript] × 4, then [C#] × 2.
Suggestion: add a TypeScript tab, or a one-line note that the override/removal callback is C#-only.

Passed checks

  • Page loads (Persistent volumes on Kubernetes | Aspire); new ## Default pod security context H2 renders with a TOC entry and anchor link (#default-pod-security-context).
  • The securityContext YAML block renders correctly.
  • Both C# examples render with the AppHost.cs title and are readable/copyable; 0 expressive-code error markers.
  • The caution aside renders.
  • No browser console errors.

Knowledge gaps

  • KG1 — TypeScript support unclear. The page teaches in both C# and TypeScript, but the override/removal pattern is shown only in C#. From the docs alone a polyglot user can't tell whether TypeScript AppHosts can't do this or the example is simply missing. (Ties to W1.)
  • KG2 (minor, expected). A blind reader can't verify the runtime claims (the 2000/OnRootMismatch default and "applied before the callback runs") from the docs alone — they're taken on faith. Normal for behavior docs; noted for completeness. Phase A found these are not evidenced on the release/13.5 source.

Recommendations

  1. Resolve the branch/version mismatch surfaced in Phase A before this lands on the 13.5 docs (the documented default ships in 13.6, not 13.5).
  2. Add a TypeScript tab or an explicit C#-only note for the override/removal examples (W1 / KG1).


A Kubernetes access mode such as `ReadWriteOnce` controls how a volume can be attached and mounted; it doesn't grant the container's Linux process permission to write to the mounted filesystem. Without further configuration, a freshly provisioned volume is typically owned by `root:root`, so a workload that runs as a non-root user fails on its first write even though the PVC is `Bound` and the pod is `Running`.

To avoid that out-of-the-box failure, both `WithPersistentVolume(...)` overloads add the following pod security context to the workload automatically:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase A — unverifiable on the source-of-truth branch (version skew).

This PR targets aspire.dev release/13.5, so the matching source of truth is microsoft/aspire release/13.5 (@ 8ab6999850). On that branch the automatic fsGroup: 2000 / fsGroupChangePolicy: OnRootMismatch default does not exist:

  • Both WithPersistentVolume(...) overloads add only annotations — no security context (KubernetesPersistentVolumeExtensions.cs:253 and :293).
  • No PodSecurityContextV1 is ever constructed in src/Aspire.Hosting.Kubernetes, the literal 2000 appears nowhere there, and OnRootMismatch appears only in a doc comment (PodSecurityContextV1.cs:114).

The behavior is present on microsoft/aspire main (13.6): KubernetesResource.cs:24-25 define DefaultPersistentVolumeFsGroup = 2000 and DefaultPersistentVolumeFsGroupChangePolicy = "OnRootMismatch" — matching the source PR's 13.6 milestone (microsoft/aspire#19374).

So every claim about the automatic default — the 2000/OnRootMismatch values (L196‑197, L200), "this default only applies to WithPersistentVolume" (L202), "applied before any PublishAsKubernetesService callback runs" (L206), and "the generated OnRootMismatch change policy is retained" (L227) — can't be verified against release/13.5. As written, a 13.5 reader is told about a default that doesn't ship in 13.5. Please confirm the intended target branch (e.g. wait for a release/13.6 docs branch, or ensure the source change is present on 13.5) before this lands on the 13.5 docs.

Note: the override/removal API used below (PublishAsKubernetesService, resource.Workload?.PodTemplate.Spec, podSpec.SecurityContext, FsGroup) is valid on release/13.5 — see the ✅ list in the review summary.


The default security context is applied before any `PublishAsKubernetesService` callback runs, so a workload or cluster that needs a different group can replace it:

<Tabs syncKey="aspire-lang">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase B (doc-tester, blind to source) — warning + knowledge gap.

This <Tabs syncKey="aspire-lang"> (and the one at L231) exposes only a C# tab. The four other <Tabs> groups on this page each offer both C# and TypeScript. Because they share the aspire-lang sync key, a reader who selected TypeScript earlier on the page arrives here and sees a C#-only block with no TypeScript equivalent and no explanation.

Knowledge gap: the page doesn't state whether overriding/removing the pod security context is available to TypeScript AppHosts. Please add a TypeScript tab, or a one-line note that this callback is C#-only.


A Kubernetes access mode such as `ReadWriteOnce` controls how a volume can be attached and mounted; it doesn't grant the container's Linux process permission to write to the mounted filesystem. Without further configuration, a freshly provisioned volume is typically owned by `root:root`, so a workload that runs as a non-root user fails on its first write even though the PVC is `Bound` and the pod is `Running`.

To avoid that out-of-the-box failure, both `WithPersistentVolume(...)` overloads add the following pod security context to the workload automatically:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also update the generated StatefulSet excerpt below to include this securityContext? This section says every WithPersistentVolume(...) workload gets it automatically, but the documented output still omits it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-from-code Copilot initiated issue from dotnet/aspire repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants