Three habits make apply safe on a directory. Diff first: kubectl 5,150 diff -f dir/ shows what a server-side dry run would change. Apply on the server: --server-side merges in the API server, which records a field manager per field and reports a conflict instead of silently overwriting another manager's field. Prune: plain apply never deletes an object whose file you removed; --prune with an ApplySet (alpha, behind KUBECTL_APPLYSET=true) records the set's members on a parent object and deletes those no longer in the files:
D=$(mktemp -d)
for n in one two; do kubectl create configmap demo-$n -n booknest --from-literal=owner=me \
--dry-run=client -o yaml > $D/demo-$n.yaml; done
kubectl apply --server-side -f $D/demo-one.yaml
sed 's/owner: me/owner: jenkins/' $D/demo-one.yaml |
kubectl apply --server-side --field-manager=jenkins -f - 2>&1 | head -1
export KUBECTL_APPLYSET=true
kubectl apply --server-side -n booknest --prune --applyset=demo -f $D
rm $D/demo-two.yaml
kubectl apply --server-side -n booknest --prune --applyset=demo -f $D
kubectl get configmaps -n booknest -l applyset.kubernetes.io/part-of -o nameconfigmap/demo-one serverside-applied error: Apply failed with 1 conflict: conflict with "kubectl": .data.owner configmap/demo-one serverside-applied configmap/demo-two serverside-applied configmap/demo-one serverside-applied configmap/demo-two pruned configmap/demo-one
The conflict names the other manager and the field; --force-conflicts takes ownership on purpose, as a CI pipeline that owns a Deployment's image should. The last apply pruned demo-two and nothing else: only labeled members of the ApplySet are candidates, unlike the older --prune -l, which could delete anything with the label. Helm 29,435 (Packaging BookNest with Helm) and Argo CD 126 (GitOps with Argo CD) do this job for whole applications.