#!/bin/sh set -e ############################################################################### # Everything that has to be true before the application answers a request. # # Runs in every container built from this image, but not every container is a # service: `docker compose run app php artisan key:generate` has to work on a # stack that has never been started, which is exactly when there is no APP_KEY # and no database yet. So the preparation below applies to the three # long-running commands, and anything else is passed straight through. ############################################################################### case "$*" in frankenphp*|*queue:work*|*schedule:work*) is_service=true ;; *) is_service=false ;; esac # A named volume mounted over storage/ starts as a copy of the image's, but a # bind mount does not, and neither survives a container being recreated with an # empty one. Cheap to guarantee, expensive to debug — and needed by one-off # commands too, since most of them write a log. mkdir -p \ storage/app/public \ storage/framework/cache/data \ storage/framework/sessions \ storage/framework/views \ storage/logs chown -R www-data:www-data storage bootstrap/cache 2>/dev/null || true if [ "$is_service" = "false" ]; then exec "$@" fi if [ -z "${APP_KEY}" ]; then echo "APP_KEY is not set. Generate one with:" >&2 echo " docker compose run --rm --no-deps app php artisan key:generate --show" >&2 echo "Without it sessions cannot be decrypted and preview links cannot be signed." >&2 exit 1 fi # Wait for Postgres rather than crash-looping against it. Compose's own # healthcheck covers the ordinary case; this covers the database restarting # under a running application. if [ "${DB_CONNECTION:-pgsql}" = "pgsql" ]; then tries=0 until pg_isready -h "${DB_HOST:-postgres}" -p "${DB_PORT:-5432}" -U "${DB_USERNAME:-pireon}" -q; do tries=$((tries + 1)) if [ "$tries" -ge 30 ]; then echo "Database at ${DB_HOST:-postgres}:${DB_PORT:-5432} never became ready." >&2 exit 1 fi echo "Waiting for the database… (${tries}/30)" sleep 2 done fi # Opt-in rather than automatic: a migration is a change to somebody's data, and # on a multi-container deploy it must run once, not once per container. Set it # on the web service only. if [ "${RUN_MIGRATIONS}" = "true" ]; then echo "Running migrations…" # CACHE_STORE is overridden for this one command, not passed to # `--isolated` — that option's value is only an exit code, never a store. # # It matters because with CACHE_STORE=database the lock table is created by # these very migrations, so on a brand new database the lock would be taken # against a table that does not exist yet and the container would crash-loop # for ever. The file store lives on the shared storage volume, so it still # stops two containers migrating at once. CACHE_STORE=file php artisan migrate --force --isolated # The permission catalogue is code, so a deploy that adds one has to sync # it or the new permission exists nowhere. php artisan permissions:sync fi # Serves uploaded images from the storage volume. Idempotent, and it has to run # here rather than at build time because the volume is mounted after the image # is built. php artisan storage:link --quiet 2>/dev/null || true # Config, routes and views compiled once per container start. This is most of # the difference between an API answering in 4ms and in 40, which matters when # an SSR frontend makes several calls per page view. php artisan config:cache --quiet php artisan route:cache --quiet php artisan view:cache --quiet exec "$@"