Dockerfile 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. # --- Stage 1: compile Tailwind CSS + vendor JS deps ----------------------
  2. # Runs the Tailwind JIT over views/, src/, and our JS so only classes that
  3. # are actually referenced end up in the output. No runtime <style> injection,
  4. # which lets the CSP drop 'unsafe-inline' for style-src.
  5. #
  6. # The same stage also vendors Alpine.js (CSP build), htmx, and SortableJS
  7. # from npm into /build/vendor/ — copied into the runtime image alongside the
  8. # CSS so the strict CSP can keep `script-src 'self'`.
  9. FROM node:20-alpine AS css-builder
  10. WORKDIR /build
  11. COPY package.json package-lock.json* ./
  12. RUN npm ci --no-audit --no-fund
  13. # Only the files that contribute to class discovery / the entry stylesheet.
  14. COPY tailwind.config.js ./
  15. COPY assets/css/input.css ./assets/css/input.css
  16. COPY views/ ./views/
  17. COPY src/ ./src/
  18. COPY public/assets/js/ ./public/assets/js/
  19. RUN npx tailwindcss -i ./assets/css/input.css -o /build/app.css --minify
  20. # Pin the vendored JS bundles. Alpine CSP is the variant that doesn't need
  21. # `unsafe-eval`; standard Alpine would require relaxing the CSP.
  22. RUN mkdir -p /build/vendor \
  23. && cp node_modules/@alpinejs/csp/dist/cdn.min.js /build/vendor/alpine-csp.min.js \
  24. && cp node_modules/htmx.org/dist/htmx.min.js /build/vendor/htmx.min.js \
  25. && cp node_modules/sortablejs/Sortable.min.js /build/vendor/sortable.min.js
  26. # --- Stage 2: the actual PHP runtime ------------------------------------
  27. FROM php:8.3-apache
  28. RUN apt-get update && apt-get install -y --no-install-recommends \
  29. libsqlite3-dev libzip-dev libpng-dev libjpeg-dev libfreetype6-dev unzip git \
  30. && docker-php-ext-configure gd --with-freetype --with-jpeg \
  31. && docker-php-ext-install pdo pdo_sqlite zip gd \
  32. && a2enmod rewrite \
  33. && rm -rf /var/lib/apt/lists/*
  34. COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
  35. WORKDIR /var/www/html
  36. COPY composer.json composer.lock* ./
  37. RUN composer install --no-dev --no-interaction --prefer-dist --no-progress
  38. COPY . .
  39. # Place the compiled CSS + vendored JS where Apache can serve them.
  40. COPY --from=css-builder /build/app.css /var/www/html/public/assets/css/app.css
  41. COPY --from=css-builder /build/vendor/ /var/www/html/public/assets/js/vendor/
  42. # Twig cache lives under data/ alongside the SQLite file. www-data must be
  43. # able to write there at request time so first-render template compilation
  44. # succeeds.
  45. RUN mkdir -p /var/www/data /var/www/data/sessions /var/www/html/data/twig-cache \
  46. && chown -R www-data:www-data /var/www/data /var/www/html/data \
  47. && printf '%s\n' \
  48. '<VirtualHost *:80>' \
  49. ' DocumentRoot /var/www/html/public' \
  50. ' <Directory /var/www/html/public>' \
  51. ' Options -Indexes +FollowSymLinks' \
  52. ' AllowOverride All' \
  53. ' Require all granted' \
  54. ' FallbackResource /index.php' \
  55. ' </Directory>' \
  56. ' ErrorLog ${APACHE_LOG_DIR}/error.log' \
  57. ' CustomLog ${APACHE_LOG_DIR}/access.log combined' \
  58. '</VirtualHost>' \
  59. > /etc/apache2/sites-available/000-default.conf
  60. # R01-N22: run migrations at container start, before Apache binds the port.
  61. # The request path no longer auto-migrates — it only checks and refuses to
  62. # serve when something is pending — so a missed entrypoint produces a loud
  63. # 503, not silent stale-schema serving.
  64. RUN install -m 0755 /var/www/html/bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
  65. EXPOSE 80
  66. ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
  67. CMD ["apache2-foreground"]