{"id":13819725,"url":"https://github.com/fenio/pv-mounter","last_synced_at":"2026-02-18T17:01:18.498Z","repository":{"id":240842484,"uuid":"803067173","full_name":"fenio/pv-mounter","owner":"fenio","description":"Mount Kubernetes PVs locally using SSHFS","archived":false,"fork":false,"pushed_at":"2026-02-13T23:07:00.000Z","size":40732,"stargazers_count":137,"open_issues_count":1,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-14T05:20:46.275Z","etag":null,"topics":["golang","k8s","k8s-at-home","krew-plugin","kubernetes","persistent-volume","persistent-volume-claim","sshfs"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fenio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-05-20T02:08:36.000Z","updated_at":"2026-02-13T23:07:02.000Z","dependencies_parsed_at":"2026-01-16T08:23:30.680Z","dependency_job_id":null,"html_url":"https://github.com/fenio/pv-mounter","commit_stats":null,"previous_names":["fenio/pv-mounter"],"tags_count":64,"template":false,"template_full_name":"replicatedhq/krew-plugin-template","purl":"pkg:github/fenio/pv-mounter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenio%2Fpv-mounter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenio%2Fpv-mounter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenio%2Fpv-mounter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenio%2Fpv-mounter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fenio","download_url":"https://codeload.github.com/fenio/pv-mounter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenio%2Fpv-mounter/sbom","scorecard":{"id":83095,"data":{"date":"2025-08-15T04:23:14Z","repo":{"name":"github.com/fenio/pv-mounter","commit":"432f9ffea6880de64f22b457671a580d94d81882"},"scorecard":{"version":"v5.2.1","commit":"ab2f6e92482462fe66246d9e32f642855a691dc1"},"score":7.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/2 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#code-review"}},{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: detected update tool: Dependabot: .github/dependabot.yml:1","Info: detected update tool: RenovateBot: .github/renovate.json5:1"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#dependency-update-tool"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#security-policy"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":9,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: topLevel 'contents' permission set to 'read': .github/workflows/build.yaml:3","Info: topLevel 'contents' permission set to 'read': .github/workflows/dependency-review.yml:13","Info: topLevel 'contents' permission set to 'read': .github/workflows/docker.yml:3","Warn: topLevel 'security-events' permission set to 'write': .github/workflows/osv-scanner.yml:26","Info: topLevel 'contents' permission set to 'read': .github/workflows/osv-scanner.yml:28","Info: topLevel 'actions' permission set to 'read': .github/workflows/osv-scanner.yml:29","Info: topLevel permissions set to 'read-all': .github/workflows/scorecard.yml:18","Info: topLevel 'contents' permission set to 'read': .github/workflows/star.yml:7","Info: topLevel 'contents' permission set to 'read': .github/workflows/test.yaml:3","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#token-permissions"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":9,"reason":"dependency not pinned by hash detected -- score normalized to 9","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/star.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/fenio/pv-mounter/star.yml/main?enable=pin","Info:  13 out of  13 GitHub-owned GitHubAction dependencies pinned","Info:  21 out of  22 third-party GitHubAction dependencies pinned","Info:   2 out of   2 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":2,"reason":"badge detected: InProgress","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#cii-best-practices"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/build.yaml:9"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#packaging"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.9.9 not signed: https://api.github.com/repos/fenio/pv-mounter/releases/238851165","Warn: release artifact v0.9.8 not signed: https://api.github.com/repos/fenio/pv-mounter/releases/236189436","Warn: release artifact v0.9.7 not signed: https://api.github.com/repos/fenio/pv-mounter/releases/226756588","Warn: release artifact v0.9.6 not signed: https://api.github.com/repos/fenio/pv-mounter/releases/219147855","Warn: release artifact v0.9.5 not signed: https://api.github.com/repos/fenio/pv-mounter/releases/219145884","Warn: release artifact v0.9.9 does not have provenance: https://api.github.com/repos/fenio/pv-mounter/releases/238851165","Warn: release artifact v0.9.8 does not have provenance: https://api.github.com/repos/fenio/pv-mounter/releases/236189436","Warn: release artifact v0.9.7 does not have provenance: https://api.github.com/repos/fenio/pv-mounter/releases/226756588","Warn: release artifact v0.9.6 does not have provenance: https://api.github.com/repos/fenio/pv-mounter/releases/219147855","Warn: release artifact v0.9.5 does not have provenance: https://api.github.com/repos/fenio/pv-mounter/releases/219145884"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#signed-releases"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#vulnerabilities"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#branch-protection"}},{"name":"SAST","score":10,"reason":"SAST tool is run on all commits","details":["Info: all commits (29) are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#sast"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#fuzzing"}},{"name":"Contributors","score":0,"reason":"project has 0 contributing companies or organizations -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project has a set of contributors from multiple organizations (e.g., companies).","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#contributors"}},{"name":"CI-Tests","score":10,"reason":"16 out of 16 merged PRs checked by a CI test -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project runs tests before pull requests are merged.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#ci-tests"}}]},"last_synced_at":"2025-08-15T06:19:22.738Z","repository_id":240842484,"created_at":"2025-08-15T06:19:22.739Z","updated_at":"2025-08-15T06:19:22.739Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29587066,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T16:55:40.614Z","status":"ssl_error","status_checked_at":"2026-02-18T16:55:37.558Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","k8s","k8s-at-home","krew-plugin","kubernetes","persistent-volume","persistent-volume-claim","sshfs"],"created_at":"2024-08-04T08:00:52.207Z","updated_at":"2026-02-18T17:01:18.136Z","avatar_url":"https://github.com/fenio.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# pv-mounter\n\n[![build](https://github.com/fenio/pv-mounter/actions/workflows/build.yaml/badge.svg)](https://github.com/fenio/pv-mounter/actions/workflows/build.yaml)\n[![test](https://github.com/fenio/pv-mounter/actions/workflows/test.yaml/badge.svg)](https://github.com/fenio/pv-mounter/actions/workflows/test.yaml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/fenio/pv-mounter)](https://goreportcard.com/report/github.com/fenio/pv-mounter)\n![Latest GitHub release](https://img.shields.io/github/release/fenio/pv-mounter.svg)\n[![GitHub license](https://img.shields.io/github/license/fenio/pv-mounter)](https://github.com/fenio/pv-mounter/blob/main/LICENSE)\n![GitHub stars](https://img.shields.io/github/stars/fenio/pv-mounter.svg?label=github%20stars)\n[![GitHub issues](https://img.shields.io/github/issues/fenio/pv-mounter)](https://github.com/fenio/pv-mounter/issues)\n![GitHub all releases](https://img.shields.io/github/downloads/fenio/pv-mounter/total)\n![Docker Pulls](https://img.shields.io/docker/pulls/bfenski/volume-exposer?label=volume-exposer%20-%20docker%20pulls)\n![Docker Pulls](https://img.shields.io/docker/pulls/bfenski/nfs-ganesha?label=nfs-ganesha%20-%20docker%20pulls)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/fenio/pv-mounter/badge)](https://scorecard.dev/viewer/?uri=github.com/fenio/pv-mounter)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9551/badge)](https://www.bestpractices.dev/projects/9551)\n[![codecov](https://codecov.io/gh/fenio/pv-mounter/graph/badge.svg?token=DHYZ71SVDV)](https://codecov.io/gh/fenio/pv-mounter)\n\nA tool to locally mount Kubernetes Persistent Volumes (PVs) using SSHFS or NFS.\n\nThis tool can also be used as a kubectl plugin.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch2 style=\"display: inline-block; margin: 0;\"\u003eDisclaimer\u003c/h2\u003e\u003c/summary\u003e\n\nThis tool was created with significant help from [ChatGPT-4o](https://chatgpt.com/?model=gpt-4o) and [perplexity](https://www.perplexity.ai/).\nIn fact, I didn't have to write much of the code myself, but I spent a lot of time crafting the correct prompts for these tools.\n\n**Update**\n\nThe above was true for versions 0.0.x. With version 0.5.0, I actually had to learn some Go. While I still used help from GPT, I had to completely change my approach.\nAI alone wasn't able to create fully functional code that met all my requirements.\n\nI published it using the Apache-2.0 license because the initial [repository](https://github.com/replicatedhq/krew-plugin-template) was licensed this way. However, to be honest, I'm not sure how such copy-and-paste code should be licensed.\n\n\u003c/details\u003e\n\n## Rationale\n\nI often need to copy some files from my [homelab](https://github.com/fenio/homelab) which is running on Kubernetes.\nHaving the ability to work on these files locally greatly simplifies this task. Thus, pv-mounter was born to automate that process.\n\n## What exactly does it do?\n\n![pv-mounter](images/pv-mounter.png)\n\npv-mounter supports two backends: **SSH** (default) and **NFS** (`--backend nfs`). Both work the same way depending on the volume's access mode.\n\nFor RWX (ReadWriteMany) volumes or unmounted RWO (ReadWriteOnce) volumes:\n\n* Spawns a standalone Pod with the chosen backend (SSH daemon or NFS-Ganesha) and binds it to the existing PVC.\n* Creates a port-forward to make it locally accessible.\n* Mounts the volume locally using SSHFS or NFSv4.\n\nFor already mounted RWO volumes:\n\n* Creates an ephemeral container with the chosen backend within the Pod that currently mounts the volume.\n* Creates a port-forward directly to the workload Pod to make it locally accessible.\n* Mounts the volume locally using SSHFS or NFSv4.\n\n## Prerequisites\n\n* **SSH backend (default):** You need a working SSHFS setup.\n  * Instructions for [macOS](https://osxfuse.github.io/).\n  * Instructions for [Linux](https://github.com/libfuse/sshfs).\n* **NFS backend (`--backend nfs`):** You need an NFS client.\n  * macOS: Built-in, no installation needed.\n  * Linux: `sudo apt install nfs-common` (Debian/Ubuntu) or `sudo dnf install nfs-utils` (RHEL/Fedora).\n\n## Quick Start / Usage\n\n```\nkubectl krew install pv-mounter\n\nkubectl pv-mounter mount [--backend ssh|nfs] [--needs-root] [--debug] [--image] [--image-secret] \u003cnamespace\u003e \u003cpvc-name\u003e \u003clocal-mount-point\u003e\nkubectl pv-mounter clean [--backend ssh|nfs] \u003cnamespace\u003e \u003cpvc-name\u003e \u003clocal-mount-point\u003e\n\n```\n\nObviously, you need to have working [krew](https://krew.sigs.k8s.io/docs/user-guide/setup/install/) installation first.\n\nOr you can simply grab binaries from [releases](https://github.com/fenio/pv-mounter/releases).\n\n## Security\n\nI spent quite some time making the solution as secure as possible.\n\n### SSH backend\n\n* SSH keys used for connections between various components are generated every time from scratch and once you wipe the environment clean, you won't be able to connect back into it using the same credentials.\n* Containers / Pods are using minimal possible privileges:\n\n```\nallowPrivilegeEscalation: false\nreadOnlyRootFilesystem: true\nrunAsUser = XYZ\nrunAsGroup = XYZ\nrunAsNonRoot = true\n```\n\nsshd_config is also limited as much as possible:\n\n```\nPermitRootLogin no\nPasswordAuthentication no\n```\n\nAbove, it's not true if you're using the --needs-root option or the NEEDS_ROOT environment variable, but well, you've asked for it.\n\n### NFS backend\n\n* NFS-Ganesha requires more capabilities than SSH (`SYS_ADMIN`, `DAC_READ_SEARCH`, `DAC_OVERRIDE`, `SYS_RESOURCE`, `CHOWN`, `FOWNER`, `SETUID`, `SETGID`) and runs with `seccompProfile: Unconfined`.\n* Standalone NFS pods run as root. Ephemeral NFS containers run as the workload pod's UID.\n* NFS exports use `No_Root_Squash` and `SecType = sys` (AUTH_SYS) — this is acceptable since traffic stays within the kubectl port-forward tunnel (localhost only).\n\n## Limitations\n\nThe tool has a \"clean\" option that does its best to clean up all the resources it created for mounting the volume locally.\nHowever, ephemeral containers can't be removed or deleted. That's the way Kubernetes works.\nAs part of the cleanup, this tool kills the process that keeps its ephemeral container alive.\nI confirmed it also kills other processes that were running in that container, but the container itself remains in a limbo state.\n\n## Windows\n\nSince I can't test Windows binaries, they are not included. However, since there seems to exist a working Windows implementation of SSHFS, in theory it should work.\n\n## Demos\n\nCreated with [VHS](https://github.com/charmbracelet/vhs) tool.\n\nThe demo GIFs are automatically regenerated when code or tape files change.\n\n### SSH\n\n#### RWX or unmounted RWO volume\n\n![ssh-rwx](images/ssh-rwx.gif)\n\n#### Mounted RWO volume\n\n![ssh-rwo](images/ssh-rwo.gif)\n\n### NFS\n\n#### RWX or unmounted RWO volume\n\n![nfs-rwx](images/nfs-rwx.gif)\n\n#### Mounted RWO volume\n\n![nfs-rwo](images/nfs-rwo.gif)\n\n## FAQ\n\nAsk more questions, if you like ;)\n\n#### I need to run the mounter pod as root, but my [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) blocks the creation. What needs to be done?\n\nYou can add a label to the namespace you want the pod to be spawned in, to create an exception.\n\n`kubectl label namespace NAMESPACE-NAME pod-security.kubernetes.io/enforce=privileged`\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=fenio/pv-mounter\u0026type=Date)](https://star-history.com/#fenio/pv-mounter\u0026Date)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffenio%2Fpv-mounter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffenio%2Fpv-mounter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffenio%2Fpv-mounter/lists"}