From d78ca5d960bc46dcbd31fc04fc1d7bac1a6d6d66 Mon Sep 17 00:00:00 2001 From: Ryan Bonnell Date: Thu, 1 Oct 2026 23:04:13 -0700 Subject: [PATCH 1/5] Add reference to Environment Variable Specification docs --- .../docs/8.reference/1.environment-variable-specification.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index bc157974..838f8f8a 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -43,6 +43,7 @@ Setting environment variables all depends on what method you're using to run you `AUTORUN_LARAVEL_MIGRATION_TIMEOUT`
*Default: "30"*|The number of seconds to wait for the database to come online before attempting `php artisan migrate`..
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_ROUTE_CACHE`
*Default: "true"*|Automatically run "php artisan route:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_SKIP_IF_NOT_FOUND`
*Default: "false"*|When set to `true`, the Laravel Automations script will exit gracefully (without error) if Laravel is not detected in `APP_BASE_DIR`, instead of failing the container. Useful when `AUTORUN_ENABLED=true` is set on a shared image where Laravel may not always be present (e.g. before the first `composer install`).
ℹ️ Requires `AUTORUN_ENABLED = true` to have any effect.| all +`AUTORUN_LARAVEL_STORAGE_INIT`
*Default: "false"*|Automatically create the Laravel `storage` and `bootstrap/cache` directory structure and make them writable on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_STORAGE_LINK`
*Default: "true"*|Automatically run "php artisan storage:link" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_VIEW_CACHE`
*Default: "true"*|Automatically run "php artisan view:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `CADDY_ACME_PROFILE`
*Default: "off"*|Select a Let's Encrypt ACME certificate profile. Valid options: `off` (default, no profile — keeps the stock Let's Encrypt + ZeroSSL issuers), `shortlived` (~6-day certs; also required for IP-address certificates), `tlsserver`, or `classic`. Setting any profile pins issuance to Let's Encrypt only and renews more frequently, so the container needs reliable egress to the ACME CA. (Official docs)|frankenphp From 19c9b6b6121e06b55c8472e45f43c1087743ff21 Mon Sep 17 00:00:00 2001 From: Ryan Bonnell Date: Thu, 1 Oct 2026 23:03:23 -0700 Subject: [PATCH 2/5] Add environment variable to Laravel Automations docs --- docs/content/docs/3.framework-guides/1.laravel/1.automations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/docs/3.framework-guides/1.laravel/1.automations.md b/docs/content/docs/3.framework-guides/1.laravel/1.automations.md index c320ba38..18a9a830 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/1.automations.md +++ b/docs/content/docs/3.framework-guides/1.laravel/1.automations.md @@ -32,6 +32,7 @@ In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once th | `AUTORUN_LARAVEL_OPTIMIZE` | `true` | `php artisan optimize`: Optimizes the application. | | `AUTORUN_LARAVEL_ROUTE_CACHE` | `true` | `php artisan route:cache`: Caches the routes. | | `AUTORUN_LARAVEL_SKIP_IF_NOT_FOUND` | `false` | When `true`, the script will exit gracefully (without error) if Laravel is not detected in `APP_BASE_DIR`, instead of failing the container. Useful when `AUTORUN_ENABLED=true` is set on a shared image where Laravel may not always be present (e.g. before the first `composer install`). | +| `AUTORUN_LARAVEL_STORAGE_INIT` | `false` | Creates the `storage` and `bootstrap/cache` directory structure if missing and ensures they are writable by the owner and group. | | `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | | `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | From 0efb11aa1d1f323ef6776b29b55b24a5c82a98bf Mon Sep 17 00:00:00 2001 From: Ryan Bonnell Date: Thu, 1 Oct 2026 22:55:15 -0700 Subject: [PATCH 3/5] Add Laravel automation for storage initialization --- .../entrypoint.d/50-laravel-automations.sh | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 6f4a811a..c8ca36d1 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -11,7 +11,8 @@ script_name="laravel-automations" : "${AUTORUN_DEBUG:=false}" : "${AUTORUN_LARAVEL_SKIP_IF_NOT_FOUND:=false}" -# Set default values for storage link +# Set default values for storage +: "${AUTORUN_LARAVEL_STORAGE_INIT:=false}" : "${AUTORUN_LARAVEL_STORAGE_LINK:=true}" # Set default values for optimizations @@ -165,6 +166,33 @@ artisan_storage_link() { fi } +laravel_storage_init() { + echo "🚀 Initializing Laravel storage structure and permissions in $APP_BASE_DIR" + for dir in \ + bootstrap/cache \ + storage/app/private \ + storage/app/public \ + storage/framework/cache \ + storage/framework/sessions \ + storage/framework/testing \ + storage/framework/views \ + storage/logs \ + storage/pail; do + if [ ! -d "$APP_BASE_DIR/$dir" ] && ! mkdir_error=$(mkdir -p "$APP_BASE_DIR/$dir" 2>&1); then + echo "❌ $script_name: Unable to create directory: $APP_BASE_DIR/$dir" + echo " $mkdir_error" + return 1 + fi + + # Only the owner can change permissions, so directories owned by another user are skipped rather than failing startup + if ! chmod ug+rwx "$APP_BASE_DIR/$dir" 2>/dev/null; then + echo "ℹ️ Unable to update permissions on $APP_BASE_DIR/$dir, skipping" + fi + done + + return 0 +} + artisan_optimize() { debug_log "Starting Laravel optimizations..." @@ -443,6 +471,11 @@ wait_for_database_connection() { ############################################################################ if laravel_is_installed; then + # Runs first because Artisan fails to boot without bootstrap/cache + if [ "$AUTORUN_LARAVEL_STORAGE_INIT" = "true" ]; then + laravel_storage_init + fi + if [ "$LOG_OUTPUT_LEVEL" = "debug" ] || [ "$AUTORUN_DEBUG" = "true" ]; then echo "Laravel detected: v$(get_laravel_version)" echo "Automation settings:" From 08598a21b84b7088303b71f00f36e8bc8cd7c8cf Mon Sep 17 00:00:00 2001 From: Ryan Bonnell Date: Thu, 1 Oct 2026 23:50:54 -0700 Subject: [PATCH 4/5] Add storage overview to Laravel Automations docs --- .../1.laravel/1.automations.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/content/docs/3.framework-guides/1.laravel/1.automations.md b/docs/content/docs/3.framework-guides/1.laravel/1.automations.md index 18a9a830..bb61bbb4 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/1.automations.md +++ b/docs/content/docs/3.framework-guides/1.laravel/1.automations.md @@ -36,6 +36,57 @@ In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once th | `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | | `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | +## Storage Initialization +Laravel expects a specific set of directories inside `storage/` and `bootstrap/cache` to exist and be writable. If they're missing, your application will fail to boot. Depending on which directory is missing, you'll see errors like these: + +- `The /var/www/html/bootstrap/cache directory must be present and writable.` +- `View path not found.` (when running `php artisan optimize` or `php artisan view:cache`) + +This commonly happens in containers when: +- Your `.dockerignore` excludes `storage/` or `bootstrap/cache` from the image build +- You mount an empty volume over `storage/` + +Setting `AUTORUN_LARAVEL_STORAGE_INIT=true` runs this step first, before every other automation, so the rest of the Laravel automations can rely on these directories being in place. + +::steps{level="3"} + +### Create missing directories +The script creates any of these directories that don't already exist: + +- `bootstrap/cache` +- `storage/app/private` +- `storage/app/public` +- `storage/framework/cache` +- `storage/framework/sessions` +- `storage/framework/testing` +- `storage/framework/views` +- `storage/logs` +- `storage/pail` + +Directories that already exist are left alone, so this step is safe to run on every container start. + +::warning +Failing to keep the [storage directory structure](https://laravel.com/framework/docs/master/structure#the-storage-directory) causes containerized Laravel processes to crash when trying to write logs or cache files. +:: + +### Make the directories writable +Each directory in the list above is made readable and writable by its owner and group. Only the directories themselves are changed. Files inside them, such as user uploads in `storage/app`, are never modified. + +::note +Only the owner of a directory can change its permissions. If a directory is owned by another user (for example, `root`), the script logs a message, skips that directory, and continues starting the container. +:: + +### Using a volume for storage +The script runs as the unprivileged `www-data` user, so the directory you mount a volume on must be writable by `www-data`. When you create a new named volume, Docker copies the ownership of the matching directory in your image. If that directory doesn't exist in the image, Docker creates the volume owned by `root`, and the script won't be able to create anything inside it. + +To avoid this, make sure `storage/` exists in your image. + +::warning +If the script can't create a directory, the container will fail to start. Make sure the directory you mount on exists in your image and is owned by `www-data`. +:: + +:: + ## Database Connection Checks Before running migrations, the automation script performs connection checks to ensure your database is ready. Understanding this process helps you configure timeouts and troubleshoot connection issues. From 4faf6583dde6ff5651e07c7011a15972ce303532 Mon Sep 17 00:00:00 2001 From: Ryan Bonnell Date: Fri, 2 Oct 2026 11:21:11 -0700 Subject: [PATCH 5/5] Use `debug_log` for storage directory mkdir errors --- src/common/etc/entrypoint.d/50-laravel-automations.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index c8ca36d1..fee030dc 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -180,7 +180,7 @@ laravel_storage_init() { storage/pail; do if [ ! -d "$APP_BASE_DIR/$dir" ] && ! mkdir_error=$(mkdir -p "$APP_BASE_DIR/$dir" 2>&1); then echo "❌ $script_name: Unable to create directory: $APP_BASE_DIR/$dir" - echo " $mkdir_error" + debug_log "$mkdir_error" return 1 fi