Docker OCI runtime exec failed comes from docker exec, not from starting a container, so to fix docker exec failures you look at the command you asked for and at the state of the container, never at the build. The runtime found the container and could not run the thing you named inside it.

The recorded run: the same running container accepts an sh exec and refuses a bash exec. The container never changed, the command did.

Exec errors and create errors read alike and mean different things. The word exec after OCI runtime is what tells you which one you have.
What this error means
A step that pokes at an already running container fails, while the container itself keeps running and the rest of the job looks healthy. The line names the command in quotes and ends in "executable file not found in $PATH", and on older daemons it ends with a further ": unknown". Our recorded run exited 127. The most common version of this in CI is a script that reaches for bash inside an Alpine image, which ships sh and ash and no bash at all, and the same run below shows an sh exec working seconds earlier in the same container.
```Actions log, exec step, Docker 29.7.2
--- docker exec exec-demo sh -c 'echo alpine has sh'
alpine has sh
--- docker exec exec-demo bash -lc 'echo unreachable'
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH
## Common causes
### The binary is not in the image
The ordinary case, and the one the recorded run shows. `bash` on Alpine is the classic, but curl, ps, git and even sh go the same way on smaller bases. The message names the command in quotes, so the diagnosis is in the error; what it cannot tell you is that the tool was never there rather than removed.
### The container is not running any more
Exec needs a live PID 1 to join. A service that exited on a bad config, a compose service in a restart loop, or a container from an earlier step that has already finished all produce a failure at exec time that is really a startup failure. In our experience this is the half of the error people spend the longest on, because the logs they read are the exec logs.
### The binary is there and not on PATH
A tool installed into an unusual prefix resolves only by absolute path, and an exec that names it bare gets the same "not found" as a tool that does not exist. The PATH inside the container is the image PATH, which is not the PATH of the step that ran the exec.
### The command was a shell line rather than an executable
A pipe, a redirect or a quoted chain needs a shell to interpret it. Docker documents this directly: the command must be an executable, and a chained or quoted command does not work. Wrap it in `sh -c` and the same line runs.
## How to fix it
### Exec a shell the image actually ships
1. Use `sh` by default; reach for bash only in images you know carry it.
2. If you need bash for real, install it in the image rather than hoping.
3. Check once with `command -v`, which tells you before the pipeline does.
```Terminal
docker exec app sh -c 'command -v bash || echo "no bash in this image"'
# in the Dockerfile, if you decide you need it
# RUN apk add --no-cache bash
Prove the container is running first
Inspect the status and dump the logs when it is not what you expect. This turns an exec error into the startup error that caused it, which is the thing you actually need to fix, and it costs one step.
docker ps --filter name=app --format '{{.Names}} {{.Status}}'
docker inspect -f '{{.State.Status}} {{.State.ExitCode}}' app
Name the binary by absolute path when PATH is in doubt
An absolute path removes the resolution question entirely. Find it once inside the container, then use it in the workflow, and keep the discovery command in the job so a future image change is visible rather than silent.
docker exec app sh -c 'command -v mytool || ls -l /usr/local/bin'
docker exec app /usr/local/bin/mytool --version
Wrap shell syntax in a shell
Pipes, redirects, globs and && are shell features, so they need a shell process. Pass the whole line to sh -c as one argument, and quote it so the outer shell in your workflow leaves it alone.
docker exec app sh -c 'pg_isready -h localhost && echo ok'
How to prevent it
- Standardize on
shfor anything you exec in CI, whatever the base image is. - Check container state before every exec in a job, and print logs when it is down.
- Bake the tools your scripts need into the image, rather than into the assumption.
- Pin the base image, so a shell that is present today is still present next month.
Exec failed and create failed are different failures
Both wordings start with "OCI runtime" and both end with a missing executable, which is why they get treated as one problem and fixed in the wrong place. The word after the runtime name is the whole diagnosis. "exec failed" means an already running container was asked to run something extra. "create failed" means the container itself could not be started, which is failed to create shim task and a different fix.
The Docker documentation adds the second half of the diagnosis: the command you exec "only runs while the container's primary process (PID 1) is running", and "the command must be an executable", because a chained or quoted command does not work without a shell around it.
| What the line says | What to check first |
|---|---|
OCI runtime exec failed |
The binary you named, and whether the container is still up |
OCI runtime create failed |
The image CMD or ENTRYPOINT, because the container never started |
executable file not found in $PATH |
Whether that tool is in the image at all, not whether it is on the host |
no such file or directory on a path you gave |
The absolute path inside the container, and the architecture of the binary |
The image decides which shell you get
A slim base image is a deliberate choice to ship less, and bash is one of the things it ships less of. Alpine gives you sh, provided by BusyBox; distroless images may give you no shell at all; a scratch image has nothing but your binary. A script written against bash works on the developer laptop, where the image was never the environment, and fails the first time CI runs it against the real container.
Write the exec against the smallest shell you can rely on, or install the one you need in the image on purpose. Both are fine; guessing is not.
# works on Alpine, Debian and most slim images
docker exec app sh -c 'echo "$PATH"'
# only if the image really has bash
docker exec app bash -lc 'echo "$PATH"'
Half of these are a container that already exited
The other reading of the same failure is that there is nothing left to exec into. A service container that crashed on startup, a compose service still in its restart loop, or a container started in an earlier step that finished while the workflow moved on all leave you execing into a container id that is no longer running a process.
Check the state before the exec rather than after the error, and print the logs when it is not up. Two lines turn a confusing runtime error into the startup failure it actually is.
- name: The container has to be up before we exec
run: |
state=$(docker inspect -f '{{.State.Status}}' app)
echo "app is $state"
[ "$state" = running ] || { docker logs --tail 50 app; exit 1; }
docker exec app sh -c 'echo ready'
What the runner does about it
No repair, and none is claimed. On the recorded run the wrapper posted the failure to the sidecar and the sidecar answered, and nothing was repaired: the image had no bash before the diagnosis and it had no bash after. Latchkey carries no pattern for this failure, which is correct, because the missing binary is inside an image the runner does not build.
What a runner can do is make the failure cheap to read. The run above prints the container state and a working exec next to the failing one, so the log answers the first two questions anybody asks. Put the same two lines in your own job and this error stops needing a page.
FAQ
Why does docker exec bash fail on Alpine?
Because Alpine has no bash. It ships BusyBox, which provides sh and ash, and bash is a separate package nobody installed. The runtime resolves the name you gave against the container PATH, finds nothing, and reports the executable as not found. aquasecurity/trivy-action#415 is the same line reported against a GitHub Action that expected bash.
What is the difference between OCI runtime exec failed and create failed?
Exec failed comes from docker exec against a container that is already running: the container is fine and the command is not. Create failed comes from starting the container at all, so the image CMD or ENTRYPOINT is what cannot be run. The fix is in the workflow for the first and in the image for the second.
Why does the exec fail when the tool is clearly installed?
Because it is installed somewhere the container PATH does not cover, or it was installed in a builder stage that the final image does not carry. Run command -v inside the container to see what the container can resolve, and use the absolute path if the tool lives outside the standard directories.
Can I exec into a container that has stopped?
No. Docker documents that the command runs only while the container primary process is running, so a stopped or restarting container has no process namespace to join. Start a new container from the same image with the command you wanted, or read the logs of the one that exited.
Top comments (0)