# syntax=docker/dockerfile:1 # ============================================================================= # PHP 8.5 + FrankenPHP - Backend Classeo # ============================================================================= FROM dunglas/frankenphp:1-php8.5-alpine AS base # Install system dependencies RUN apk add --no-cache \ acl \ fcgi \ file \ gettext \ git \ freetype-dev \ icu-dev \ imagemagick-dev \ libjpeg-turbo-dev \ libpng-dev \ libzip-dev \ postgresql-dev \ rabbitmq-c-dev \ linux-headers \ $PHPIZE_DEPS # Install PHP extensions (opcache is pre-installed in FrankenPHP) RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install gd intl pcntl pdo_pgsql zip sockets # Install Imagick extension for image processing (logo resize, etc.) RUN pecl install imagick && docker-php-ext-enable imagick # Install AMQP extension for RabbitMQ RUN pecl install amqp && docker-php-ext-enable amqp # Install Redis extension RUN pecl install redis && docker-php-ext-enable redis # Install Composer COPY --from=composer:2 /usr/bin/composer /usr/bin/composer # Set working directory WORKDIR /app # Configure PHP for production RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" # Custom PHP configuration RUN echo "opcache.enable=1" >> "$PHP_INI_DIR/conf.d/opcache.ini" \ && echo "opcache.memory_consumption=256" >> "$PHP_INI_DIR/conf.d/opcache.ini" \ && echo "opcache.interned_strings_buffer=16" >> "$PHP_INI_DIR/conf.d/opcache.ini" \ && echo "opcache.max_accelerated_files=20000" >> "$PHP_INI_DIR/conf.d/opcache.ini" \ && echo "opcache.validate_timestamps=0" >> "$PHP_INI_DIR/conf.d/opcache.ini" \ && echo "realpath_cache_size=4096K" >> "$PHP_INI_DIR/conf.d/opcache.ini" \ && echo "realpath_cache_ttl=600" >> "$PHP_INI_DIR/conf.d/opcache.ini" # ============================================================================= # Development stage # ============================================================================= FROM base AS dev # Install gosu for proper user switching ENV GOSU_VERSION=1.17 RUN set -eux; \ apk add --no-cache --virtual .gosu-deps dpkg gnupg; \ dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ chmod +x /usr/local/bin/gosu; \ gosu --version; \ gosu nobody true; \ apk del --no-network .gosu-deps # Enable opcache revalidation for dev (zz- prefix loads last alphabetically) RUN echo "opcache.validate_timestamps=1" >> "$PHP_INI_DIR/conf.d/zz-opcache-dev.ini" # Enable Xdebug for development RUN pecl install xdebug && docker-php-ext-enable xdebug RUN echo "xdebug.mode=develop,debug,coverage" >> "$PHP_INI_DIR/conf.d/xdebug.ini" \ && echo "xdebug.client_host=host.docker.internal" >> "$PHP_INI_DIR/conf.d/xdebug.ini" \ && echo "xdebug.start_with_request=trigger" >> "$PHP_INI_DIR/conf.d/xdebug.ini" # Caddy config for FrankenPHP ENV SERVER_NAME=:8000 # In dev mode, we do NOT use worker mode to enable automatic file reloading # Each request loads PHP fresh, so code changes are picked up immediately # Worker mode is only used in production for performance ENV FRANKENPHP_CONFIG="" # Entrypoint: detect host UID/GID and run as matching user # Uses gosu with UID:GID directly (no need to create user in Dockerfile) COPY --chmod=755 <<'EOF' /usr/local/bin/docker-entrypoint.sh #!/bin/sh set -e # Detect UID/GID from mounted /app directory HOST_UID=$(stat -c %u /app) HOST_GID=$(stat -c %g /app) # If root owns /app, run as root (CI environment or volume not mounted) if [ "$HOST_UID" = "0" ]; then # Install dependencies if not present if [ ! -f /app/vendor/autoload.php ]; then echo "Installing Composer dependencies..." composer install --prefer-dist --no-progress --no-interaction fi mkdir -p /app/var/cache /app/var/log exec "$@" fi # Ensure directories exist with correct ownership mkdir -p /app/var/cache /app/var/log /data /config chown -R "$HOST_UID:$HOST_GID" /app/var /data /config 2>/dev/null || true # Install Composer dependencies if not present (as host user) if [ ! -f /app/vendor/autoload.php ]; then echo "Installing Composer dependencies..." gosu "$HOST_UID:$HOST_GID" composer install --prefer-dist --no-progress --no-interaction fi # Run command as host user via gosu (using UID:GID directly) exec gosu "$HOST_UID:$HOST_GID" "$@" EOF EXPOSE 8000 ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"] # ============================================================================= # Production stage # ============================================================================= FROM base AS prod ENV APP_ENV=prod ENV SERVER_NAME=:8000 ENV FRANKENPHP_CONFIG="worker ./public/index.php" # Copy application files COPY . /app # Install dependencies (prod only, optimized) RUN composer install --no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader # Warmup cache RUN php bin/console cache:warmup # Ensure var directory exists with proper permissions RUN mkdir -p var/cache var/log && chmod -R 755 var EXPOSE 8000 CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]