Laravel app: permission denied to write on mounted volume

I am using serversideup/php:8.3-unit as docker image in my application. The home directory of my application is /var/www/html/ and I am mounting the volume to /var/www/html/storage/app/public which is where the files that are uploaded to my application are stored. I already set the environment variable RAILWAY_RUN_UID=0 but I have permission issues to write to the mounted volume. The container is running as a non-privileged user www-data but i still have the following error:
[2024-08-22 15:57:00] production.ERROR: file_put_contents(/var/www/html/storage/app/public/hello.txt): Failed to open stream: Permission denied {"userId":1,"exception":"[object] (ErrorException(code: 0): file_put_contents(/var/www/html/storage/app/public/storage/hello.txt): Failed to open stream: Permission denied at /var/www/html/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php:204)
[2024-08-22 15:57:00] production.ERROR: file_put_contents(/var/www/html/storage/app/public/hello.txt): Failed to open stream: Permission denied {"userId":1,"exception":"[object] (ErrorException(code: 0): file_put_contents(/var/www/html/storage/app/public/storage/hello.txt): Failed to open stream: Permission denied at /var/www/html/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php:204)
This is the code that generates the error:
Route::get('/example', function () {
File::put('/var/www/html/storage/app/public/hello.txt', now()->toDateTimeString());

return File::get('/var/www/html/storage/app/public/hello.txt');
});
Route::get('/example', function () {
File::put('/var/www/html/storage/app/public/hello.txt', now()->toDateTimeString());

return File::get('/var/www/html/storage/app/public/hello.txt');
});
Solution:
Because I'm using a docker image that by default runs as a non-privileged user www-data:www-data or 33:33, it's necessary to change the owner of the mounted volume after starting the container. To achieve this, I added a script in /etc/entrypoint.d/99-starting-hook.sh with the following content: ```sh...
Jump to solution
11 Replies
Percy
Percy4mo ago
Project ID: e2d6c1c6-c741-45c3-b553-98603eaddb38
gregorip02
gregorip02OP4mo ago
e2d6c1c6-c741-45c3-b553-98603eaddb38
Brody
Brody4mo ago
Hey, can you send your dockerfile?
gregorip02
gregorip02OP4mo ago
FROM serversideup/php:8.3-unit

USER root

# Install server dependencies
RUN apt-get update \
&& apt-get install -y ca-certificates gnupg \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& install-php-extensions intl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*

# Install project dependencies
COPY package*.json composer.* ./
RUN composer install --no-dev --no-autoloader --no-scripts --no-interaction && npm ci

COPY . .

RUN composer dump --no-interaction && \
npm run build && \
rm -rf node_modules && \
# find /var/www/html -type d -not -path "./vendor/*" -not -path "./.git/*" -exec chmod 755 "{}" \; && \
# find /var/www/html -type f -not -path "./vendor/*" -not -path "./.git/*" -exec chmod 644 "{}" \; && \
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache && \
php /var/www/html/artisan filament:optimize

USER www-data
FROM serversideup/php:8.3-unit

USER root

# Install server dependencies
RUN apt-get update \
&& apt-get install -y ca-certificates gnupg \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& install-php-extensions intl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*

# Install project dependencies
COPY package*.json composer.* ./
RUN composer install --no-dev --no-autoloader --no-scripts --no-interaction && npm ci

COPY . .

RUN composer dump --no-interaction && \
npm run build && \
rm -rf node_modules && \
# find /var/www/html -type d -not -path "./vendor/*" -not -path "./.git/*" -exec chmod 755 "{}" \; && \
# find /var/www/html -type f -not -path "./vendor/*" -not -path "./.git/*" -exec chmod 644 "{}" \; && \
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache && \
php /var/www/html/artisan filament:optimize

USER www-data
Brody
Brody4mo ago
can you try without switching back to the non root user?
gregorip02
gregorip02OP4mo ago
Yes, trying... Same problem. Can i specify RAILWAY_RUN_UID=33 to make it the same uid as the image?
gregorip02
gregorip02OP4mo ago
No description
Brody
Brody4mo ago
nope, the volume is mounted as root, you would need to chown the volume mount before starting your app like what is done here - https://github.com/railwayapp-templates/elasticsearch/blob/main/entrypoint-new.sh
gregorip02
gregorip02OP4mo ago
The problem has been solved, I will share my solution.
Solution
gregorip02
gregorip024mo ago
Because I'm using a docker image that by default runs as a non-privileged user www-data:www-data or 33:33, it's necessary to change the owner of the mounted volume after starting the container. To achieve this, I added a script in /etc/entrypoint.d/99-starting-hook.sh with the following content:
#!/bin/sh

# chown the mount to allow the www-data user read and write access.
chown -R 33:33 /var/www/html/storage/app/public && echo "✅ added permissions to mounted volume"

# optimize filament for production (optional).
php /var/www/html/artisan filament:optimize
#!/bin/sh

# chown the mount to allow the www-data user read and write access.
chown -R 33:33 /var/www/html/storage/app/public && echo "✅ added permissions to mounted volume"

# optimize filament for production (optional).
php /var/www/html/artisan filament:optimize
This is my final Dockerfile:
FROM serversideup/php:8.3-unit

USER root

# Install server dependencies
RUN apt-get update \
&& apt-get install -y ca-certificates gnupg \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& install-php-extensions intl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*

COPY --chmod=755 ./99-starting-hook.sh /etc/entrypoint.d/99-starting-hook.sh

USER www-data

COPY --chown=www-data:www-data . .

RUN composer install --no-dev --no-interaction && \
npm ci && \
npm run build && \
rm -rf node_modules/ && \
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache /var/www/html/public
FROM serversideup/php:8.3-unit

USER root

# Install server dependencies
RUN apt-get update \
&& apt-get install -y ca-certificates gnupg \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& install-php-extensions intl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*

COPY --chmod=755 ./99-starting-hook.sh /etc/entrypoint.d/99-starting-hook.sh

USER www-data

COPY --chown=www-data:www-data . .

RUN composer install --no-dev --no-interaction && \
npm ci && \
npm run build && \
rm -rf node_modules/ && \
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache /var/www/html/public
There is no need to add the RAILWAY_RUN_UID=0 environment variable because the volume is already owned by the www-data user. If you are using serversideup/php:8.3-unit you must add the following environment variable: AUTORUN_ENABLED=true This will run php artisan storage:link after starting the container.
Brody
Brody4mo ago
perfect, glad i could help!
Want results from more Discord servers?
Add your server