| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- # --- Stage 1: compile Tailwind CSS + vendor JS deps ----------------------
- # Runs the Tailwind JIT over views/, src/, and our JS so only classes that
- # are actually referenced end up in the output. No runtime <style> injection,
- # which lets the CSP drop 'unsafe-inline' for style-src.
- #
- # The same stage also vendors Alpine.js (CSP build), htmx, and SortableJS
- # from npm into /build/vendor/ — copied into the runtime image alongside the
- # CSS so the strict CSP can keep `script-src 'self'`.
- FROM node:20-alpine AS css-builder
- WORKDIR /build
- COPY package.json package-lock.json* ./
- RUN npm ci --no-audit --no-fund
- # Only the files that contribute to class discovery / the entry stylesheet.
- COPY tailwind.config.js ./
- COPY assets/css/input.css ./assets/css/input.css
- COPY views/ ./views/
- COPY src/ ./src/
- COPY public/assets/js/ ./public/assets/js/
- RUN npx tailwindcss -i ./assets/css/input.css -o /build/app.css --minify
- # Pin the vendored JS bundles. Alpine CSP is the variant that doesn't need
- # `unsafe-eval`; standard Alpine would require relaxing the CSP.
- RUN mkdir -p /build/vendor \
- && cp node_modules/@alpinejs/csp/dist/cdn.min.js /build/vendor/alpine-csp.min.js \
- && cp node_modules/htmx.org/dist/htmx.min.js /build/vendor/htmx.min.js \
- && cp node_modules/sortablejs/Sortable.min.js /build/vendor/sortable.min.js
- # --- Stage 2: tailwind --watch (dev only) -------------------------------
- # Used by docker-compose.dev.yml. Source dirs are bind-mounted from the
- # host at /build/* and `app.css` is regenerated on save. Vendor JS bundles
- # are seeded into the host-mounted public/assets/js/vendor on first start
- # by bin/dev-css-watcher.sh — they don't change between iterations so a
- # single seed at startup is enough.
- FROM node:20-alpine AS css-watcher
- WORKDIR /build
- COPY package.json package-lock.json* ./
- RUN npm ci --no-audit --no-fund
- COPY bin/dev-css-watcher.sh /usr/local/bin/dev-css-watcher.sh
- RUN chmod +x /usr/local/bin/dev-css-watcher.sh
- CMD ["/usr/local/bin/dev-css-watcher.sh"]
- # --- Stage 3: the actual PHP runtime ------------------------------------
- FROM php:8.3-apache AS runtime
- RUN apt-get update && apt-get install -y --no-install-recommends \
- libsqlite3-dev libzip-dev libpng-dev libjpeg-dev libfreetype6-dev unzip git \
- && docker-php-ext-configure gd --with-freetype --with-jpeg \
- && docker-php-ext-install pdo pdo_sqlite zip gd \
- && a2enmod rewrite \
- && rm -rf /var/lib/apt/lists/*
- COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
- WORKDIR /var/www/html
- COPY composer.json composer.lock* ./
- RUN composer install --no-dev --no-interaction --prefer-dist --no-progress
- COPY . .
- # Place the compiled CSS + vendored JS where Apache can serve them.
- COPY --from=css-builder /build/app.css /var/www/html/public/assets/css/app.css
- COPY --from=css-builder /build/vendor/ /var/www/html/public/assets/js/vendor/
- # Twig cache lives under data/ alongside the SQLite file. www-data must be
- # able to write there at request time so first-render template compilation
- # succeeds.
- RUN mkdir -p /var/www/data /var/www/data/sessions /var/www/html/data/twig-cache \
- && chown -R www-data:www-data /var/www/data /var/www/html/data \
- && printf '%s\n' \
- '<VirtualHost *:80>' \
- ' DocumentRoot /var/www/html/public' \
- ' <Directory /var/www/html/public>' \
- ' Options -Indexes +FollowSymLinks' \
- ' AllowOverride All' \
- ' Require all granted' \
- ' FallbackResource /index.php' \
- ' </Directory>' \
- ' ErrorLog ${APACHE_LOG_DIR}/error.log' \
- ' CustomLog ${APACHE_LOG_DIR}/access.log combined' \
- '</VirtualHost>' \
- > /etc/apache2/sites-available/000-default.conf
- # R01-N22: run migrations at container start, before Apache binds the port.
- # The request path no longer auto-migrates — it only checks and refuses to
- # serve when something is pending — so a missed entrypoint produces a loud
- # 503, not silent stale-schema serving.
- RUN install -m 0755 /var/www/html/bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
- EXPOSE 80
- ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
- CMD ["apache2-foreground"]
- # --- Stage 4: phpunit + dev composer deps (test runner) -----------------
- # Built on top of `runtime` so the test environment matches prod exactly,
- # then layers in the composer dev dependencies (phpunit, mocks). Used by
- # `make test` / `make check` via `docker compose run --rm tests`.
- # Inherits the entrypoint, which runs migrations against an in-container
- # SQLite before phpunit starts — tests get a fresh, fully-migrated schema.
- FROM runtime AS tests
- RUN composer install --no-interaction --prefer-dist --no-progress
- CMD ["composer", "test"]
|