diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bac6021 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.gitignore +.dockerignore +vendor +storage +.env +.phpunit.cache +.phpunit.result.cache +docs +README.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..38be150 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# syntax=docker/dockerfile:1 + +FROM php:8.3-apache + +# --- PHP extensions and CLI tools ---------------------------------------------- +RUN apt-get update && apt-get install -y --no-install-recommends \ + libsqlite3-dev \ + libonig-dev \ + unzip \ + curl \ + && docker-php-ext-install pdo_sqlite mbstring \ + && rm -rf /var/lib/apt/lists/* + +# --- Composer ---------------------------------------------------------------- +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +# --- Apache: document root -> public/, allow .htaccess rewrites ------------- +RUN a2enmod rewrite +COPY docker/apache.conf /etc/apache2/sites-available/000-default.conf + +WORKDIR /var/www/html + +# --- PHP dependencies (own layer, cached unless composer.* changes) -------- +COPY composer.json composer.lock ./ +RUN composer install --no-dev --no-interaction --no-progress --prefer-dist --no-autoloader + +# --- Application source ---------------------------------------------------- +COPY . . +RUN composer dump-autoload --optimize --no-dev \ + && mkdir -p storage \ + && chown -R www-data:www-data storage + +# --- Entrypoint: migrate, then hand off to Apache ------------------------ +COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["apache2-foreground"] + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ + CMD curl -fsS http://localhost/api/health || exit 1 diff --git a/README.md b/README.md index 28736c7..a6c2ec7 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,35 @@ SQLite file, plus a single-page frontend (added in a later stage). | 2 | Todo CRUD API | planned | | 3 | Single-page frontend | planned | -## Requirements +## Run with Docker -- PHP 8.1+ with the `pdo_sqlite` and `mbstring` extensions -- [Composer](https://getcomposer.org/) +The only requirement is Docker with the Compose plugin. -On Fedora: +```bash +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 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`. +- `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 + [docker-compose.yml](docker-compose.yml)). + +## Run without Docker + +Requires PHP 8.1+ with the `pdo_sqlite` and `mbstring` extensions, plus +[Composer](https://getcomposer.org/). On Fedora: ```bash sudo dnf install php-cli php-pdo php-mbstring composer ``` -## Setup +### Setup ```bash composer install @@ -30,7 +47,7 @@ cp .env.example .env # optional; sane defaults are used without it composer migrate # creates storage/database.sqlite and its tables ``` -## Running +### Running ```bash composer serve # http://localhost:8080 (php -S localhost:8080 -t public) @@ -154,6 +171,9 @@ src/Http/JsonErrorHandler.php Uniform JSON error envelope src/Http/Controllers/ Request handlers src/Repository/ Database access migrations/*.sql Schema, applied by bin/migrate.php +Dockerfile PHP 8.3 + Apache image +docker-compose.yml One-command local stack +docker/ Apache vhost + container entrypoint ``` ## Provenance diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e3762d7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + app: + build: . + image: php-todo-list + ports: + - "8080:80" + environment: + APP_DEBUG: "${APP_DEBUG:-false}" + # 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 + restart: unless-stopped + +volumes: + storage: diff --git a/docker/apache.conf b/docker/apache.conf new file mode 100644 index 0000000..96cee74 --- /dev/null +++ b/docker/apache.conf @@ -0,0 +1,13 @@ + + DocumentRoot /var/www/html/public + + + Options -Indexes +FollowSymLinks + AllowOverride All + Require all granted + DirectoryIndex index.php + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..f4ca681 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,13 @@ +#!/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 + +# Apply pending migrations as www-data so the SQLite file it creates stays +# writable by Apache. +su -s /bin/sh -c 'php bin/migrate.php' www-data + +exec docker-php-entrypoint "$@"