DevOps

Docker 'OCI runtime create failed': a flag after the image

See why runc tries to execute -w, why the official node image now answers node: bad option: -w, and which cause each other create-time message and exit code points to.

Mahdi Ben RhoumaMahdi Ben Rhouma
9 min read

TL;DR#

docker run reads its own options only up to the image name; every word after the image is the command the container should start. In the question, node -w "/var/www" node npm start makes node the image and -w the program to run. On an image without an entrypoint, the runtime looks for an executable called -w, finds none and refuses to create the container, with exit code 127. Put every docker run option in front of the image and leave only the command, with its own options, after it: … -w "/var/www" node npm start. On the official node images the same mistake prints node: bad option: -w instead, because their entrypoint script hands a leading flag to Node. Same cause, same fix. A message that mentions permission denied, no such file or directory or not a directory instead has a different cause, mapped further down.

The error in 2017, and the same mistake today#

The question on Stack Overflow mounts a small Express project at /var/www and tries to start it with npm start from the official node image:

bash
docker run -p 8085:3000 -v /home/joel/workspace/plural_docker_webdev:/var/www node -w "/var/www" node npm start

In December 2017, Docker answered:

text
docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused "exec: \"-w\": executable file not found in $PATH": unknown.
ERRO[0000] error waiting for container: context canceled

The same mistake was reproduced on 6 October 2026 with Docker Desktop 4.89.0 (Docker Engine 29.7.2, containerd 2.3.3 and runc 1.4.3, on WSL 2). Run against alpine:3.22, an image with no entrypoint, with a project mounted at /var/www and ls as the command, it prints the current wording and exits with 127:

text
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: "-w": executable file not found in $PATH
 
Run 'docker run --help' for more information

The source location (container_linux.go:296) and the trailing : unknown are gone, and the CLI did not print the ERRO[0000] line. The quoted name after exec: is still the part that explains the failure. Without --rm, the failed container stays behind: docker ps -a lists it as Created, and docker inspect shows what Docker was asked to start:

text
State=created ExitCode=127 Path="-w" Args=["/var/www","ls"]

Delete it with docker rm once you have read it.

Why the runtime tries to execute -w#

Docker's running containers guide gives the general form as docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...], and the CLI applies that order literally. In run.go of Docker CLI 29.7.2, the run command calls flags.SetInterspersed(false). With that setting, the pflag parser stops at the first argument that is not a flag and keeps it, and everything after it, as positional arguments. run then takes the first of them as the image (args[0]) and the rest as the command (args[1:]). In the question, the first non-flag argument is node, so -w, /var/www, node, npm and start all become the container's command line. That is exactly what Path="-w" in the inspect output shows.

The runtime refuses that command before the container exists. Before runc reports a container as created, it resolves the first word of the command inside the container, and the comment above that lookup in runc 1.4.3 gives the reason: "Check for the arg before waiting to make sure it exists and it is returned as a create time error." containerd's shim prefixes runc's error with OCI runtime create failed, and the Docker daemon adds failed to create task for container. No process from the image ever ran.

The exit code sorts these failures before you read the text. Docker's exit status reference says 125 means "the error is with Docker daemon itself", 126 that "the specified contained command can't be invoked", and 127 that "the contained command can't be found". The CLI picks the code by reading the error message: toStatusError returns 127 when it contains executable file not found or no such file or directory, 126 when it contains permission denied or is a directory, and 125 for anything else.

Fix: Docker's options before the image, the command after it#

  1. Read the quoted name after exec:. A name that starts with a dash is an option written after the image. Either it is a docker run option, like -w here, which belongs in front of the image (step 2), or it was meant for the program inside the container, which then has to be named first (step 4).

  2. Move all the options in front of the image name. This is the corrected command from the top answer (score 33) on the question:

bash
docker run -p 8085:3000 -v /home/joel/workspace/plural_docker_webdev:/var/www -w "/var/www" node npm start
  1. Leave only the container's command after the image. Here that is npm start: the node image already provides Node, and npm start runs the start script of the mounted package.json. Keeping the question's second node (… -w "/var/www" node node npm start) gets past Docker, but asks Node to load a script called npm from the working directory. In the reproduction, that run stopped with exit code 1:
