From 6c8b7d4dde69b5d80901219f64a63f752205a682 Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:36:56 +0200 Subject: [PATCH] fix(docker): recreate storage directories and guard the chown (#705) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport of InvoiceShelf/InvoiceShelf#702 to 2.x. The chown half of it is a 2.x-only fix — 3.x had already dropped that line. storage/framework/{cache,sessions,views}, storage/logs and storage/app hold no tracked content, only .gitignore stubs, so nothing guarantees they exist inside a mounted volume. Docker seeds a named volume from the image once, when it is empty, and never again — a volume created by an older image keeps whatever it had through every upgrade. Without those directories Laravel cannot boot and the sqlite branch cannot place its database. The unguarded `chown -R www-data:www-data storage` was worse than ineffective. The image runs as www-data (uid 82), so chown of any file it does not own returns EPERM, and under `set -e` that aborts the entrypoint. A single uploaded file owned by the host user is enough to stop the container from starting — on exactly the mounted-volume setups the chown was meant to help. Verified against a built image: entrypoint exits 1 before this change, 0 after. A mount the container genuinely cannot write to is not something the entrypoint can repair, so it now says what is wrong and names the remedy instead of failing later with `cp: can't create ...`. --- docker/production/entrypoint.d/00-setup.sh | 38 ++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/docker/production/entrypoint.d/00-setup.sh b/docker/production/entrypoint.d/00-setup.sh index 93a1b99c..2691496b 100644 --- a/docker/production/entrypoint.d/00-setup.sh +++ b/docker/production/entrypoint.d/00-setup.sh @@ -12,6 +12,32 @@ InvoiceShelf Version: $version cd /var/www/html +# These carry no tracked content — only .gitignore stubs — so a mount over +# storage/ can arrive without them, and Laravel then dies at boot with "Please +# provide a valid cache path" (the compiled view path is resolved with +# realpath(), which returns false for a missing directory). Recreate them before +# anything writes there, including the sqlite database placed in storage/app +# below. See InvoiceShelf/docker#75, #69 and #77. +echo "**** Ensuring storage directories exist ****" +if ! mkdir -p \ + storage/app/public \ + storage/framework/cache/data \ + storage/framework/sessions \ + storage/framework/views \ + storage/logs \ + bootstrap/cache 2>/dev/null; then + echo "!!!! Cannot write to /var/www/html/storage." + echo "!!!! This container runs as uid $(id -u) (www-data), but the mounted" + echo "!!!! directory belongs to someone else — usually a bind mount pointing" + echo "!!!! at a host directory owned by your own user." + echo "!!!! Give that directory to uid 82 on the host and start again:" + echo "!!!!" + echo "!!!! sudo chown -R 82:82 /path/to/your/storage" + echo "!!!!" + echo "!!!! See https://github.com/InvoiceShelf/docker/issues/77" + exit 1 +fi + if [ ! -e /var/www/html/.env ]; then cp .env.example .env echo "**** Setup initial .env values ****" && \ @@ -35,8 +61,16 @@ fi echo "**** Setting up folder permissions ****" chmod +x artisan -chown -R www-data:www-data storage bootstrap/cache -chmod -R 775 storage bootstrap/cache + +# Only root may change ownership. The image runs as www-data, where chown of a +# foreign-owned file is EPERM — and under `set -e` that stopped the container +# from starting at all, on exactly the mounted-volume setups this was meant to +# help. Skip it there rather than fail the boot; still applied when running as +# root. See InvoiceShelf/docker#77 and #69. +if [ "$(id -u)" = "0" ]; then + chown -R www-data:www-data storage bootstrap/cache + chmod -R 775 storage bootstrap/cache +fi if [ ! -L /var/www/html/public/storage ]; then echo "**** Creating storage symlink (public/storage) ****"