A small Go sidecar container that watches Kubernetes ConfigMaps and Secrets
and materializes their contents as files in a shared volume. Use it to deliver
configuration to applications that read files (not the Kubernetes API) — without
restarting the pod on every change.
A lighter, focused alternative to kiwigrid/k8s-sidecar.
- Live sync: initial list + streaming watch; updates land on disk within seconds
- Label or annotation filtering: match resources by label or annotation, with optional value match
- ConfigMap
dataandbinaryData: text and binary keys both supported - Secret
data: each key becomes a file - Per-resource folder override: redirect a resource's files via an annotation
- Multi-namespace: comma-separated list of namespaces
- Hash-based dedup: only rewrites files when their content actually changes
- Atomic writes: temp-file + rename, so consumers never see a partial file
- Watch resume: threads
resourceVersionto avoid event gaps; re-lists only on410 Gone - Tiny image: static Go binary on
scratch, runs as non-root uid65532
Apply the example manifest (Deployment + ConfigMaps + Secrets + RBAC):
kubectl apply -f examples/example.yamlIt runs an alpine container printing the shared folder every 5s, alongside
this sidecar writing ConfigMap/Secret data into /etc/config.
All configuration is via environment variables, prefixed FS_SIDECAR_.
| Variable | Required | Default | Description |
|---|---|---|---|
FS_SIDECAR_LABEL |
Yes* | - | Label or annotation key used for filtering |
FS_SIDECAR_LABEL_VALUE |
No | - | If set, the key must equal this value; if unset, key presence is enough |
FS_SIDECAR_LABEL_ANNOTATION |
No | label |
Filter by label or annotation |
FS_SIDECAR_FOLDER |
Yes | - | Destination directory for materialized files |
FS_SIDECAR_FOLDER_ANNOTATION |
No | k8s-sidecar-target-directory |
Per-resource annotation that overrides the destination folder |
FS_SIDECAR_NAMESPACE |
No | current namespace | Comma-separated namespaces to watch |
FS_SIDECAR_RESOURCE |
No | both |
configmap, secret, or both |
FS_SIDECAR_LOG_LEVEL |
No | info |
trace, debug, info, warn, error |
FS_SIDECAR_LOG_FORMAT |
No | json |
json or logfmt |
FS_SIDECAR_FILE_MODE |
No | 0600 |
Octal permission mode for synced files (e.g. 0644 for world-readable) |
FS_SIDECAR_DIR_MODE |
No | 0700 |
Octal permission mode for created directories (e.g. 0755) |
*FS_SIDECAR_LABEL is always required.
Redirect a specific resource's files to a custom location by annotating it:
metadata:
annotations:
k8s-sidecar-target-directory: "/custom/path"Absolute paths are used as-is; relative paths are resolved under FS_SIDECAR_FOLDER.
The sidecar's ServiceAccount needs get, list, and watch on the
resources it syncs. The example manifest includes a matching Role +
RoleBinding; for cluster-wide use, switch to a ClusterRole/ClusterRoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: k8s-fs-sidecar
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "watch", "list"]Files are written 0600 and directories 0700 by default (owner-only).
The published image runs as uid 65532, so for a consuming container running
a different uid, you have two options:
- Share the uid — set a pod-level
securityContext.runAsUser: 65532so both containers run as the same user. This is the most secure option because secret material stays owner-only. - Widen the file permissions — set
FS_SIDECAR_FILE_MODE=0644andFS_SIDECAR_DIR_MODE=0755so the files are world-readable. Convenient when you don't control the consuming container's uid, at the cost of exposing synced Secrets to every user on the node.
env:
- name: FS_SIDECAR_FILE_MODE
value: "0644"
- name: FS_SIDECAR_DIR_MODE
value: "0755"Modes are parsed as octal; setuid/setgid/sticky bits (>0777) are rejected.
- Initial sync — list matching resources, write their files
- Watch — open a
Watchfrom the list'sresourceVersion(no gap) - Events —
ADDED/MODIFIEDwrite/update files;DELETEDremoves them - Dedup — SHA-256 comparison skips writes when content is unchanged
- Atomic writes — temp file +
rename, so readers never observe a partial file - Reconnect — on a dropped or expired (
410 Gone) watch, resume from the last observedresourceVersion, or re-list if it has expired
See ARCHITECTURE.md for the package layout and data flow.
See CONTRIBUTING.md for conventions, the test strategy,
and how to add a new resource type or a new configuration knob.
Pre-built images are published to the GitHub Container Registry by the
Release workflow, which runs whenever a v* tag is pushed:
ghcr.io/acidghost/k8s-fs-sidecar:1.0.0 # exact version
ghcr.io/acidghost/k8s-fs-sidecar:1.0 # major.minor
ghcr.io/acidghost/k8s-fs-sidecar:1 # major
ghcr.io/acidghost/k8s-fs-sidecar:latest # newest non-prerelease
Every published image is signed with cosign (keyless, by digest) and ships with provenance and an SBOM. See ARCHITECTURE.md → Release & distribution for the security model and how to verify an image.
Released into the public domain under the terms of the UNLICENSE.