✦ For everyone, free.

Practical knowledge for real and everyday life

Home

16.2.3.4 Executable Bit Missing

A focused guide to Executable Bit Missing, connecting core concepts with practical Docker and container operations.

Executable bit missing is a specific, narrow but frequently encountered cause of a "permission denied" error at container startup, where a script or binary genuinely has the wrong file mode, lacking the executable permission bit entirely, and the most useful aspect of this particular problem is that it has a small, well-understood set of root causes, almost all related to how the file's permissions were set, or lost, before it ever reached the Docker build.

Confirming the bit is actually missing

Before assuming this is the cause, checking the file's actual mode directly inside the built image confirms or rules it out immediately:

docker run --rm my-api:1.4.0 ls -la /entrypoint.sh
-rw-r--r-- 1 root root 245 Jun 1 12:00 /entrypoint.sh

The absence of any x in the permission string (-rw-r--r-- rather than -rwxr-xr-x) confirms directly and unambiguously that the executable bit is genuinely missing, rather than this being some other category of permission or path issue.

The most common cause: git not tracking the executable bit correctly

Git tracks a file's executable bit as part of its own metadata, but this tracking can be lost or never set correctly in several common scenarios, particularly when a file was created or edited on a system or through a tool that does not preserve executable permissions consistently:

git diff --summary
mode change 100644 => 100755 entrypoint.sh

Running this command after manually setting the executable bit locally and before committing shows whether git correctly recognizes and will track the change; if a file consistently loses its executable bit across commits despite repeatedly chmod'ing it locally, the underlying repository's core.fileMode setting or the specific tool used to create or edit the file is worth investigating directly.

git update-index --chmod=+x entrypoint.sh
git commit -m "Make entrypoint.sh executable"

Explicitly updating git's tracked mode for the file, rather than relying on the local filesystem's bit alone, ensures the executable status is actually committed and will be preserved when the repository is cloned elsewhere, including by whatever system performs the Docker build.

Windows checkouts losing executable bits

A particularly common scenario is a repository cloned or checked out on Windows, where the native filesystem has no concept of a Unix executable bit at all, which can result in git either losing track of the bit or a CI system checking out the repository on a Windows-based runner producing files without the bit set, even though the same repository checked out on Linux or macOS would have it correctly:

git config core.fileMode false

This specific git configuration, occasionally set to avoid noisy mode-change diffs caused by cross-platform development, has the side effect of git ignoring executable bit changes entirely, which can mean a file's bit, once lost on a Windows checkout, never gets corrected even after being properly set on a Linux or macOS machine, since git is configured to ignore mode changes altogether.

COPY behavior and the build context

Docker's COPY instruction generally preserves the executable bit of files as they exist in the build context at the time of the build, which means if the bit is missing in the actual files being sent as context (because of one of the causes above), COPY faithfully reproduces that missing bit into the image rather than correcting it:

COPY entrypoint.sh /entrypoint.sh
ls -la entrypoint.sh

Checking the file's mode directly in the build context, immediately before building, isolates whether the problem already exists in the source files being copied or is somehow introduced by the COPY instruction itself, which is a useful distinction since the latter would point toward a different, considerably less common category of cause.

The reliable fix: setting the bit explicitly in the Dockerfile

Regardless of the underlying reason the bit was lost upstream, explicitly setting it as part of the build is the most reliable, self-contained fix, since it does not depend on the source repository, the checkout process, or any host's filesystem correctly preserving the bit at all:

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

This approach makes the image's own build process the single source of truth for the executable bit, removing the dependency on every upstream step, git tracking, checkout platform, build context assembly, getting it right independently, which is considerably more robust than trying to fix the issue at each of those upstream points individually.

Using .gitattributes for more consistent cross-platform handling

For repositories regularly checked out across different operating systems, a .gitattributes entry can help enforce more consistent line ending and, in combination with the explicit mode tracking commands described above, reduce the frequency of this issue recurring, though it does not replace the need to explicitly chmod within the Dockerfile as the ultimate, reliable safeguard:

*.sh text eol=lf

This specifically addresses line-ending consistency, which is a related but distinct concern from the executable bit itself, worth mentioning because a script with Windows-style line endings can also fail to execute correctly even with the executable bit correctly set, producing a different but adjacent class of script execution failure.

Verifying the fix took effect

After adding an explicit chmod instruction, rebuilding and directly checking the resulting image's file mode confirms the fix actually resolved the issue, rather than assuming success based on the build completing without error:

docker build -t my-api .
docker run --rm my-api ls -la /entrypoint.sh
-rwxr-xr-x 1 root root 245 Jun 1 12:00 /entrypoint.sh

Common mistakes

  • Repeatedly setting a file's executable bit locally without verifying git actually tracked and committed that mode change, only to have it reappear missing after the next checkout.
  • Setting core.fileMode false to avoid noisy diffs without recognizing this also causes git to ignore and potentially never correct executable bit issues going forward.
  • Assuming a Windows-based CI checkout will preserve a Unix executable bit the same way a Linux or macOS checkout would.
  • Relying on the source repository or checkout process to consistently preserve the executable bit rather than explicitly setting it within the Dockerfile as a reliable, self-contained safeguard.
  • Not verifying the actual file mode inside the built image directly after applying a fix, assuming success based only on the build completing without error.

An executable bit missing error is one of the more mechanically simple permission issues to both diagnose, a direct ls -la check confirms it immediately, and fix reliably, an explicit chmod +x in the Dockerfile removes the dependency on every upstream step correctly preserving the bit, making it one of the more satisfying categories of Docker troubleshooting to resolve completely and permanently rather than needing to be rediscovered repeatedly.