AI disclosure: This post was written with assistance from an AI (ChatGPT).
Privacy note: All user-specific paths have been generalized (use$HOME
instead 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
docker
ordocker compose
worked. - 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 yourFPATH
is only set in~/.zshrc
, that non-interactive check won’t see the_docker
file—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-compose
binary: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:-}"
⚠️
~/.zshenv
is 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
≠rg
flags: If you aliasgrep='rg'
, note thatrg -E
means “encoding,” not “extended regex.”
Useegrep='rg'
andfgrep='rg -F'
.Safety aliases vs real
rm
: If you aliasrm='trash'
, commands likerm -f
will fail.
Usecommand rm
or\rm
when you truly want realrm
.for
loop 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-highlighting
last 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~/.zprofile
for 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
$HOME
and$BREW_PREFIX
in examples.
If this helped you, someone else wrestling with zsh and Docker Desktop will probably appreciate it too. Happy hacking! 🐳🧰
Top comments (0)