text
Error: Cannot find module '/var/www/npm'
  1. For an option meant for the program, name the program first. In node -e 'console.log(123)', the -e belongs to Node, whose CLI documentation describes it as "Evaluate the following argument as JavaScript". Moved in front of the image, it would become docker run's own -e, which docker run --help lists as "Set environment variables". With the entrypoint of node:24-alpine cleared (--entrypoint "") so that it behaved like an image without one, -e 'console.log(123)' after the image failed with exec: "-e": executable file not found in $PATH, while naming the program first printed 123. On the node image as it is, the same command also prints 123:
bash
docker run --rm node:24-alpine node -e 'console.log(123)'

A command this long is easy to scramble again. The Compose-based development setup writes the same port, bind mount and start command as separate ports, volumes and command keys of a docker-compose.yml.

Verify the container now starts#

The reproduction ran the corrected order with node:24-alpine (Node 24.21.0) against a minimal app: a package.json whose start script runs node server.js, and a server.js that listens on port 3000. Two details differ from the question, and the container name and host path are shortened here. Docker chose the host port (-p 0:3000 instead of a fixed 8085:3000), and the container ran detached with a memory cap:

bash
docker run -d --name web --memory=256m -p 0:3000 -v "$PWD:/var/www" -w /var/www node:24-alpine npm start
docker logs web
docker port web 3000

docker logs showed npm running the script, then the server:

text
> oci-demo@1.0.0 start
> node server.js
 
listening on 3000

docker port reported 0.0.0.0:32773, and curl -sS http://127.0.0.1:32773/ returned ok. docker inspect confirms what the container runs now:

text
Path="docker-entrypoint.sh" Args=["npm","start"] WorkingDir="/var/www"

Path is a script from the image rather than npm, and that script is also why today's node image reports the original mistake differently.

Why the node image now prints node: bad option: -w#

Run the question's command against today's node image, with only the tag, the host port and the host path changed, and --rm, a container name and a memory cap added, and the OCI error does not appear. With node:24-alpine, the reproduction printed one line and exited with code 9:

text
node: bad option: -w

The official images run an entrypoint script, docker-entrypoint.sh, added by nodejs/docker-node PR #1043, merged on 4 June 2019. It answered issue #1039, titled Add ENTRYPOINT ["node"], whose example docker run --rm node -e 'console.log(123)' hit the same OCI error with -e. There, -e was meant for Node, not for Docker. The script puts node in front of the command when the first argument starts with a dash, is not a command the shell can find, or is a file without the execute bit:

sh
if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then
  set -- node "$@"
fi
 
exec "$@"

So -w "/var/www" node npm start becomes node -w /var/www node npm start, and Node rejects -w. docker image inspect shows Entrypoint=["docker-entrypoint.sh"] on node:24-alpine, node:24 and node:22 alike, and the issue's example now prints 123, because the script hands -e to Node. Clearing the entrypoint brings the runtime error back: adding --entrypoint "" to that same run, and changing nothing else, gave exec: "-w": executable file not found in $PATH and exit code 127 again.

Whether the misplaced flag reaches runc (an image with no entrypoint, such as alpine) or the image's entrypoint (node), the fix is the same. How ENTRYPOINT, CMD and the --entrypoint flag combine is covered in overriding an entrypoint inherited from a base image.

The other endings of "unable to start container process"#

The four endings below share the whole prefix, up to error during container init:. The clause after it names the cause, and the exit code follows from that clause. Each one was reproduced on the same setup, together with its fix.

exec: "bash": executable file not found in $PATH, exit code 127. The name is a real program that the image does not ship. alpine:3.22 has no bash, and sh -c '…' ran in the same image. Finding out what an image contains, and bringing in a tool it lacks, is covered in the docker exec variant of this error.

exec: "/entrypoint.sh": permission denied, exit code 126. The file exists but has no execute bit. A script written with chmod 644 failed this way, and the same Dockerfile with chmod 755 printed entrypoint ran. Set the mode inside the image, after the copy:

dockerfile
FROM alpine:3.22
COPY entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

