It may help to run php artisan storage:link
, in case it’s helpful here’s what ChatGPT suggests:
It’s almost always the missing storage mount in the Nginx container.
Why the logo never shows up
- The upload API writes the file to
/var/www/app/storage/app/public/...
inside the app container. - The browser is then asked to render
/storage/…
, which is just a symlink inside/var/www/app/public
pointing to that same storage path. - Your nginx service receives only the
public
directory. Because the storage directory isn’t mounted there, the symlink target is missing, Nginx returns 404, and the UI won’t let you “Save”.
– Exactly the symptom reported in other logo threads when the storage volume isn’t shared (forum.invoiceninja.com, github.com)
The fix in one line of YAML
Add the storage bind-mount to the nginx service and redeploy:
nginx:
image: nginx:latest
volumes:
- /volume1/docker/invoiceninja/in-vhost.conf:/etc/nginx/conf.d/in-vhost.conf:ro
- /volume1/docker/invoiceninja/public:/var/www/app/public:ro
- /volume1/docker/invoiceninja/storage:/var/www/app/storage:ro # ← add this line
Then:
# recreate the symlink in case it’s missing
docker exec -it InvoiceNinja php artisan storage:link
# clear caches (optional but tidy)
docker exec -it InvoiceNinja php artisan optimize
# bounce the stack
docker compose down
docker compose up -d
Re-upload the logo—-it should render instantly and the Save button will work.
No need for 777 permissions, extra queue workers, or changes to APP_URL
; the root cause is simply that the web server couldn’t see the file until the storage volume was mounted.