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

> 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.

- Asked: 2026-08-13
- Answered: 2026-08-18
- Asked by: Barış
- Tags: docker, guvenlik, laravel
- Source: https://muhammetsafak.com/just-ask/my-non-root-container-cant-write-to-the-mounted-storage-directory/
- Language: en-US
- Author: Muhammet Şafak

---
**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`?


Short answer: it's not the permission bits, it's a UID mismatch.

## Short answer

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`. I covered the other face of building the image non-root and as narrow as possible in [the multi-stage and distroless record](/just-ask/shrinking-docker-images-with-multi-stage-builds-and-distroless/).

## Why

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, copies the ownership and permissions of the image's mount point (usually root, but that's not guaranteed); a bind mount carries the host directory's ownership as-is. The fix differs in each case.

3. **Why `chmod 777` isn't the fix.** 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 already enough; the problem isn't that the permission bits are too tight, it's that ownership sits in the wrong place.

## What to do

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. Set the setgid bit on the directory (`chmod g+s`) so new files keep the right group.

2. **For named volumes, do a targeted chown + drop in the entrypoint.** Start the container as root, chown only the directories that must be writable, then drop to the user:

   ```bash
   #!/bin/sh
   # entrypoint (starts as root, then drops to app)
   chown -R app:app storage bootstrap/cache
   exec su-exec app "$@"
   ```

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.

## Related Reading

- [How do I isolate self-hosted CI/CD runners and build an ephemeral, clean-after-each-run environment?](https://muhammetsafak.com/just-ask/isolating-and-securing-self-hosted-ci-cd-runners/) — Just Ask
- [For comments across many models should I use a polymorphic relation or separate tables, given indexing and foreign keys?](https://muhammetsafak.com/just-ask/comments-polymorphic-relation-separate-tables-indexing-foreign-keys/) — Just Ask
- [My Go worker in a container never receives SIGTERM and won't shut down gracefully—is this a PID 1 problem?](https://muhammetsafak.com/just-ask/go-worker-never-receives-sigterm-pid-1-problem/) — Just Ask
