Skip to content
Muhammet Şafak
tr
Asked by: Barış Answered:

My non-root container can't write to the mounted storage directory—how do I fix the permissions?


Question

I run my Laravel app in Docker and, for security, I switched from running the image as root to a non-root user I created. But after that, writes under `storage/` started failing: it can't write logs, cache and session files aren't created, and I keep getting "Permission denied." My setup: an image based on `php:8.3-fpm-alpine`, application code baked into the image, but I mount the `storage/` directory as a volume for persistence. When the container ran as root there were no issues. How do I fix the permissions correctly for the non-root user without doing `chmod 777`?

Answer

Short answer: it’s not the permission bits, it’s a UID mismatch. The numeric UID of the user inside the container doesn’t match the owner of the mounted directory; the reason it worked as root is that root (UID 0) can write anywhere. The fix is aligning ownership, not chmod 777.

  1. Root cause: UID, not the username. Linux permissions care about the numeric UID/GID. The app user in your image might be UID 1000, but if the mounted directory is owned by a different UID on the host, the kernel refuses the write. Matching names means nothing.
  2. Named volume or bind mount? A named volume is Docker-managed and, on first creation, is populated with root ownership; a bind mount carries the host directory’s ownership as-is. The fix differs in each case.
  3. Targeted chown + drop in the entrypoint. Start the container as root, chown only the directories that must be writable, then drop to the user:
#!/bin/sh
# entrypoint (starts as root, then drops to app)
chown -R app:app storage bootstrap/cache
exec su-exec app "$@"
  1. Align the UID at build time for bind mounts. In the Dockerfile use ARG UID/ARG GID to create the user with your host’s UID, and copy the code with COPY --chown=app:app. Ownership is then correct from the start, without even needing the entrypoint trick.
  2. Avoid chmod 777. It’s a security regression and defeats the whole point of going non-root. A group-writable bit (g+w) plus correct group membership is enough; set the setgid bit on the directory (chmod g+s) so new files keep the right group.
  3. Put persistent state in the right place. If you’ll run multiple replicas, don’t share storage/ over a shared filesystem — move sessions/cache to Redis and files to object storage like S3. The volume permission headache disappears entirely.

Bottom line: personally I’d pin the user with a build-time ARG UID, use COPY --chown, and for named volumes chown only storage and bootstrap/cache in the entrypoint before dropping to app with su-exec. Long term I’d move persistent data to S3/Redis wherever possible and keep the container genuinely stateless.

Tags: #docker#security#laravel
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind