Skip to content

Local-Path PVC: SSH Sidecar for Developer Access

Apps on local-path storage are fast but inaccessible from the network — unlike NFS, there is no share to mount. This pattern adds a minimal SSH sidecar to the pod that shares the same PVC, enabling live bidirectional file sync from a developer machine via Mutagen.

graph LR
    MAC["Mac\n~/mnt/ha-config"]
    MUTAGEN["Mutagen\n(FSEvents + SFTP)"]
    PF["kubectl port-forward\n:2222 → pod:22"]
    SSH["SSH sidecar\nalpine + openssh"]
    PVC["local-path PVC\n/config"]
    APP["App container\n/config"]

    MAC <-->|"live sync\ntwo-way-safe"| MUTAGEN
    MUTAGEN <-->|"SSH"| PF
    PF <-->|"port-forward"| SSH
    SSH <--> PVC
    APP <--> PVC
Hold "Alt" / "Option" to enable pan & zoom

Development use only

This sidecar is a convenience tool for editing config files. Do not use it as a backup mechanism — use the SQLite + local-path pattern for durability.


Kubernetes Resources

SSH public key ConfigMap

configmap-ssh-keys.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ssh-authorized-keys
  namespace: <ns>
data:
  authorized_keys: |
    ssh-ed25519 AAAA... user@host

Public keys in git are fine

SSH public keys are designed to be shared. Only the private key (on your Mac) is sensitive. A ConfigMap is appropriate here — no need for ExternalSecrets.

Sidecar container

Add to the app Deployment alongside the existing container:

deployment.yaml (sidecar)
- name: ssh
  image: alpine:3.21
  command: ["/bin/sh", "-c"]
  args:
  - |
    apk add --no-cache openssh-server &&
    mkdir -p /root/.ssh &&
    cp /ssh-keys/authorized_keys /root/.ssh/authorized_keys &&
    chmod 700 /root/.ssh && chmod 600 /root/.ssh/authorized_keys &&
    ssh-keygen -A &&
    exec /usr/sbin/sshd -D -e \
      -o PermitRootLogin=yes \
      -o PasswordAuthentication=no \
      -o AllowTcpForwarding=no \
      -o X11Forwarding=no
  ports:
  - containerPort: 22
  volumeMounts:
  - name: <config-volume>
    mountPath: /config
  - name: ssh-keys
    mountPath: /ssh-keys
    readOnly: true

Add the ConfigMap volume to the pod's volumes list:

- name: ssh-keys
  configMap:
    name: ssh-authorized-keys

The existing config volume (<config-volume>) is reused — both containers share the same PVC.


Developer Machine Setup

Prerequisites (one-time)

brew install mutagen-io/mutagen/mutagen

No macFUSE or kernel extensions required.

SSH config (one-time, added automatically by the script below)

Host ha-sftp-local
  HostName localhost
  Port 2222
  User root
  IdentityFile ~/.ssh/id_ed25519
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null

StrictHostKeyChecking=no is safe here — the sidecar generates fresh SSH host keys on every pod restart, and the connection is local-only via kubectl port-forward.

Sync script

scripts/ha-mount.sh
#!/usr/bin/env bash
set -euo pipefail

LOCAL_DIR="$HOME/mnt/ha-config"
PF_PID_FILE="/tmp/ha-mount-pf.pid"
NAMESPACE="home"
DEPLOYMENT="homeassistant"
LOCAL_PORT=2222
SSH_KEY="$HOME/.ssh/id_ed25519"
MUTAGEN_SESSION="ha-config"
SSH_ALIAS="ha-sftp-local"

ensure_ssh_config() {
  if ! grep -q "Host ${SSH_ALIAS}" ~/.ssh/config 2>/dev/null; then
    cat >> ~/.ssh/config <<EOF

Host ${SSH_ALIAS}
  HostName localhost
  Port ${LOCAL_PORT}
  User root
  IdentityFile ${SSH_KEY}
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null
EOF
  fi
}

start() {
  mkdir -p "$LOCAL_DIR"
  ensure_ssh_config
  lsof -ti "tcp:${LOCAL_PORT}" | xargs kill -9 2>/dev/null || true
  sleep 1
  kubectl port-forward -n "$NAMESPACE" "deploy/$DEPLOYMENT" "$LOCAL_PORT:22" &
  echo $! > "$PF_PID_FILE"
  sleep 2
  mutagen sync create \
    --name "$MUTAGEN_SESSION" \
    --sync-mode two-way-safe \
    --ignore "home-assistant_v2.db*" \
    --ignore "*.log" --ignore "*.log.*" \
    --ignore ".storage" \
    "$LOCAL_DIR" "${SSH_ALIAS}:/config"
  echo "Live sync: $LOCAL_DIR ↔ pod:/config"
}

stop() {
  mutagen sync terminate "$MUTAGEN_SESSION" 2>/dev/null || true
  [ -f "$PF_PID_FILE" ] && kill "$(cat "$PF_PID_FILE")" 2>/dev/null || true
  rm -f "$PF_PID_FILE"
}

status() { mutagen sync list "$MUTAGEN_SESSION"; }

case "${1:-start}" in
  start) start ;; stop) stop ;; status) status ;;
  *) echo "Usage: $0 [start|stop|status]" ; exit 1 ;;
esac
chmod +x scripts/ha-mount.sh

Usage

ha-mount.sh start     # start port-forward + Mutagen session
# edit files in ~/mnt/ha-config — syncs both ways instantly
ha-mount.sh status    # check sync health / conflicts
ha-mount.sh stop      # terminate

Troubleshooting

Symptom Cause Fix
Permission denied (publickey) SSH config has wrong User Run sed -i '' 's/User sftp/User root/' ~/.ssh/config
server magic number incorrect SFTP-only container (e.g. atmoz/sftp) blocks Mutagen agent Use full openssh sidecar — atmoz/sftp forces internal-sftp
rclone mount fails on Apple Silicon Homebrew rclone strips macFUSE; official binary needs Recovery Mode kext approval Use Mutagen instead — no kernel extension needed
Port 2222 already in use Stale port-forward from previous session lsof -ti tcp:2222 \| xargs kill -9
Mutagen conflict Both HA and local edited the same file Run mutagen sync list ha-config to identify, resolve manually