Fix: bash: command not found
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix bash command not found error caused by missing PATH, uninstalled packages, wrong shell, typos, missing aliases, and broken symlinks on Linux and macOS.
The Error
You type a command in the terminal and get:
bash: docker: command not foundOr variations:
zsh: command not found: node-bash: terraform: command not foundsh: kubectl: not foundThe shell cannot find the program you are trying to run. The command either is not installed, or is installed but not in a directory listed in your PATH environment variable.
Why This Happens
When you type a command like docker, the shell searches through directories listed in the PATH variable for an executable file named docker. If it does not find one, it reports command not found.
The shell’s lookup is purely textual: it walks each directory in PATH from left to right and stops at the first executable matching the command name. There is no caching across shells, and no fallback to “maybe it exists in /opt anyway.” If the directory holding the binary is not in PATH, the command does not exist as far as your shell is concerned. The same binary that worked an hour ago can break the moment something modifies PATH — a source ~/.bashrc that conditionally drops a directory, a base image bump that changes default PATH, a sudo invocation that uses secure_path instead of your user PATH.
In production containers, the failure surfaces immediately at process startup. In interactive sessions, it often shows up after an apparently unrelated change: you installed a new shell config, switched from bash to zsh, started a Docker build using a different base image, or your team migrated CI runners from Ubuntu 22.04 to 24.04.
Common causes:
- The program is not installed. You need to install it first.
- The program is installed but not in PATH. The binary is somewhere on the system, but the directory is not in PATH.
- You just installed it but the shell is not refreshed. The shell cached the old PATH. A new terminal or
source ~/.bashrcis needed. - Wrong shell. You configured PATH in
~/.bashrcbut you are using zsh (which reads~/.zshrc). - Typo. You misspelled the command name.
- Broken symbolic link. A symlink in
/usr/local/binpoints to a deleted file. - Sudo environment. Running with
sudouses a different PATH.
In Production: Incident Lens
In production, command not found inside a container is a deploy-time outage. The container starts, the entrypoint tries to run node, python, wkhtmltopdf, or some other CLI tool that used to be present, and the process exits immediately with code 127. Kubernetes marks the pod as CrashLoopBackOff. If the health check is correctly wired up, the deploy never reaches the ready state and the old pods keep serving traffic — minimal user impact, loud alerts. If the health check is wired up loosely (TCP probe only, or HTTP probe against a different process), the new pods can register as ready before the missing command bites, and you lose traffic to dead containers.
The single most common trigger is a base image bump. You change FROM node:20-alpine to FROM node:22-alpine and the new image drops python3 from its default install, or moves git to a different package. Your build still passes because the missing tool is only invoked at runtime by some post-deploy task. The second most common trigger is a multi-stage Dockerfile where the runner stage was supposed to copy a binary from the builder stage but the COPY --from=builder path was mistyped.
The blast radius is one Deployment / one StatefulSet — but since modern orchestrators replace pods together during a rollout, the blast can cascade across the entire service tier if there is no canary. A CrashLoopBackOff on a single pod is recoverable; a CrashLoopBackOff on every pod after a rollout means hard outage.
The monitoring signal that catches this within seconds is the deploy health check failing. kubectl rollout status blocks until pods are ready and times out on this failure mode. A Prometheus alert on kube_pod_status_phase{phase="Failed"} or rate(kube_pod_container_status_restarts_total[5m]) > 0 after a deploy SHA change is the production-grade signal. At the application layer, an HTTP readiness probe that genuinely exercises the missing command (/health running a tiny git --version check) will catch this on the first pod, not after every pod has failed.
Recovery sequence: the first action is roll back the deploy. kubectl rollout undo deployment/foo restores the previous image and stops the bleeding within a minute. Then kubectl exec into a temporarily-running old pod to confirm the command exists there, docker run --rm -it <new-image> which <command> to confirm it is missing in the new image, then trace the Dockerfile change that dropped it. Fix the Dockerfile (re-add the package, fix the COPY --from), rebuild, redeploy.
Postmortem preventive: pin base images to immutable SHA digests, not floating tags. FROM node@sha256:abc... cannot silently change beneath you the way FROM node:22-alpine can. Add a startup smoke test to the container’s entrypoint that runs command -v <every required tool> || exit 1 before forking the main process — that turns a runtime crash hours into the deploy into a hard fail at container start, where the orchestrator catches it immediately. Add a CI step that runs docker run --rm <image> /bin/sh -c 'command -v node && command -v git' against every new image so you discover the missing tool before the deploy.
Fix 1: Install the Program
The program might simply not be installed. Install it with your system’s package manager:
Ubuntu/Debian:
sudo apt update
sudo apt install <package-name>Fedora/RHEL/CentOS:
sudo dnf install <package-name>macOS (Homebrew):
brew install <package-name>Arch Linux:
sudo pacman -S <package-name>Common packages and their install commands:
| Command | Ubuntu/Debian | macOS (Homebrew) |
|---|---|---|
docker | sudo apt install docker.io | brew install docker |
node | sudo apt install nodejs | brew install node |
python3 | sudo apt install python3 | brew install python3 |
git | sudo apt install git | brew install git |
curl | sudo apt install curl | Pre-installed |
wget | sudo apt install wget | brew install wget |
terraform | HashiCorp repo | brew install terraform |
kubectl | Kubernetes docs | brew install kubectl |
If apt cannot find the package, see Fix: apt-get unable to locate package.
Fix 2: Add the Binary to PATH
The program is installed but its directory is not in PATH:
Find where the program is:
find / -name "docker" -type f 2>/dev/null
which docker
whereis dockerCommon directories not in PATH by default:
~/.local/bin— pip-installed Python tools/usr/local/go/bin— Go installation~/go/bin— Go workspace binaries~/.cargo/bin— Rust/cargo binaries~/.nvm/versions/node/v20.x/bin— NVM-managed Node.js/snap/bin— Snap packages/opt/<program>/bin— Optional software
Add a directory to PATH:
export PATH="$PATH:/path/to/directory"Make it permanent. Add the export line to your shell configuration file:
For bash (~/.bashrc or ~/.bash_profile):
echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc
source ~/.bashrcFor zsh (~/.zshrc):
echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.zshrc
source ~/.zshrcPro Tip: Check which shell you are using with
echo $SHELL. If it says/bin/zsh, edit~/.zshrc. If it says/bin/bash, edit~/.bashrc. Editing the wrong file is the #1 reason PATH changes “don’t work.”
Fix 3: Refresh Your Shell Session
After installing a program or modifying PATH, the current shell might have a cached lookup table:
# Reload shell config
source ~/.bashrc # for bash
source ~/.zshrc # for zsh
# Or clear the command hash table
hash -rOr simply open a new terminal window. New terminals always read the latest configuration.
SSH sessions: If you installed something and then SSH’d in, the SSH session might have a cached PATH. Disconnect and reconnect.
Fix 4: Fix sudo PATH Issues
Commands that work without sudo might fail with sudo:
node --version # Works
sudo node --version # command not foundsudo uses a restricted PATH (defined by secure_path in /etc/sudoers). Your user PATH additions are not available.
Fix: Use the full path:
sudo /home/user/.nvm/versions/node/v20.12.0/bin/node --versionFix: Preserve PATH with sudo:
sudo env "PATH=$PATH" node --versionFix: Edit sudoers to include the path (permanent):
sudo visudoFind the secure_path line and add your directory:
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"Fix 5: Fix Version Manager Commands
Version managers (nvm, pyenv, rbenv, sdkman) install binaries in custom directories and require initialization:
Node.js (nvm):
# Add to ~/.bashrc or ~/.zshrc:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"Then:
source ~/.bashrc
nvm install 20
node --versionPython (pyenv):
# Add to ~/.bashrc or ~/.zshrc:
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"For Python-specific command not found issues, see Fix: python command not found.
Ruby (rbenv):
eval "$(rbenv init -)"Common Mistake: Installing a version manager but forgetting to add its initialization to your shell config. The manager installs successfully, but its commands are not available in new terminal sessions. Always follow the post-install instructions.
Fix 6: Fix Broken Symbolic Links
A broken symlink in /usr/local/bin or /usr/bin can cause command not found even though ls shows the file:
ls -la /usr/local/bin/terraform
# lrwxrwxrwx 1 root root 35 Jan 1 /usr/local/bin/terraform -> /opt/terraform/0.14/terraform
# The target might not exist anymoreCheck if the link is broken:
file /usr/local/bin/terraform
# /usr/local/bin/terraform: broken symbolic link to /opt/terraform/0.14/terraformFix: Remove the broken link and recreate it:
sudo rm /usr/local/bin/terraform
sudo ln -s /opt/terraform/1.7/terraform /usr/local/bin/terraformOr reinstall the program to recreate the link automatically.
Fix 7: Fix Commands Inside Scripts
If a command works in your terminal but fails in a script:
#!/bin/bash
docker compose up # command not foundThe script uses a different PATH. Add the PATH at the top of the script:
#!/bin/bash
export PATH="/usr/local/bin:$PATH"
docker compose upOr use the full path:
#!/bin/bash
/usr/local/bin/docker compose upCheck the shebang line. #!/bin/sh uses a minimal POSIX shell, not bash. Some features and PATH settings might differ:
#!/bin/bash # Use bash
#!/usr/bin/env bash # More portable — finds bash in PATHIf the script has permission issues, see Fix: bash permission denied.
Fix 8: Fix Docker Container Commands
Inside Docker containers, commands available on the host are not automatically available:
docker exec -it mycontainer bash
# Inside container:
curl http://example.com
# bash: curl: command not foundFix: Install the tool in the Dockerfile:
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl wget vimOr for Alpine-based images:
FROM alpine:3.19
RUN apk add --no-cache curl wgetIf the Docker daemon itself is unreachable rather than the command being missing, that is a separate failure mode — check systemctl status docker and the daemon logs.
Still Not Working?
If the command is still not found:
Check for typos:
dcoker --version # Typo: "dcoker" instead of "docker"
pytohn --version # Typo: "pytohn" instead of "python"Check for aliases. Some commands are aliases defined in your shell config:
alias | grep <command>
type <command>If the command is an alias and you’re in a script, aliases might not be loaded. Use the full command name.
Check if the command is a shell builtin:
type cd # cd is a shell builtin
type echo # echo is a shell builtinBuiltins are part of the shell, not external programs. They are always available.
Check for architecture mismatch. On Apple Silicon Macs (M1/M2/M3), x86 binaries need Rosetta. On ARM Linux, x86 binaries do not run natively. Check the binary architecture:
file $(which <command>)Check snap and flatpak. If the program was installed via snap or flatpak, it might be in a different location:
snap list
flatpak listCheck container base-image PATH drift. Compare the default PATH between the old and new base images: docker run --rm <old-image> printenv PATH and the same for the new image. Some minimal base images (distroless, scratch, slim variants) ship a stripped PATH that excludes /usr/local/bin. If your binary was installed there, you need to either reinstall it under /usr/bin or extend PATH in your Dockerfile with ENV PATH=/usr/local/bin:${PATH}.
Check for AppArmor or SELinux blocking execution. A binary that exists in PATH and is executable can still report Permission denied (sometimes mistranslated as not found by older shells) when AppArmor or SELinux blocks execution. Run dmesg | tail after the failed command; look for apparmor="DENIED" or avc: denied lines naming the binary. The fix is to update the policy, not the PATH.
Check for a deleted-but-cached binary. If you uninstalled and reinstalled a tool and the old absolute path is cached in your shell’s command hash table, you can get command not found even though the binary now exists. Run hash -r (bash) or rehash (zsh) to clear the cache, then retry.
For SSH connection issues that prevent you from reaching the server where the command is installed, see Fix: SSH connection timed out.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: EMFILE Too Many Open Files / ulimit Error on Linux
How to fix EMFILE too many open files errors on Linux and Node.js — caused by low ulimit file descriptor limits, file handle leaks, and how to increase limits permanently.
Fix: Cron Job Not Running on Linux
How to fix cron jobs not running on Linux — caused by PATH issues, missing newlines, permission errors, environment variables not set, and cron daemon not running.
Fix: sudo: command not found on Linux
How to fix 'sudo: command not found' on Linux — caused by sudo not installed, missing PATH in scripts, Docker containers without sudo, and su vs sudo confusion on minimal systems.
Fix: Valkey Not Working — Redis Client Compatibility, ACL, Cluster Mode, and Migration
How to fix Valkey errors — client connection refused, RESP protocol compatibility, ACL user setup, cluster slot reshard, persistence config (RDB/AOF), TLS, Sentinel mode, and migrating from Redis.