[{"data":1,"prerenderedAt":1281},["ShallowReactive",2],{"blog-mount-local-storage-to-kubernetes-pods-k3s":3,"blog-mount-local-storage-to-kubernetes-pods-k3s-favor-of":1280},{"id":4,"title":5,"body":6,"cover_image":1264,"date":1265,"description":1266,"extension":1267,"meta":1268,"navigation":1269,"path":1270,"seo":1271,"stem":1272,"tags":1273,"type":1278,"__hash__":1279},"blog\u002Fblog\u002Fmount-local-storage-to-kubernetes-pods-k3s.md","Mount Local Storage to Kubernetes Pods: An Easy Tutorial Using K3s and the Local Path Provisioner",{"type":7,"value":8,"toc":1239},"minimark",[9,13,17,25,28,35,38,43,46,49,84,87,110,125,127,131,134,140,145,148,152,159,162,166,173,175,179,182,300,303,306,312,314,318,321,325,430,438,445,451,453,457,460,527,531,538,557,560,583,586,607,609,613,616,620,627,740,743,768,773,781,790,794,800,970,973,989,992,1006,1012,1018,1022,1025,1049,1052,1080,1083,1101,1104,1108,1119,1135,1138,1141,1168,1171,1173,1177,1180,1204,1206,1210,1235],[10,11,5],"h1",{"id":12},"mount-local-storage-to-kubernetes-pods-an-easy-tutorial-using-k3s-and-the-local-path-provisioner",[14,15,16],"p",{},"Persistent storage remains one of the first hurdles engineers hit when deploying stateful applications to Kubernetes. While stateless services restart cleanly anywhere across a cluster, databases, message brokers, and ML workloads require durable disks that survive pod restarts and rescheduling.",[14,18,19,20,24],{},"When developers first encounter storage on Kubernetes, they often find conflicting advice: enterprise blogs recommend multi-node distributed storage clusters like Ceph or Longhorn, while quickstart tutorials use raw ",[21,22,23],"code",{},"hostPath"," volumes. For single-node clusters, homelabs, and edge environments, both extremes cause problems.",[14,26,27],{},"This guide clarifies how Kubernetes storage abstractions operate, examines how distributions handle local storage out of the box, and shows how Rancher's Local Path Provisioner enables automated, high-performance NVMe storage for your workloads.",[14,29,30],{},[31,32],"img",{"alt":33,"src":34},"Storage Architecture Comparison","\u002Fimages\u002Fblog\u002Fstorage-options-matrix.png",[36,37],"hr",{},[39,40,42],"h2",{"id":41},"the-ephemeral-container-problem","The Ephemeral Container Problem",[14,44,45],{},"Kubernetes schedules Pods—groups of one or more co-located containers—onto worker nodes across a cluster. Containers provide lightweight, isolated process environments, but their file systems remain ephemeral by default. When a container crashes or restarts, Kubernetes recreates it from the base container image, discarding uncommitted runtime data.",[14,47,48],{},"In traditional Docker environments on a single physical host, engineers solve persistence by mounting a host directory via bind mounts:",[50,51,56],"pre",{"className":52,"code":53,"language":54,"meta":55,"style":55},"language-bash shiki shiki-themes github-light github-dark","docker run -d -v \u002Fvar\u002Fdata\u002Fpostgres:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata postgres:16\n","bash","",[21,57,58],{"__ignoreMap":55},[59,60,63,67,71,75,78,81],"span",{"class":61,"line":62},"line",1,[59,64,66],{"class":65},"sScJk","docker",[59,68,70],{"class":69},"sZZnC"," run",[59,72,74],{"class":73},"sj4cs"," -d",[59,76,77],{"class":73}," -v",[59,79,80],{"class":69}," \u002Fvar\u002Fdata\u002Fpostgres:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata",[59,82,83],{"class":69}," postgres:16\n",[14,85,86],{},"In a distributed Kubernetes cluster, direct host paths break down:",[88,89,90,98,104],"ol",{},[91,92,93,97],"li",{},[94,95,96],"strong",{},"Scheduling Blindness:"," The Kubernetes scheduler places pods dynamically based on CPU, memory, and affinity rules. If Kubernetes terminates a pod on Node A and restarts it on Node B, a hardcoded path on Node A remains unreachable from the new node.",[91,99,100,103],{},[94,101,102],{},"Resource Leaks:"," When you delete a workload, Kubernetes deletes the pod, but the files on the host disk stay behind indefinitely. Without manual cleanup, dead workloads slowly fill host disks.",[91,105,106,109],{},[94,107,108],{},"Security Risks:"," Hardcoding host filesystem paths in application manifests gives container processes direct access to node-level file systems, bypassing namespace isolation.",[14,111,112,113,116,117,120,121,124],{},"To solve these problems without tying manifests to physical hardware, Kubernetes introduces three layers of storage abstraction: ",[94,114,115],{},"PersistentVolumes",", ",[94,118,119],{},"PersistentVolumeClaims",", and ",[94,122,123],{},"StorageClasses",".",[36,126],{},[39,128,130],{"id":129},"the-kubernetes-storage-hierarchy","The Kubernetes Storage Hierarchy",[14,132,133],{},"Kubernetes separates storage management into distinct concerns for infrastructure administrators and application developers.",[14,135,136],{},[31,137],{"alt":138,"src":139},"Kubernetes Storage Pipeline","\u002Fimages\u002Fblog\u002Fk3s-storage-pipeline.png",[141,142,144],"h3",{"id":143},"_1-persistentvolume-pv","1. PersistentVolume (PV)",[14,146,147],{},"A PersistentVolume represents an actual piece of storage in the cluster—such as an allocated NVMe directory on a worker node, an NFS export, or an AWS EBS volume. An administrator can provision PVs manually, or a storage driver can generate them dynamically. A PV defines capacity, access modes, and technical connection details.",[141,149,151],{"id":150},"_2-persistentvolumeclaim-pvc","2. PersistentVolumeClaim (PVC)",[14,153,154,155,158],{},"A PersistentVolumeClaim is an application developer's request for storage. Instead of specifying host paths or disk serial numbers, a developer specifies capacity (such as ",[21,156,157],{},"10Gi","), access modes, and an optional StorageClass name.",[14,160,161],{},"When you submit a PVC, the Kubernetes control plane matches the request against available PVs. Once matched, it binds the PV to the PVC in a strict one-to-one relationship.",[141,163,165],{"id":164},"_3-storageclass","3. StorageClass",[14,167,168,169,172],{},"In production clusters, administrators do not manually create PersistentVolume manifests for every developer deployment. A ",[94,170,171],{},"StorageClass"," defines a dynamic provisioner plugin and parameters. When a developer submits a PVC referencing that StorageClass, the provisioner creates the backing storage volume and registers the matching PV automatically.",[36,174],{},[39,176,178],{"id":177},"the-homelab-storage-dilemma-distributed-san-vs-local-path","The Homelab Storage Dilemma: Distributed SAN vs. Local Path",[14,180,181],{},"When setting up storage for local clusters, engineers generally consider three architectures:",[183,184,185,211],"table",{},[186,187,188],"thead",{},[189,190,191,196,199,202,205,208],"tr",{},[192,193,195],"th",{"align":194},"left","Storage Model",[192,197,198],{"align":194},"Provisioning",[192,200,201],{"align":194},"Cleanup on Delete",[192,203,204],{"align":194},"Latency",[192,206,207],{"align":194},"Cluster Resource Overhead",[192,209,210],{"align":194},"Ideal Workload",[212,213,214,237,258,279],"tbody",{},[189,215,216,222,225,228,231,234],{},[217,218,219],"td",{"align":194},[94,220,221],{},"Raw hostPath",[217,223,224],{"align":194},"Manual YAML per volume",[217,226,227],{"align":194},"None (leaks on host disk)",[217,229,230],{"align":194},"Sub-millisecond",[217,232,233],{"align":194},"None",[217,235,236],{"align":194},"Host node agents (Fluentbit, node-exporter)",[189,238,239,244,247,250,252,255],{},[217,240,241],{"align":194},[94,242,243],{},"Local Path Provisioner",[217,245,246],{"align":194},"Fully automated via PVC",[217,248,249],{"align":194},"Automated helper cleanup",[217,251,230],{"align":194},[217,253,254],{"align":194},"Negligible (ephemeral helper pods)",[217,256,257],{"align":194},"Databases with app-level HA, single-node labs",[189,259,260,265,267,270,273,276],{},[217,261,262],{"align":194},[94,263,264],{},"Distributed (Longhorn\u002FCeph)",[217,266,246],{"align":194},[217,268,269],{"align":194},"Fully automated",[217,271,272],{"align":194},"Network sync delay",[217,274,275],{"align":194},"Heavy (15–30% CPU, gigabytes RAM)",[217,277,278],{"align":194},"Multi-node clusters requiring block failover",[189,280,281,286,289,291,294,297],{},[217,282,283],{"align":194},[94,284,285],{},"Cloud CSI (EBS\u002FPD)",[217,287,288],{"align":194},"Fully automated via API",[217,290,269],{"align":194},[217,292,293],{"align":194},"Network attached",[217,295,296],{"align":194},"Offloaded to cloud hypervisor",[217,298,299],{"align":194},"Public cloud managed Kubernetes",[14,301,302],{},"In enterprise data centers with 10GbE or 40GbE network fabrics, distributed storage systems like Rook-Ceph or Longhorn offer automatic replication and seamless volume failover between nodes.",[14,304,305],{},"In single-node homelabs, edge nodes, and developer workstations, running a distributed storage layer wastes significant resources. Systems like Longhorn replicate data across the network across the network, consuming 15% to 30% of cluster CPU capacity and gigabytes of RAM.",[14,307,308,309,311],{},"For high-throughput databases (such as PostgreSQL, Valkey, and ChromaDB), direct host NVMe storage provides the lowest latency and highest IOPS. Rancher's ",[94,310,243],{}," delivers that native performance while maintaining standard Kubernetes PVC workflows.",[36,313],{},[39,315,317],{"id":316},"how-the-local-path-provisioner-works-under-the-hood","How the Local Path Provisioner Works Under the Hood",[14,319,320],{},"The Local Path Provisioner runs as a lightweight controller pod inside the cluster. It watches for PVCs that request its StorageClass and manages host directories through a dedicated lifecycle.",[141,322,324],{"id":323},"the-lifecycle-flow","The Lifecycle Flow",[88,326,327,336,350,396,406,416],{},[91,328,329,332,333,124],{},[94,330,331],{},"PVC Submission:"," A developer applies a PVC manifest specifying ",[21,334,335],{},"storageClassName: local-path",[91,337,338,341,342,345,346,349],{},[94,339,340],{},"Scheduler Placement:"," Because the storage class uses ",[21,343,344],{},"volumeBindingMode: WaitForFirstConsumer",", the claim stays in ",[21,347,348],{},"Pending"," until a pod references it. Once you deploy the pod, the Kubernetes scheduler assigns the pod to an available worker node based on CPU and memory limits.",[91,351,352,355,356,359,360],{},[94,353,354],{},"Helper Pod Execution:"," The Local Path controller detects the target node and launches a temporary helper pod (",[21,357,358],{},"rancher\u002Flibrary-busybox",") on that specific machine. The helper pod executes:\n",[50,361,363],{"className":52,"code":362,"language":54,"meta":55,"style":55},"mkdir -m 0777 -p \u002Fvar\u002Flib\u002Francher\u002Fk3s\u002Fstorage\u002Fpvc-\u003Cuuid>\n",[21,364,365],{"__ignoreMap":55},[59,366,367,370,373,376,379,382,386,389,393],{"class":61,"line":62},[59,368,369],{"class":65},"mkdir",[59,371,372],{"class":73}," -m",[59,374,375],{"class":73}," 0777",[59,377,378],{"class":73}," -p",[59,380,381],{"class":69}," \u002Fvar\u002Flib\u002Francher\u002Fk3s\u002Fstorage\u002Fpvc-",[59,383,385],{"class":384},"szBVR","\u003C",[59,387,388],{"class":69},"uui",[59,390,392],{"class":391},"sVt8B","d",[59,394,395],{"class":384},">\n",[91,397,398,401,402,405],{},[94,399,400],{},"PV Generation and Binding:"," The provisioner registers a new PersistentVolume with ",[21,403,404],{},"nodeAffinity"," targeting the chosen node and binds it to the PVC.",[91,407,408,411,412,415],{},[94,409,410],{},"Direct Kubelet Bind-Mount:"," The node's kubelet bind-mounts the newly initialized host directory directly into the pod container at the target path (such as ",[21,413,414],{},"\u002Fdata",").",[91,417,418,421,422,425,426,429],{},[94,419,420],{},"Automated Teardown:"," When you delete the PVC, the provisioner observes the ",[21,423,424],{},"reclaimPolicy: Delete"," rule, launches a cleanup helper pod on the node, and runs ",[21,427,428],{},"rm -rf"," on the directory, ensuring no orphaned data remains.",[141,431,433,434,437],{"id":432},"why-waitforfirstconsumer-is-non-negotiable","Why ",[21,435,436],{},"WaitForFirstConsumer"," Is Non-Negotiable",[14,439,440,441,444],{},"Standard cloud block storage often uses ",[21,442,443],{},"volumeBindingMode: Immediate",", which allocates a disk before assigning a pod to a node.",[14,446,447,448,450],{},"For local node storage, immediate binding causes scheduling failures. If the controller allocates a directory on Node A before the scheduler runs, and the pod subsequently requires a GPU or memory capacity available only on Node B, the pod will remain unschedulable indefinitely. ",[21,449,436],{}," delays volume creation until the scheduler selects the target node.",[36,452],{},[39,454,456],{"id":455},"distribution-support-does-your-cluster-include-it","Distribution Support: Does Your Cluster Include It?",[14,458,459],{},"Support for local storage provisioners varies across Kubernetes distributions:",[461,462,463,481,493,503,512],"ul",{},[91,464,465,468,469,472,473,476,477,480],{},[94,466,467],{},"K3s:"," ",[94,470,471],{},"Included out of the box."," K3s deploys Rancher's Local Path Provisioner by default in the ",[21,474,475],{},"kube-system"," namespace and sets ",[21,478,479],{},"local-path"," as the cluster's default StorageClass.",[91,482,483,468,486,488,489,492],{},[94,484,485],{},"Kind (Kubernetes in Docker):",[94,487,471],{}," Kind bundles Rancher's local-path provisioner under the ",[21,490,491],{},"standard"," storage class.",[91,494,495,498,499,502],{},[94,496,497],{},"Minikube:"," Includes its own default ",[21,500,501],{},"storage-provisioner"," addon.",[91,504,505,508,509,124],{},[94,506,507],{},"MicroK8s:"," Requires running ",[21,510,511],{},"microk8s enable hostpath-storage",[91,513,514,468,517,520,521,524,525,124],{},[94,515,516],{},"Vanilla Kubernetes (kubeadm, Talos, RKE2, Bare-Metal):",[94,518,519],{},"Not included."," Fresh bare-metal clusters do not include a default StorageClass. Running ",[21,522,523],{},"kubectl get sc"," returns an empty list, and any deployed PVC hangs in ",[21,526,348],{},[141,528,530],{"id":529},"installing-local-path-provisioner-on-any-cluster","Installing Local Path Provisioner on Any Cluster",[14,532,533,534,537],{},"If you run bare-metal Kubernetes via ",[21,535,536],{},"kubeadm",", Talos, or RKE2, you can install the Rancher Local Path Provisioner with a single command:",[50,539,541],{"className":52,"code":540,"language":54,"meta":55,"style":55},"kubectl apply -f https:\u002F\u002Fraw.githubusercontent.com\u002Francher\u002Flocal-path-provisioner\u002Fv0.0.30\u002Fdeploy\u002Flocal-path-storage.yaml\n",[21,542,543],{"__ignoreMap":55},[59,544,545,548,551,554],{"class":61,"line":62},[59,546,547],{"class":65},"kubectl",[59,549,550],{"class":69}," apply",[59,552,553],{"class":73}," -f",[59,555,556],{"class":69}," https:\u002F\u002Fraw.githubusercontent.com\u002Francher\u002Flocal-path-provisioner\u002Fv0.0.30\u002Fdeploy\u002Flocal-path-storage.yaml\n",[14,558,559],{},"To configure it as your cluster's default storage class, apply the standard default annotation:",[50,561,563],{"className":52,"code":562,"language":54,"meta":55,"style":55},"kubectl patch storageclass local-path -p '{\"metadata\": {\"annotations\":{\"storageclass.kubernetes.io\u002Fis-default-class\":\"true\"}}}'\n",[21,564,565],{"__ignoreMap":55},[59,566,567,569,572,575,578,580],{"class":61,"line":62},[59,568,547],{"class":65},[59,570,571],{"class":69}," patch",[59,573,574],{"class":69}," storageclass",[59,576,577],{"class":69}," local-path",[59,579,378],{"class":73},[59,581,582],{"class":69}," '{\"metadata\": {\"annotations\":{\"storageclass.kubernetes.io\u002Fis-default-class\":\"true\"}}}'\n",[14,584,585],{},"Verify that the provisioner pod is running:",[50,587,589],{"className":52,"code":588,"language":54,"meta":55,"style":55},"kubectl -n local-path-storage get pods\n",[21,590,591],{"__ignoreMap":55},[59,592,593,595,598,601,604],{"class":61,"line":62},[59,594,547],{"class":65},[59,596,597],{"class":73}," -n",[59,599,600],{"class":69}," local-path-storage",[59,602,603],{"class":69}," get",[59,605,606],{"class":69}," pods\n",[36,608],{},[39,610,612],{"id":611},"hands-on-walkthrough-deploying-a-stateful-pod","Hands-On Walkthrough: Deploying a Stateful Pod",[14,614,615],{},"Let us deploy a stateful test application to examine volume creation, file persistence, and cleanup in practice.",[141,617,619],{"id":618},"step-1-create-a-persistentvolumeclaim","Step 1: Create a PersistentVolumeClaim",[14,621,622,623,626],{},"Create a manifest named ",[21,624,625],{},"pvc.yaml",":",[50,628,632],{"className":629,"code":630,"language":631,"meta":55,"style":55},"language-yaml shiki shiki-themes github-light github-dark","apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: app-data-pvc\nspec:\n  accessModes:\n    - ReadWriteOnce\n  storageClassName: local-path\n  resources:\n    requests:\n      storage: 5Gi\n","yaml",[21,633,634,646,657,666,677,685,693,702,713,721,729],{"__ignoreMap":55},[59,635,636,640,643],{"class":61,"line":62},[59,637,639],{"class":638},"s9eBZ","apiVersion",[59,641,642],{"class":391},": ",[59,644,645],{"class":69},"v1\n",[59,647,649,652,654],{"class":61,"line":648},2,[59,650,651],{"class":638},"kind",[59,653,642],{"class":391},[59,655,656],{"class":69},"PersistentVolumeClaim\n",[59,658,660,663],{"class":61,"line":659},3,[59,661,662],{"class":638},"metadata",[59,664,665],{"class":391},":\n",[59,667,669,672,674],{"class":61,"line":668},4,[59,670,671],{"class":638},"  name",[59,673,642],{"class":391},[59,675,676],{"class":69},"app-data-pvc\n",[59,678,680,683],{"class":61,"line":679},5,[59,681,682],{"class":638},"spec",[59,684,665],{"class":391},[59,686,688,691],{"class":61,"line":687},6,[59,689,690],{"class":638},"  accessModes",[59,692,665],{"class":391},[59,694,696,699],{"class":61,"line":695},7,[59,697,698],{"class":391},"    - ",[59,700,701],{"class":69},"ReadWriteOnce\n",[59,703,705,708,710],{"class":61,"line":704},8,[59,706,707],{"class":638},"  storageClassName",[59,709,642],{"class":391},[59,711,712],{"class":69},"local-path\n",[59,714,716,719],{"class":61,"line":715},9,[59,717,718],{"class":638},"  resources",[59,720,665],{"class":391},[59,722,724,727],{"class":61,"line":723},10,[59,725,726],{"class":638},"    requests",[59,728,665],{"class":391},[59,730,732,735,737],{"class":61,"line":731},11,[59,733,734],{"class":638},"      storage",[59,736,642],{"class":391},[59,738,739],{"class":69},"5Gi\n",[14,741,742],{},"Apply the manifest and inspect its status:",[50,744,746],{"className":52,"code":745,"language":54,"meta":55,"style":55},"kubectl apply -f pvc.yaml\nkubectl get pvc\n",[21,747,748,759],{"__ignoreMap":55},[59,749,750,752,754,756],{"class":61,"line":62},[59,751,547],{"class":65},[59,753,550],{"class":69},[59,755,553],{"class":73},[59,757,758],{"class":69}," pvc.yaml\n",[59,760,761,763,765],{"class":61,"line":648},[59,762,547],{"class":65},[59,764,603],{"class":69},[59,766,767],{"class":69}," pvc\n",[14,769,770,771,626],{},"Notice that the claim status remains ",[21,772,348],{},[50,774,779],{"className":775,"code":777,"language":778,"meta":55},[776],"language-text","NAME           STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE\napp-data-pvc   Pending                                      local-path     5s\n","text",[21,780,777],{"__ignoreMap":55},[14,782,783,784,786,787,789],{},"Because ",[21,785,479],{}," uses ",[21,788,436],{},", Kubernetes intentionally waits until a pod requests this volume before choosing a node.",[141,791,793],{"id":792},"step-2-deploy-the-stateful-pod","Step 2: Deploy the Stateful Pod",[14,795,796,797,626],{},"Create a pod manifest named ",[21,798,799],{},"pod.yaml",[50,801,803],{"className":629,"code":802,"language":631,"meta":55,"style":55},"apiVersion: v1\nkind: Pod\nmetadata:\n  name: storage-demo-pod\nspec:\n  containers:\n    - name: alpine-app\n      image: alpine:latest\n      command:\n        - \"sh\"\n        - \"-c\"\n        - \"echo 'Data persisted at $(date)' >> \u002Fdata\u002Flog.txt && sleep 3600\"\n      volumeMounts:\n        - name: storage-volume\n          mountPath: \u002Fdata\n  volumes:\n    - name: storage-volume\n      persistentVolumeClaim:\n        claimName: app-data-pvc\n",[21,804,805,813,822,828,837,843,850,862,872,879,887,894,902,910,922,933,941,952,960],{"__ignoreMap":55},[59,806,807,809,811],{"class":61,"line":62},[59,808,639],{"class":638},[59,810,642],{"class":391},[59,812,645],{"class":69},[59,814,815,817,819],{"class":61,"line":648},[59,816,651],{"class":638},[59,818,642],{"class":391},[59,820,821],{"class":69},"Pod\n",[59,823,824,826],{"class":61,"line":659},[59,825,662],{"class":638},[59,827,665],{"class":391},[59,829,830,832,834],{"class":61,"line":668},[59,831,671],{"class":638},[59,833,642],{"class":391},[59,835,836],{"class":69},"storage-demo-pod\n",[59,838,839,841],{"class":61,"line":679},[59,840,682],{"class":638},[59,842,665],{"class":391},[59,844,845,848],{"class":61,"line":687},[59,846,847],{"class":638},"  containers",[59,849,665],{"class":391},[59,851,852,854,857,859],{"class":61,"line":695},[59,853,698],{"class":391},[59,855,856],{"class":638},"name",[59,858,642],{"class":391},[59,860,861],{"class":69},"alpine-app\n",[59,863,864,867,869],{"class":61,"line":704},[59,865,866],{"class":638},"      image",[59,868,642],{"class":391},[59,870,871],{"class":69},"alpine:latest\n",[59,873,874,877],{"class":61,"line":715},[59,875,876],{"class":638},"      command",[59,878,665],{"class":391},[59,880,881,884],{"class":61,"line":723},[59,882,883],{"class":391},"        - ",[59,885,886],{"class":69},"\"sh\"\n",[59,888,889,891],{"class":61,"line":731},[59,890,883],{"class":391},[59,892,893],{"class":69},"\"-c\"\n",[59,895,897,899],{"class":61,"line":896},12,[59,898,883],{"class":391},[59,900,901],{"class":69},"\"echo 'Data persisted at $(date)' >> \u002Fdata\u002Flog.txt && sleep 3600\"\n",[59,903,905,908],{"class":61,"line":904},13,[59,906,907],{"class":638},"      volumeMounts",[59,909,665],{"class":391},[59,911,913,915,917,919],{"class":61,"line":912},14,[59,914,883],{"class":391},[59,916,856],{"class":638},[59,918,642],{"class":391},[59,920,921],{"class":69},"storage-volume\n",[59,923,925,928,930],{"class":61,"line":924},15,[59,926,927],{"class":638},"          mountPath",[59,929,642],{"class":391},[59,931,932],{"class":69},"\u002Fdata\n",[59,934,936,939],{"class":61,"line":935},16,[59,937,938],{"class":638},"  volumes",[59,940,665],{"class":391},[59,942,944,946,948,950],{"class":61,"line":943},17,[59,945,698],{"class":391},[59,947,856],{"class":638},[59,949,642],{"class":391},[59,951,921],{"class":69},[59,953,955,958],{"class":61,"line":954},18,[59,956,957],{"class":638},"      persistentVolumeClaim",[59,959,665],{"class":391},[59,961,963,966,968],{"class":61,"line":962},19,[59,964,965],{"class":638},"        claimName",[59,967,642],{"class":391},[59,969,676],{"class":69},[14,971,972],{},"Apply the pod:",[50,974,976],{"className":52,"code":975,"language":54,"meta":55,"style":55},"kubectl apply -f pod.yaml\n",[21,977,978],{"__ignoreMap":55},[59,979,980,982,984,986],{"class":61,"line":62},[59,981,547],{"class":65},[59,983,550],{"class":69},[59,985,553],{"class":73},[59,987,988],{"class":69}," pod.yaml\n",[14,990,991],{},"Check the PVC again:",[50,993,995],{"className":52,"code":994,"language":54,"meta":55,"style":55},"kubectl get pvc,pv\n",[21,996,997],{"__ignoreMap":55},[59,998,999,1001,1003],{"class":61,"line":62},[59,1000,547],{"class":65},[59,1002,603],{"class":69},[59,1004,1005],{"class":69}," pvc,pv\n",[14,1007,1008,1009,626],{},"The volume immediately transitions to ",[21,1010,1011],{},"Bound",[50,1013,1016],{"className":1014,"code":1015,"language":778,"meta":55},[776],"NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE\npersistentvolumeclaim\u002Fapp-data-pvc   Bound    pvc-8e2b851a-7b3c-4b68-b80c-0744be958210   5Gi        RWO            local-path     30s\n\nNAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                  STORAGECLASS   AGE\npersistentvolume\u002Fpvc-8e2b851a-7b3c-4b68-b80c-0744be958210   5Gi        RWO            Delete           Bound    default\u002Fapp-data-pvc   local-path     5s\n",[21,1017,1015],{"__ignoreMap":55},[141,1019,1021],{"id":1020},"step-3-verify-persistence-across-restarts","Step 3: Verify Persistence Across Restarts",[14,1023,1024],{},"Verify that the pod wrote data to the mounted volume:",[50,1026,1028],{"className":52,"code":1027,"language":54,"meta":55,"style":55},"kubectl exec storage-demo-pod -- cat \u002Fdata\u002Flog.txt\n",[21,1029,1030],{"__ignoreMap":55},[59,1031,1032,1034,1037,1040,1043,1046],{"class":61,"line":62},[59,1033,547],{"class":65},[59,1035,1036],{"class":69}," exec",[59,1038,1039],{"class":69}," storage-demo-pod",[59,1041,1042],{"class":73}," --",[59,1044,1045],{"class":69}," cat",[59,1047,1048],{"class":69}," \u002Fdata\u002Flog.txt\n",[14,1050,1051],{},"Now delete and recreate the pod:",[50,1053,1055],{"className":52,"code":1054,"language":54,"meta":55,"style":55},"kubectl delete pod storage-demo-pod\nkubectl apply -f pod.yaml\n",[21,1056,1057,1070],{"__ignoreMap":55},[59,1058,1059,1061,1064,1067],{"class":61,"line":62},[59,1060,547],{"class":65},[59,1062,1063],{"class":69}," delete",[59,1065,1066],{"class":69}," pod",[59,1068,1069],{"class":69}," storage-demo-pod\n",[59,1071,1072,1074,1076,1078],{"class":61,"line":648},[59,1073,547],{"class":65},[59,1075,550],{"class":69},[59,1077,553],{"class":73},[59,1079,988],{"class":69},[14,1081,1082],{},"Check the log contents again:",[50,1084,1085],{"className":52,"code":1027,"language":54,"meta":55,"style":55},[21,1086,1087],{"__ignoreMap":55},[59,1088,1089,1091,1093,1095,1097,1099],{"class":61,"line":62},[59,1090,547],{"class":65},[59,1092,1036],{"class":69},[59,1094,1039],{"class":69},[59,1096,1042],{"class":73},[59,1098,1045],{"class":69},[59,1100,1048],{"class":69},[14,1102,1103],{},"You will see both timestamp entries preserved across the pod lifecycle.",[141,1105,1107],{"id":1106},"step-4-verify-host-directory-cleanup","Step 4: Verify Host Directory Cleanup",[14,1109,1110,1111,1114,1115,1118],{},"On the physical host running the node, inspect the local path storage directory (defaulting to ",[21,1112,1113],{},"\u002Fvar\u002Flib\u002Francher\u002Fk3s\u002Fstorage"," on K3s, or ",[21,1116,1117],{},"\u002Fopt\u002Flocal-path-provisioner"," on standard installs):",[50,1120,1122],{"className":52,"code":1121,"language":54,"meta":55,"style":55},"ls -la \u002Fvar\u002Flib\u002Francher\u002Fk3s\u002Fstorage\n",[21,1123,1124],{"__ignoreMap":55},[59,1125,1126,1129,1132],{"class":61,"line":62},[59,1127,1128],{"class":65},"ls",[59,1130,1131],{"class":73}," -la",[59,1133,1134],{"class":69}," \u002Fvar\u002Flib\u002Francher\u002Fk3s\u002Fstorage\n",[14,1136,1137],{},"You will find the directory matching the generated PV volume name.",[14,1139,1140],{},"When you delete the PVC:",[50,1142,1144],{"className":52,"code":1143,"language":54,"meta":55,"style":55},"kubectl delete pod storage-demo-pod\nkubectl delete pvc app-data-pvc\n",[21,1145,1146,1156],{"__ignoreMap":55},[59,1147,1148,1150,1152,1154],{"class":61,"line":62},[59,1149,547],{"class":65},[59,1151,1063],{"class":69},[59,1153,1066],{"class":69},[59,1155,1069],{"class":69},[59,1157,1158,1160,1162,1165],{"class":61,"line":648},[59,1159,547],{"class":65},[59,1161,1063],{"class":69},[59,1163,1164],{"class":69}," pvc",[59,1166,1167],{"class":69}," app-data-pvc\n",[14,1169,1170],{},"The provisioner launches its cleanup helper and removes the folder from the host filesystem, preventing disk accumulation.",[36,1172],{},[39,1174,1176],{"id":1175},"operational-caveats-architectural-boundaries","Operational Caveats & Architectural Boundaries",[14,1178,1179],{},"While local path storage delivers exceptional speed, engineers must weigh its architectural constraints before choosing it for multi-node production clusters:",[88,1181,1182,1188,1194],{},[91,1183,1184,1187],{},[94,1185,1186],{},"No Node-Level Redundancy:"," Local storage pins data to a specific physical machine. If the underlying hardware fails, Kubernetes cannot remount that data on another node.",[91,1189,1190,1193],{},[94,1191,1192],{},"Replication Strategy:"," Use local path storage for workloads that manage data replication at the application layer—such as PostgreSQL with CloudNativePG or patroni streaming replicas, Redis\u002FValkey clusters, and Kafka brokers. For workloads lacking internal replication, combine local storage with automated cloud snapshot backups (such as Barman S3 backups or Velero).",[91,1195,1196,1199,1200,1203],{},[94,1197,1198],{},"No Filesystem Quota Enforcement:"," By default, the ",[21,1201,1202],{},"storage: 5Gi"," request in your PVC acts solely as a scheduling filter. The Local Path Provisioner does not enforce Linux filesystem quotas unless your host partition is explicitly formatted and mounted with ext4 or xfs project quota support. A runaway container can write past its claimed capacity and fill the host partition.",[36,1205],{},[39,1207,1209],{"id":1208},"key-takeaways","Key Takeaways",[461,1211,1212,1215,1221,1227,1232],{},[91,1213,1214],{},"Pod filesystems are ephemeral by default; persistent storage requires decoupling workload lifecycles from container lifecycles.",[91,1216,1217,1218,1220],{},"Raw ",[21,1219,23],{}," mounts introduce security risks, lack dynamic provisioning, and orphan data on disk upon deletion.",[91,1222,1223,1224,1226],{},"K3s includes Rancher's Local Path Provisioner out of the box under the ",[21,1225,479],{}," storage class; bare-metal clusters can install it via a single manifest.",[91,1228,1229,1231],{},[21,1230,344],{}," ensures the provisioner allocates volumes on the exact worker node selected by the scheduler.",[91,1233,1234],{},"Local path storage delivers raw NVMe throughput with zero network overhead, making it the ideal storage choice for homelabs, edge nodes, and replicated database clusters.",[1236,1237,1238],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":55,"searchDepth":648,"depth":648,"links":1240},[1241,1242,1247,1248,1253,1256,1262,1263],{"id":41,"depth":648,"text":42},{"id":129,"depth":648,"text":130,"children":1243},[1244,1245,1246],{"id":143,"depth":659,"text":144},{"id":150,"depth":659,"text":151},{"id":164,"depth":659,"text":165},{"id":177,"depth":648,"text":178},{"id":316,"depth":648,"text":317,"children":1249},[1250,1251],{"id":323,"depth":659,"text":324},{"id":432,"depth":659,"text":1252},"Why WaitForFirstConsumer Is Non-Negotiable",{"id":455,"depth":648,"text":456,"children":1254},[1255],{"id":529,"depth":659,"text":530},{"id":611,"depth":648,"text":612,"children":1257},[1258,1259,1260,1261],{"id":618,"depth":659,"text":619},{"id":792,"depth":659,"text":793},{"id":1020,"depth":659,"text":1021},{"id":1106,"depth":659,"text":1107},{"id":1175,"depth":648,"text":1176},{"id":1208,"depth":648,"text":1209},"\u002Fimages\u002Fcovers\u002Fmount-local-storage-to-kubernetes-pods-k3s.png","2025-01-08","How Kubernetes storage works under the hood: PersistentVolumes, Claims, StorageClasses, and how K3s uses the Rancher local-path provisioner to mount node disks directly.","md",{},true,"\u002Fblog\u002Fmount-local-storage-to-kubernetes-pods-k3s",{"title":5,"description":1266},"blog\u002Fmount-local-storage-to-kubernetes-pods-k3s",[1274,1275,1276,1277],"kubernetes","k3s","storage","devops","blog_post","ZYVw7AIs6Lw5MUkuekObLwojd2cYz3V5EDvKIbNBD0A",null,1791464647282]