Skip to content
Persistent Agent — A Kali Workstation on Kubernetes
Persistent Agent — A Kali Workstation on Kubernetes

Persistent Agent — A Kali Workstation on Kubernetes

My laptop is not always online. The Claude mobile app is useful but cannot run terminal commands, install tools, or maintain a persistent workspace. This post covers deploying a persistent Kali Linux container on gpu-1 as an always-on development workstation, accessible via SSH from anywhere on the network.

    flowchart TD
  subgraph GPU1[gpu-1 — i9, 128GB RAM]
    Kali[kali container<br/>kalilinux/kali-rolling]
    SSHD[SSH server<br/>port 2222]
    PVC[50Gi PVC<br/>longhorn-gpu-local<br/>/root]
    Claude[Claude Code<br/>--remote headless]
  end
  subgraph LB[Cilium L2 LoadBalancer]
    SVC[Service<br/>192.168.55.215:22<br/>→ 2222]
  end
  subgraph Client[Operator]
    SSH[ssh root@192.168.55.215]
  end

  SSH --> SVC
  SVC -->|port map 22→2222| SSHD
  SSHD --> Claude
  Claude -->|persists| PVC
  

Why a Container Instead of a VM

A Deployment with a PersistentVolumeClaimA Kubernetes request for durable storage. The pod names a claim and the storage layer — Longhorn on Frank — binds real disk behind it, so the data outlives the pod. is simpler than a full VM:

  • Survives restarts — 50Gi PVC at /root preserves home directory, tools, SSH host keys, and Claude Code config across pod restarts.
  • Self-heals — pod crashes → Kubernetes restarts it. gpu-1 reboots → pod comes back automatically.
  • GitOps model — six YAML files under apps/kali/manifests/, managed by ArgoCD.

A VM would give a full OS with its own kernel, but for a development workstation that mostly runs a terminal, a container is the right tool.

Why Kali

Kali Linux comes with a massive repository of security and networking tools pre-packaged. Even though the primary use case is Claude Code, having nmap, netcat, tcpdump, and hundreds of other tools a single apt-get install away makes it versatile for anything beyond coding.

Architecture

One ArgoCD app, raw manifests:

ResourcePurpose
Namespacekali-system with privileged PodSecurity
Deploymentkalilinux/kali-rolling, pinned to gpu-1
PVC50Gi on longhorn-gpu-local (single replica, local to gpu-1)
ConfigMapStartup script — installs sshd, configures key-only auth
SecretSSH authorized_keys (public key, not sensitive)
ServiceLoadBalancer at 192.168.55.215:22 via Cilium L2

The Kali container runs on gpu-1 but does not request a GPU resource. It is there for the 128GB RAM and i9 CPU — overkill for a shell but ideal for running Claude Code’s agent loops and large-context operations.

The Startup Script

The container uses the official kalilinux/kali-rolling image with no customisation. A ConfigMap-mounted startup script handles everything:

set -e
apt-get update -qq
apt-get install -y -qq openssh-server curl git procps

# Persist SSH host keys in /root (PVC-backed)
HOST_KEY_DIR=/root/.ssh-host-keys
mkdir -p "$HOST_KEY_DIR"
if [ -f "$HOST_KEY_DIR/ssh_host_ed25519_key" ]; then
  cp "$HOST_KEY_DIR"/ssh_host_* /etc/ssh/
else
  ssh-keygen -A
  cp /etc/ssh/ssh_host_* "$HOST_KEY_DIR/"
fi

cat > /etc/ssh/sshd_config.d/kali-k8s.conf <<'SSHCONF'
PermitRootLogin prohibit-password
PubkeyAuthentication yes
PasswordAuthentication no
SSHCONF

cp /etc/ssh-keys/authorized_keys /root/.ssh/authorized_keys
/usr/sbin/sshd
exec sleep infinity

Two worth noting:

  1. Host key persistence — host keys stored on PVC. Without it, every restart regenerates keys and SSH warns about changed fingerprints.
  2. No custom imageapt-get install runs on every start (~30s). For a workstation that restarts once a month, building a custom image is not worth the overhead.

SSH Key as a Public Resource

The authorized_keys file contains a public key — it is not sensitive. Instead of Secrets OPerationSMozilla's tool for encrypting the *values* in a YAML file while leaving the keys readable, so an encrypted secret still reviews as a sensible diff. encryption, the key lives directly in apps/kali/manifests/secret.yaml and is managed by ArgoCD like any other resource.

The Always-On Agent

After deployment:

ssh root@192.168.55.215
# Install Claude Code
claude --remote          # headless session, survives SSH disconnect

Claude Code’s --remote flag starts a headless session that persists independently of the SSH connection. Start a task from your laptop, close the lid, check results from your phone later. The agent keeps running because the container keeps running.

Missteps

What HappenedWhy It Was WrongHow We Fixed ItCommit
SSH host key changes on every restart — no PVC persistence for /etc/ssh/ keysHost keys regenerated each boot; SSH clients warned about changed fingerprintStore host keys in /root/.ssh-host-keys/ on PVC, copy back on boot
apt-get install on every start — 30s startup delay while installing packagesEfficiency choice — trade-off for not maintaining a custom imageAccepted as design choice; easy to pre-bake image if startup time becomes critical

Recovery Path

SymptomCauseFix
SSH host key warningPod restarted, host keys regeneratedHost keys stored on PVC; verify HOST_KEY_DIR copy logic in startup script
Cannot SSH to 192.168.55.215Pod not running or sshd not startedCheck kubectl get pods -n kali-system
--remote session lostPod restart (PVC still intact)Reconnect via SSH, claude --resume

References

Next: Progressive Delivery with Argo Rollouts