• 4 min read
File permissions can be a little hard to get right when working with Docker due to how the host machine and containers are mapped to one another. During development, it can be aggravating to encounter the following issues:
These problems have cropped up for me in different ways, but deploying Laravel apps to a Docker container have been consistently frustrating due to it needing write access to files and directories. I'll be using this as my main example for the rest of this post.
In a typical Laravel application, logs and cache files are created and stored in the
directory. These file operations are performed by the PHP process (either through FPM or Apache).
This will usually be ran as the
www-data system user.
Usually when we develop a PHP application with Docker, we'll want to mount the application directory
as a volume. Unfortunately this means that
storage ends up being owned by the host user (usually
seen as user
1000 within the container). Within the container, these two users have no concept of
one another, let alone have permission to change each other's files.
Here are some solutions to get around this.
Thinking about this purely in terms of the Linux permission model, it should be obvious we can
simply add the
www-data user to our host user's group and add group write permissions to our
We can do this by adding something like this to our
FROM php:7.3-apache # Add `www-data` to group `appuser` RUN addgroup --gid 1000 appuser; \ adduser --uid 1000 --gid 1000 --disabled-password appuser; \ adduser www-data appuser;
Within the container, user
1000 can only be referenced by UID and does not have an associated group.
We need to create an actual user in the container and assign it the UID of
1000. In our example,
we've created one called
This should allow our app to write into
storage, but we'll need to be sure to check that the
directory has group write access too:
COPY ./src /var/www/html # Add group write access to `storage` RUN chmod -R 760 /var/www/html/storage
One problem with the previous solution is that new files written by the container's service user
will also be owned by that user. This means that the host user won't have write access and you'll
need to use
sudo to modify them outside of the container.
This can be a bit of a pain if you need to change these files often. A workaround for this is to map the container's system user directly to the host:
FROM php:7.3-apache # Set www-data to have UID 1000 RUN usermod -u 1000 www-data;
www-data to have a UID of 1000, which corresponds to our host user. Now whenever
creates files within the container, they are also owned by the host user too.
By default many Docker containers will start as
root, leaving consumers to decide if they want to
start a container as a different user. From a security perspective, starting containers as
can introduce the risk of privilege escalation, so it's usually a good practice to explicitly set it
to a non-root user.
In our case, it's desirable to change the user just so that we can correctly set user file permissions. We can start the container as our host user by running the following:
docker run --user 1000:1000 your-container
Or setting it in our
services: app: user: '1000:1000'
This approach is more flexible than the previous solutions as we don't need to explicitly set the
user inside of the
Dockerfile at build time. This allows us to more easily change the user at run
time e.g. with environment variables.
Unfortunately this solution doesn't quite work in our Apache-based PHP example as it needs to start
root to run properly. Thankfully we can override this by setting the user with the
APACHE_RUN_GROUP environment variables
We can set these in
services: app: environment: APACHE_RUN_USER: '#1000' APACHE_RUN_GROUP: '#1000'
Although it's possible to wrangle file permissions to work correctly, I would say that it's actually better to not share files between the host/container at all. Containers try to provide isolated, disposable environments and mounting shared volumes are counter-intuitive to this. As we've seen, we need to leak host details like UIDs and GIDs through to the container to make everything work correctly.
Ideally we should adjust our application so that it: