diff --git a/Dockerfile b/Dockerfile index 6ac9dd3..a1e9654 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,130 +1,93 @@ -# syntax=docker/dockerfile:1.6 +# ---------- 1) Build Composer deps ---------- + FROM composer:2 AS vendor + ARG COMPOSER_NO_DEV=1 + WORKDIR /app + COPY composer.json composer.lock ./ + # Disable scripts to avoid "artisan not found" + RUN if [ "$COMPOSER_NO_DEV" = "1" ]; then \ + composer install --no-dev --prefer-dist --no-progress --no-interaction --classmap-authoritative --no-scripts; \ + else \ + composer install --prefer-dist --no-progress --no-interaction --no-scripts; \ + fi + + + # ---------- 2) Copy app source (optional separate build stage) ---------- + FROM alpine:3.20 AS app_src + WORKDIR /app + COPY . ./ + + + # ---------- 3) Final image: Nginx + PHP-FPM ---------- + FROM php:8.4-fpm-alpine + + # System dependencies & build tools + RUN set -eux; \ + apk add --no-cache --virtual .build-deps \ + $PHPIZE_DEPS \ + icu-dev libzip-dev postgresql-dev \ + freetype-dev libjpeg-turbo-dev libpng-dev \ + oniguruma-dev pkgconf \ + && apk add --no-cache \ + icu-libs libzip libpq tzdata bash git nginx supervisor curl \ + oniguruma \ + \ + # PHP extensions + && docker-php-ext-configure intl \ + && docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install -j"$(nproc)" \ + intl opcache zip bcmath mbstring gd pdo pdo_pgsql pgsql \ + \ + # Cleanup + && apk del .build-deps \ + && rm -rf /var/cache/apk/* /tmp/* -################################# -# Stage 1: Build PHP + Composer # -################################# -FROM php:8.4-fpm-alpine AS php_build - -# System deps for PHP extensions -RUN set -eux; \ - apk add --no-cache git unzip icu-dev libzip-dev oniguruma-dev postgresql-dev autoconf build-base - -# PHP extensions needed by Laravel -RUN set -eux; \ - docker-php-ext-configure zip; \ - docker-php-ext-install -j"$(nproc)" \ - pdo_mysql pdo_pgsql pgsql zip intl mbstring bcmath opcache - -# Composer -ENV COMPOSER_ALLOW_SUPERUSER=1 COMPOSER_HOME=/tmp/composer -COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer - -WORKDIR /app - -# --- PASS 1: deps only (cacheable), NO scripts (artisan not present yet) --- -COPY composer.json composer.lock ./ -RUN composer install \ - --no-dev --no-interaction --prefer-dist \ - --no-scripts --no-progress --optimize-autoloader - -# Now copy the whole application -COPY . . - -# --- PASS 2: run scripts now that artisan exists --- -# (This is quick: vendor is already populated; this mainly runs package:discover) -RUN composer install \ - --no-dev --no-interaction --prefer-dist \ - --optimize-autoloader - -################################# -# Stage 2: Build Frontend (Vite)# -################################# -FROM node:22-alpine AS node_build -WORKDIR /app - -# Bring full app INCLUDING vendor so imports like ../../vendor/... work -COPY --from=php_build /app /app - -RUN apk add --no-cache python3 make g++ - -# Install JS deps with the appropriate lockfile -RUN set -eux; \ - if [ -f pnpm-lock.yaml ]; then corepack enable && pnpm i --frozen-lockfile; \ - elif [ -f yarn.lock ]; then corepack enable && yarn install --frozen-lockfile; \ - elif [ -f package-lock.json ]; then npm ci; \ - else npm i; fi - -ENV NODE_ENV=production -RUN npm run build - -################################# -# Stage 3: Runtime Image # -################################# -FROM php:8.4-fpm-alpine - -# Runtime packages -RUN set -eux; \ - apk add --no-cache nginx supervisor curl icu-libs libzip oniguruma libpq - -# PHP runtime extensions -RUN set -eux; \ - apk add --no-cache icu-dev libzip-dev oniguruma-dev postgresql-dev autoconf build-base; \ - docker-php-ext-configure zip; \ - docker-php-ext-install -j"$(nproc)" \ - pdo_mysql pdo_pgsql pgsql zip intl mbstring bcmath opcache; \ - apk del --no-progress --purge icu-dev libzip-dev oniguruma-dev postgresql-dev autoconf build-base || true - -# php.ini production + a bit of opcache tuning -RUN set -eux; \ - mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"; \ - { \ - echo "opcache.enable=1"; \ - echo "opcache.enable_cli=0"; \ - echo "opcache.memory_consumption=192"; \ - echo "opcache.max_accelerated_files=40000"; \ - echo "opcache.validate_timestamps=0"; \ - echo "realpath_cache_size=4096K"; \ - echo "realpath_cache_ttl=600"; \ - } >> "$PHP_INI_DIR/conf.d/99-opcache.ini" - -# FPM config (listen on TCP for nginx) -RUN set -eux; \ - sed -ri 's|^;?clear_env\s*=.*|clear_env = no|g' /usr/local/etc/php-fpm.d/www.conf; \ - sed -ri 's|^listen = .*|listen = 127.0.0.1:9000|g' /usr/local/etc/php-fpm.d/www.conf; \ - sed -ri 's|^user\s*=.*|user = nginx|g' /usr/local/etc/php-fpm.d/www.conf; \ - sed -ri 's|^group\s*=.*|group = nginx|g' /usr/local/etc/php-fpm.d/www.conf; \ - { echo "ping.path = /ping"; echo "pm.status_path = /status"; echo "catch_workers_output = yes"; } >> /usr/local/etc/php-fpm.d/www.conf - -# Paths -RUN set -eux; \ - mkdir -p /run/nginx /var/log/nginx /var/log/supervisor \ - /etc/nginx/conf.d /var/www/html \ - /var/cache/nginx /var/lib/nginx/tmp - -WORKDIR /var/www/html - -# App + built assets -COPY --from=php_build --chown=nginx:nginx /app /var/www/html -COPY --from=node_build --chown=nginx:nginx /app/public/build /var/www/html/public/build - -# Configs (use the nginx site config you posted) -COPY ./docker/nginx.conf /etc/nginx/nginx.conf -COPY ./docker/site.conf /etc/nginx/conf.d/default.conf -COPY ./docker/supervisord.conf /etc/supervisord.conf -COPY ./docker/entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh - -# Logs to STDOUT/ERR -RUN ln -sf /dev/stdout /var/log/nginx/access.log && \ - ln -sf /dev/stderr /var/log/nginx/error.log - -# Laravel writable dirs & perms -RUN mkdir -p storage/framework/{cache,sessions,views} storage/logs bootstrap/cache && \ - chown -R nginx:nginx storage bootstrap/cache && \ - chmod -R ug+rwX storage bootstrap/cache - -ENV APP_ENV=production APP_DEBUG=false APP_URL=http://localhost APP_PORT=8080 -EXPOSE 8080 - -USER nginx:nginx -CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] + + # Configure PHP-FPM to use UNIX socket for Nginx + RUN set -eux; \ + sed -ri 's|^;?listen = .*$|listen = /run/php-fpm.sock|' /usr/local/etc/php-fpm.d/zz-docker.conf; \ + { \ + echo "memory_limit=512M"; \ + echo "upload_max_filesize=64M"; \ + echo "post_max_size=64M"; \ + echo "max_execution_time=60"; \ + echo "date.timezone=UTC"; \ + echo "opcache.enable=1"; \ + echo "opcache.enable_cli=0"; \ + echo "opcache.validate_timestamps=0"; \ + echo "opcache.jit=tracing"; \ + echo "opcache.jit_buffer_size=128M"; \ + echo "opcache.interned_strings_buffer=16"; \ + echo "opcache.max_accelerated_files=20000"; \ + } > /usr/local/etc/php/conf.d/laravel.ini + + # Prepare directories + RUN mkdir -p /run/nginx /run/php /var/log/supervisor /var/www/html /etc/nginx/conf.d + + # Copy app & vendor + COPY --from=app_src /app /var/www/html + COPY --from=vendor /app/vendor /var/www/html/vendor + + # Copy configs + COPY deploy/nginx/default.conf /etc/nginx/conf.d/default.conf + COPY deploy/supervisord.conf /etc/supervisord.conf + + WORKDIR /var/www/html + + # (Optional) Run artisan discovery after copy — nonfatal + RUN php artisan package:discover --ansi || true + + # Fix permissions for Laravel + RUN set -eux; \ + mkdir -p storage bootstrap/cache; \ + chown -R www-data:www-data storage bootstrap/cache; \ + find storage -type d -exec chmod 775 {} \; ; \ + find storage -type f -exec chmod 664 {} \; ; \ + chmod -R 775 bootstrap/cache + + EXPOSE 80 + HEALTHCHECK --interval=30s --timeout=5s --retries=5 \ + CMD curl -fsS http://localhost/healthz || exit 1 + + # Supervisor runs both services + CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] + \ No newline at end of file diff --git a/deploy/nginx/default.conf b/deploy/nginx/default.conf new file mode 100644 index 0000000..e50b199 --- /dev/null +++ b/deploy/nginx/default.conf @@ -0,0 +1,48 @@ +server { + listen 80 default_server; + server_name _; + + root /var/www/html/public; + index index.php; + + # Healthcheck + location = /healthz { return 200 "ok\n"; add_header Content-Type text/plain; } + + # Static + location ~* \.(?:css|js|mjs|map|jpg|jpeg|png|gif|ico|svg|webp|avif|ttf|otf|woff|woff2)$ { + access_log off; log_not_found off; + expires 7d; + add_header Cache-Control "public, max-age=604800, immutable"; + try_files $uri =404; + } + + # Front controller + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # PHP via unix socket + location ~ \.php$ { + include fastcgi_params; + fastcgi_index index.php; + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_read_timeout 60s; + fastcgi_buffering on; + fastcgi_buffers 16 16k; + fastcgi_buffer_size 32k; + } + + client_max_body_size 64m; + + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + gzip on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_types text/plain text/css application/json application/javascript application/xml+rss application/xml application/x-font-ttf font/opentype image/svg+xml; +} diff --git a/deploy/supervisord.conf b/deploy/supervisord.conf new file mode 100644 index 0000000..53536fe --- /dev/null +++ b/deploy/supervisord.conf @@ -0,0 +1,21 @@ +[supervisord] +nodaemon=true +logfile=/var/log/supervisor/supervisord.log + +[program:php-fpm] +command=/usr/local/sbin/php-fpm -F +autorestart=true +priority=10 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autorestart=true +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docker/supervisord.conf b/docker/supervisord.conf deleted file mode 100644 index 00f4414..0000000 --- a/docker/supervisord.conf +++ /dev/null @@ -1,44 +0,0 @@ -[supervisord] -nodaemon=true -logfile=/dev/fd/1 -logfile_maxbytes=0 -pidfile=/tmp/supervisord.pid -childlogdir=/tmp - -; one-shot init before services -[program:init] -command=/usr/local/bin/entrypoint.sh -startsecs=0 -startretries=1 -autorestart=false -priority=5 -stdout_logfile=/dev/fd/1 -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/fd/2 -stderr_logfile_maxbytes=0 - -[program:php-fpm] -command=/usr/local/sbin/php-fpm -F -priority=10 -autostart=true -autorestart=true -stopsignal=QUIT -stopasgroup=true -killasgroup=true -stdout_logfile=/dev/fd/1 -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/fd/2 -stderr_logfile_maxbytes=0 - -[program:nginx] -command=/usr/sbin/nginx -g "daemon off;" -priority=20 -autostart=true -autorestart=true -stopsignal=QUIT -stopasgroup=true -killasgroup=true -stdout_logfile=/dev/fd/1 -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/fd/2 -stderr_logfile_maxbytes=0