Troubleshooting
Docker & Compose

Fix Docker Entrypoint "permission denied" or "no such file" for Django

Fix Django containers that fail with exec ./entrypoint.sh: permission denied or exec format error and no such file or directory: set the executable bit in Git, convert CRLF line endings, and use the right shebang.

Problem statement

The Django image builds, but the container exits immediately with one of these:

exec /app/entrypoint.sh: permission denied
exec /app/entrypoint.sh: no such file or directory

The second one is baffling because the file is clearly there. Both come from the kernel refusing to execute the script. "Permission denied" means the file lacks the executable bit. "No such file or directory" usually means the file has Windows CRLF line endings, so the shebang line asks for an interpreter named /bin/sh\r, which does not exist, or the shebang names a shell the image does not have.

Why the kernel refuses the scriptContainer startsruns ENTRYPOINTKernel checks filemode and shebangBad mode or CRLFno x bit, sh\rExec failscontainer exitsBoth errors are about the script file itself, not about Django or your settings.
  1. Permission denied: chmod +x entrypoint.sh and commit the mode with git update-index --chmod=+x entrypoint.sh, or RUN chmod +x in the Dockerfile.
  2. No such file: convert line endings to LF (dos2unix entrypoint.sh or set your editor) and add *.sh text eol=lf to .gitattributes.
  3. Check the shebang: #!/bin/sh works on slim and Alpine images; #!/bin/bash fails on Alpine without bash.
  4. Rebuild without cache to be sure the new file is in the image.
  5. Verify inside the image: docker run --rm --entrypoint sh image -c "ls -l /app/entrypoint.sh; head -1 /app/entrypoint.sh | od -c | head -2".

Step-by-step solution

1. Inspect the file inside the image

Override the entrypoint to look at what was actually copied:

docker run --rm --entrypoint sh myapp:latest -c 'ls -l /app/entrypoint.sh; head -1 /app/entrypoint.sh | od -c | head -3'
-rw-r--r-- 1 root root 312 Oct  2 10:00 /app/entrypoint.sh
0000000   #   !   /   b   i   n   /   s   h  \r  \n

Here both problems are visible: no x in the mode, and \r\n at the end of the shebang.

2. Fix the executable bit

Set it in the build so it does not depend on the checkout:

COPY --chmod=755 entrypoint.sh /app/entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]

COPY --chmod needs BuildKit (default in current Docker). On older builders, add RUN chmod +x /app/entrypoint.sh after the copy. Also record the mode in Git so every clone gets it:

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

Windows checkouts do not preserve Unix modes, which is why a script that works on a Mac fails when built from a Windows machine or some CI runners.

3. Fix CRLF line endings

Convert the file and stop Git from reintroducing CRLF:

dos2unix entrypoint.sh            # or: sed -i 's/\r$//' entrypoint.sh
printf '*.sh text eol=lf\n' >> .gitattributes
git add --renormalize .
git commit -m "Normalize shell scripts to LF"

With core.autocrlf=true on Windows, Git converts LF to CRLF on checkout unless .gitattributes says otherwise. The attribute fixes it for every contributor.

the exec error names the problemCauseFixpermission deniedno executable bit✓COPY --chmod=755no such file or directoryCRLF or missing shell✓LF endings, #!/bin/shexec format errorno shebang, or wrong arch✓add shebang, check platformNone of these are Django problems; they happen before Python runsdjango-deployment.com
Look at the file inside the image to confirm which one applies.

4. Use a shebang the image supports

#!/bin/sh
set -e
python manage.py collectstatic --noinput
exec "$@"
  • python:3.x-slim has /bin/sh (dash) and /bin/bash.
  • python:3.x-alpine has /bin/sh (busybox) but not bash unless you install it.
  • Distroless images have no shell; use a Python entrypoint instead.

If the script uses bash features ([[ ]], arrays), either keep #!/bin/bash on Debian-based images or rewrite it in POSIX sh.

5. End with exec

exec "$@" replaces the shell with Gunicorn so it becomes PID 1 and receives SIGTERM on docker stop. Without exec, the shell stays PID 1, ignores the signal, and Docker kills the container after the 10-second timeout, dropping in-flight requests.

an entrypoint that always runsExecutable mode set in the buildCOPY --chmod=755LF line endings enforced.gitattributes *.sh eol=lfShebang matches the image#!/bin/sh is safestset -e at the topfailures stop the startEnds with exec "$@"Gunicorn becomes PID 1These five lines of discipline prevent the whole class of exec errorsdjango-deployment.com
Setting the mode in the Dockerfile makes the build independent of how the repo was checked out.

Verification check:

docker build --no-cache -t myapp:test .
docker run --rm myapp:test python -c "print('ok')"
docker run --rm --entrypoint sh myapp:test -c 'file /app/entrypoint.sh'

file should report POSIX shell script, ASCII text executable without with CRLF line terminators.

Explanation

When a container starts, Docker asks the kernel to execve the entrypoint. For a script, the kernel reads the first line, finds the interpreter after #!, and runs it with the script as an argument. The kernel requires the execute permission bit, and it takes the interpreter path literally, including a trailing carriage return. So a perfectly valid script edited on Windows names an interpreter /bin/sh\r that does not exist, and the kernel reports "no such file or directory" about the interpreter, not the script.

Edge cases / notes

  • Bind mounts in development override the image's file with your local copy, including its mode and line endings.
  • ENTRYPOINT in shell form (ENTRYPOINT ./entrypoint.sh) runs via /bin/sh -c, which hides permission problems but breaks signal handling. Prefer the exec form.
  • exec format error can also mean the image was built for another CPU architecture.
  • Python entrypoints (ENTRYPOINT ["python", "/app/entrypoint.py"]) avoid shell and mode issues entirely.

Next steps

FAQ

Why does it say no such file when the file exists?

The shebang line ends with a Windows carriage return, so the kernel looks for an interpreter called /bin/sh\r.

Is chmod in the Dockerfile enough?

Yes for the image. Also set the mode in Git so local runs and other tools behave the same.

Can I use bash in my entrypoint?

On Debian-based images, yes. Alpine needs apk add bash, or use POSIX sh.

Why end the script with exec?

So Gunicorn replaces the shell as PID 1 and receives stop signals directly.

Keep going