How to Fix MicroK8s 'Snap Not Found' After Strict Confinement Change
Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 10–20 mins
TL;DR
- What broke: Changing MicroK8s from
classictostrictconfinement invalidates snap binary symlinks and AppArmor profiles, making themicrok8scommand and all sub-commands (kubectl,helm,dashboard-proxy) unreachable. - How to fix it: Refresh the snap with the correct confinement flag, regenerate aliases, and reload the AppArmor profile for the snap namespace.
- Shortcut: Use our Client-Side Sandbox above to paste your
snap listoutput andjournalctldump — it auto-generates the exact remediation script without sending your node data anywhere.
The Incident (What Does the Error Mean?)
You'll see one or more of these on the shell or in a CI runner:
bash: microk8s: command not found
error: cannot find snap "microk8s"
snap "microk8s" is not installed
AppArmor profile "snap.microk8s.microk8s" does not exist
The immediate consequence is total loss of cluster control-plane access. Every downstream process — Helm deployments, kubectl apply, health checks, ingress reloads — fails immediately. If this is a single-node production cluster, your workloads are still running but are now unmanageable. You cannot drain nodes, rotate secrets, or respond to a security incident.
The Attack Vector / Blast Radius
This is not a theoretical risk. Here is the cascading failure chain:
- Confinement mismatch: snapd tracks confinement mode (
classicvsstrict) in/var/lib/snapd/state.json. A manualsnap refresh microk8s --channel=latest/stablewithout specifying--classicon a previously classic-confined install silently migrates the confinement model. - Symlink rot:
/snap/bin/microk8sand all wrappers (microk8s.kubectl,microk8s.helm3) are regenerated by snapd post-refresh. If the AppArmor policy for the new strict profile fails to load, snapd rolls back the binary wrappers but leaves the state partially written — the symlinks exist but point to a broken mount namespace. - Blast radius: Any automation (cron jobs, Ansible playbooks, GitOps runners, Prometheus exporters using
microk8s kubectl) that calls the binary will throwcommand not foundor exit code1. In a GitOps pipeline this cascades to failed reconciliation loops, leaving your cluster in a drift state with no automated correction possible. - Security posture degradation: Strict confinement is the more secure mode — it uses seccomp and AppArmor to sandbox the snap. Reverting to classic to "fix" the error, as most Stack Overflow answers suggest, widens the attack surface by granting the snap unrestricted filesystem access. Do not do this in production.
How to Fix It
Step 1 — Confirm the actual state
snap list microk8s
snapd --version
systemctl status snapd
journalctl -u snapd --since "1 hour ago" | grep -i "microk8s\|apparmor\|error"
ls -la /snap/bin/microk8s
Look for: AppArmor profile load failed, cannot mount snap, or missing mount namespace.
Basic Fix — Force Refresh with Correct Confinement
- snap refresh microk8s
+ snap refresh microk8s --channel=1.30/stable --classic
⚠️ Only use
--classicif your original install was classic-confined. Check with:snap info microk8s | grep confinement
After refresh:
# Regenerate all aliases
sudo snap alias microk8s.kubectl kubectl
sudo snap alias microk8s.helm3 helm
# Verify binary is resolvable
which microk8s
microk8s status --wait-ready --timeout 60
Enterprise Best Practice — Strict Confinement with AppArmor Repair
If you are intentionally migrating to strict confinement (correct move for CIS benchmark compliance):
- snap install microk8s --classic
+ snap install microk8s --channel=1.30/strict
After install, if AppArmor profiles fail to load:
# Reload AppArmor profiles for the snap namespace
sudo apparmor_parser -r /var/lib/snapd/apparmor/profiles/snap.microk8s.*
# Force snapd to regenerate mount units
sudo systemctl restart snapd
sudo snap run --shell microk8s -c "exit 0" 2>&1
# Validate strict confinement is active
snap connections microk8s
For multi-node clusters managed via Ansible, pin the confinement in your role:
- name: Install MicroK8s
community.general.snap:
name: microk8s
- classic: false
+ classic: false
+ channel: "1.30/strict"
+ state: present
register: snap_result
+ name: Validate AppArmor profile loaded
+ command: apparmor_parser -r /var/lib/snapd/apparmor/profiles/snap.microk8s.microk8s
+ when: snap_result.changed
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Lock the snap channel in IaC
Never let snapd auto-refresh MicroK8s in production. Set a hold:
snap refresh --hold=forever microk8s
Or in /etc/snap/hold.json (snapd 2.58+):
{
"microk8s": {
"hold": "forever"
}
}
2. Add a pre-flight check to your CI pipeline
# .github/workflows/k8s-deploy.yml
- name: Verify MicroK8s binary and confinement
run: |
which microk8s || (echo "FATAL: microk8s not in PATH" && exit 1)
CONFINEMENT=$(snap info microk8s | awk '/confinement/{print $2}')
echo "Confinement mode: $CONFINEMENT"
[[ "$CONFINEMENT" == "strict" ]] || echo "WARN: Not running strict confinement"
microk8s status --wait-ready --timeout 30
3. OPA/Gatekeeper policy — enforce node-level snap integrity
Use a DaemonSet with a startup probe that validates the snap binary hash against a known-good SHA256 stored in a ConfigMap. Alert via Alertmanager if the binary checksum drifts — this catches both accidental upgrades and supply-chain tampering.
4. Checkov / Trivy in your Ansible pipeline
Run checkov -d ./ansible/roles/microk8s with a custom check that asserts classic: false is set and channel is pinned to a specific semver — not latest/stable.