diff --git a/.env.example b/.env.example index bfe5738..e8fd9ca 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,13 @@ # Show full exception details in API error responses. Never enable in production. APP_DEBUG=false +# Directory for generated files: the SQLite database and the JWT signing key. +# Defaults to /storage. The Docker setup points this at a volume +# outside the bind-mounted source. +STORAGE_PATH=storage + # Path to the SQLite database file (absolute, or relative to the project root). +# Defaults to /database.sqlite. DATABASE_PATH=storage/database.sqlite # Secret used to sign JWTs. Leave blank to auto-generate one into storage/secret.key. diff --git a/README.md b/README.md index a6c2ec7..353a498 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,15 @@ docker compose up -d This builds a PHP 8.3 + Apache image, applies migrations, and serves the API at (e.g. `curl http://localhost:8080/api/health`). +- The project directory is bind-mounted into the container, so editing PHP + source takes effect without a rebuild (within ~2s, due to the opcache + revalidation interval). `vendor/` is used from the host — run `composer` + once first if it is missing (see "Run without Docker" below, or + `docker compose run --rm --entrypoint composer app install`). - The SQLite database and the generated JWT signing key live in the `storage` - named volume, so they survive `docker compose restart` / `down` + `up`. -- After changing PHP source or `composer.json`, rebuild: `docker compose up -d --build`. + named volume, mounted at `/var/www/storage` (outside the bind-mounted source), + so they survive `docker compose restart` / `down` + `up`. +- Rebuild only after changing the `Dockerfile`: `docker compose up -d --build`. - `docker compose down -v` removes the volume and gives you a clean database. - Override settings via the environment or a `.env` file in this directory (Compose substitutes `APP_DEBUG`, `JWT_SECRET`, `JWT_TTL` — see diff --git a/docker-compose.yml b/docker-compose.yml index e3762d7..b7ef2d3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,12 +6,17 @@ services: - "8080:80" environment: APP_DEBUG: "${APP_DEBUG:-false}" + # Generated files (SQLite DB + JWT signing key) go here, on their own + # volume so they don't overlap the bind mount below. + STORAGE_PATH: /var/www/storage # Leave blank to auto-generate a secret into the storage volume on first run. JWT_SECRET: "${JWT_SECRET:-}" JWT_TTL: "${JWT_TTL:-86400}" volumes: - # Persists the SQLite database and the generated signing key across restarts. - - storage:/var/www/html/storage + # Live source: edit on the host, no image rebuild needed. + - .:/var/www/html + # Persistent storage, kept outside /var/www/html to avoid a nested mount. + - storage:/var/www/storage restart: unless-stopped volumes: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index f4ca681..8609581 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,10 +1,11 @@ #!/bin/sh set -e -# The storage directory may be a freshly created named volume; make sure it -# exists and is writable by the web-server user. -mkdir -p storage -chown -R www-data:www-data storage +# Storage lives outside the app directory when the source is bind-mounted for +# development (see docker-compose.yml); the plain image falls back to ./storage. +STORAGE_DIR="${STORAGE_PATH:-storage}" +mkdir -p "$STORAGE_DIR" +chown -R www-data:www-data "$STORAGE_DIR" # Apply pending migrations as www-data so the SQLite file it creates stays # writable by Apache. diff --git a/src/Support/Config.php b/src/Support/Config.php index 03e6b50..3b17f79 100644 --- a/src/Support/Config.php +++ b/src/Support/Config.php @@ -24,7 +24,7 @@ final class Config \Dotenv\Dotenv::createImmutable($basePath)->safeLoad(); } - $storagePath = $basePath . '/storage'; + $storagePath = self::env('STORAGE_PATH', $basePath . '/storage'); if (!is_dir($storagePath)) { mkdir($storagePath, 0775, true); }