exec: "/var/www/start.sh": stat /var/www/start.sh: no such file or directory, exit code 127. The path does not exist inside the container. One way to get there is close to the question's setup: the image keeps its start script in the directory your project is mounted over. Docker's bind mount documentation says that in this case "the pre-existing files are obscured by the mount". An image whose CMD was /var/www/start.sh ran on its own, then failed with this message once an empty host directory was mounted on /var/www. With the script moved to /usr/local/bin, the same mount worked. A React app that loses public/index.html under Compose hits the same masking from inside the app.

not a directory: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type, exit code 125. The host path and the container path are of different types. The daemon appends this hint to any not a directory error, and the comment above that code in daemon/errors.go names both directions: "attempted to mount a file onto a directory, or a directory onto a file". A -v source that does not exist on the host is one way to get there. The bind mount syntax section explains that Docker then "automatically creates the directory on the host for you. It's always created as a directory." When the target in the image is a file (/etc/alpine-release in the reproduction), runc cannot mount that new directory onto it. The reverse, a host file mounted onto a directory of the image (/mnt), failed with the same hint. Earlier in the same message, error mounting names the host path and the container path, so it tells you which mount to check. After the first failure, the missing path existed on the host as an empty directory, and a second run of the same command failed the same way. Deleting that directory, creating the file and re-running the unchanged command printed the file's contents. The code is 125 because the message contains none of the strings toStatusError looks for.

Look-alikes, and the memory theory from the second answer#

exec /entrypoint.sh: no such file or directory, exit code 255, with no OCI prefix. This one is not a create failure. Here the script exists and is executable, so runc's create-time lookup passes. The failure comes later, when the kernel tries to start the interpreter named on the script's first line, and runc's init process prints that error and exits with 255. Two causes produced this exact line in the reproduction. With Windows line endings, od -c shows the first line as # ! / b i n / s h \r \n. The kernel's script loader ends the interpreter name at the newline, trimming only spaces and tabs, so it looks for /bin/sh followed by a carriage return. With #!/bin/bash on Alpine, the interpreter is simply not installed. Both match the ENOENT case of execve(2): "path or a script or ELF interpreter does not exist". The same script saved with LF endings and #!/bin/sh printed entrypoint ran.

exec /entrypoint.sh: exec format error, exit code 255, again with no OCI prefix. The file exists and is executable, but the kernel cannot run it: execve(2) returns ENOEXEC when an executable "is not in a recognized format, is for the wrong architecture, or has some other format error that means it cannot be executed". An entrypoint script with no #! line is one such file: the same script as above, without its first line, failed with exactly this message. A binary built for another CPU architecture is the other cause, not reproduced here. The Fargate walkthrough covers both, an image built for the wrong architecture and a shell script entrypoint with no shebang line.

Memory, as in the second answer. The second answer on the question reports that the error went away after Docker was given 4GB instead of the recommended minimum of 2GB, plus a reboot. It does not show its error message, and memory does not produce exec: "-w". The misplaced flag failed the same way under --memory=1g as under --memory=64m, and moving the flag fixed it under the smaller cap. runc does have a message for a container whose init process is killed for lack of memory, container init was OOM-killed (memory limit too low?), in process_linux.go. Unless your error says that, more memory is not the fix. The smallest limit Docker accepts is --memory=6m, according to its resource constraints page, and it did not trigger that message for alpine:3.22 true here.

When the corrected run stops on the port instead#

With the order fixed, the container gets created, and -p 8085:3000 becomes the next thing that can stop it. If the corrected run fails on port is already allocated or address already in use, the problem has moved to host port 8085. The walkthrough for a taken PostgreSQL port tells those messages apart, along with a third one that Docker Desktop for Windows can give, ports are not available, and shows how to find what holds the port on Linux, macOS and Windows; only the port number changes.

OCI runtime create failed means the container was never started, and only the last clause explains why. A quoted name that begins with a dash is an option written after the image: move a docker run option in front of the image, and name the program before an option that was meant for it. A real program name, a script path or a mount path points at the image or the host instead, and the exit code (127 for a missing command or path, 126 for one that cannot be executed, 125 for the rest) narrows the search before you read further.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.