AI disclosure: This post was written with assistance from an AI (ChatGPT).
Privacy note: All user-specific paths have been generalized (use$HOMEinstead of real usernames). Avoid posting machine-specific secrets from your shell files.
TL;DR
Docker completions worked in my interactive shell, but Docker Desktop kept showing “Unable to find Docker zsh completion in your FPATH.”
The fix was to make the completions available in a path that non-interactive shells can see, and ensure that path is in FPATH very early (via ~/.zshenv).
Quick fix (Option A—what solved it for me):
# 1) Put completion files where Docker expects
mkdir -p "$HOME/.docker/completions"
docker completion zsh > "$HOME/.docker/completions/_docker"
docker compose completion zsh > "$HOME/.docker/completions/_docker-compose" 2>/dev/null || true
# 2) Make FPATH visible to non-interactive shells (put in ~/.zshenv)
# ~/.zshenv (keep this file minimal—no heavy logic)
export FPATH="$HOME/.docker/completions:/opt/homebrew/share/zsh/site-functions:${FPATH:-}"
# 3) Refresh completion cache and restart shell
command rm -f "$HOME"/.zcompdump* 2>/dev/null
exec zsh -l
Alternative (Option B—UI-friendly too): also install to Homebrew’s site-functions:
BREW_PREFIX="$(brew --prefix)"
mkdir -p "$BREW_PREFIX/share/zsh/site-functions"
docker completion zsh > "$BREW_PREFIX/share/zsh/site-functions/_docker"
docker compose completion zsh > "$BREW_PREFIX/share/zsh/site-functions/_docker-compose" 2>/dev/null || true
chmod -R go-w "$BREW_PREFIX/share/zsh"
command rm -f "$HOME"/.zcompdump* 2>/dev/null
exec zsh -l
Either approach makes Docker Desktop happy and keeps completions working.
Symptoms
- In Terminal/iTerm, pressing TAB on
dockerordocker composeworked. - But Docker Desktop → Settings → General → “Configure shell completions” (or similar) kept showing: “Unable to find Docker zsh completion in your FPATH.”
What’s actually going on
-
Interactive shells (your usual Terminal) read
~/.zshrc, runoh-my-zsh,compinit, etc. Completions can be loaded from places like~/.oh-my-zsh/cache/completions/_docker. -
Non-interactive shells (like checks run by apps using
/bin/zsh -lc '...') often do not read~/.zshrc. They usually read only~/.zshenv. If yourFPATHis only set in~/.zshrc, that non-interactive check won’t see the_dockerfile—hence the warning.
Bottom line: Docker Desktop’s detection is conservative. If _docker isn’t visible to a non-interactive zsh, you’ll get that message—even if completions work fine interactively.
The fix I used (Option A)
- Create the completion files where Docker commonly looks:
mkdir -p "$HOME/.docker/completions"
docker completion zsh > "$HOME/.docker/completions/_docker"
docker compose completion zsh > "$HOME/.docker/completions/_docker-compose" 2>/dev/null || true
If you still use the legacy
docker-composebinary:command -v docker-compose >/dev/null && \ docker-compose completion zsh > "$HOME/.docker/completions/_docker-compose"
-
Expose the path to non-interactive shells by editing
~/.zshenv:
# ~/.zshenv (keep this tiny—env only)
export FPATH="$HOME/.docker/completions:/opt/homebrew/share/zsh/site-functions:${FPATH:-}"
⚠️
~/.zshenvis read by every zsh invocation. Don’t put heavy logic or commands here—only lightweight environment setup.
- Rebuild completion cache and restart a login shell:
command rm -f "$HOME"/.zcompdump* 2>/dev/null
exec zsh -l
- Verify:
# Show every candidate file named _docker across fpath
print -rl -- $^fpath/_docker(.N)
# After triggering a completion at least once:
whence -v _docker # should print “... from .../_docker”
Alternative (Option B): install into Homebrew site-functions
Many setups include $(brew --prefix)/share/zsh/site-functions in fpath by default. Placing _docker there tends to satisfy both interactive and non-interactive checks:
BREW_PREFIX="$(brew --prefix)"
mkdir -p "$BREW_PREFIX/share/zsh/site-functions"
docker completion zsh > "$BREW_PREFIX/share/zsh/site-functions/_docker"
docker compose completion zsh > "$BREW_PREFIX/share/zsh/site-functions/_docker-compose" 2>/dev/null || true
chmod -R go-w "$BREW_PREFIX/share/zsh"
command rm -f "$HOME"/.zcompdump* 2>/dev/null
exec zsh -l
Sanity checks & useful one-liners
# Where will zsh look for the _docker file?
print -rl -- $^fpath/_docker(.N)
# After you hit TAB on `docker`, what file is actually sourced?
whence -v _docker
# (If you’re curious) emulate a non-interactive check like apps do:
# Note: this won’t run compinit unless you call it.
# 1) Just check files exist in $fpath (no compinit):
/bin/zsh -lc 'print -rl -- $^fpath/_docker(.N) || echo "no _docker in fpath"'
# 2) Or explicitly run compinit first, then ask whence:
/bin/zsh -lc 'autoload -Uz compinit; compinit -i; whence -v _docker || echo NO__docker'
Gotchas I hit (and how to avoid them)
grep≠rgflags: If you aliasgrep='rg', note thatrg -Emeans “encoding,” not “extended regex.”
Useegrep='rg'andfgrep='rg -F'.Safety aliases vs real
rm: If you aliasrm='trash', commands likerm -fwill fail.
Usecommand rmor\rmwhen you truly want realrm.forloop syntax in zsh: Don’t forgetdo … done.
for d in $fpath; do [[ -r $d/_docker ]] && echo "$d/_docker"; done
-
Load order: It’s conventional to source
zsh-syntax-highlightinglast to avoid clobbering by later plugins.
Appendix: Which zsh files run when?
-
~/.zshenv— read by all invocations (interactive, non-interactive, login, non-login). Keep it tiny and safe. -
~/.zprofile— read by login shells. -
~/.zshrc— read by interactive shells (your usual Terminal tabs). -
~/.zlogin— after~/.zprofilefor login shells.
Docker Desktop (or other apps) often check using /bin/zsh -lc '...', which does not read your ~/.zshrc. Hence putting FPATH in ~/.zshenv ensures the completion files are discoverable in that scenario.
Final notes
- If everything completes on TAB, you’re functionally fine—even if some UI still looks grumpy.
- For posting config snippets on the internet, redact usernames and avoid sharing secrets. Prefer
$HOMEand$BREW_PREFIXin examples.
If this helped you, someone else wrestling with zsh and Docker Desktop will probably appreciate it too. Happy hacking! 🐳🧰
Top comments (0)