dunglas / symfony-docker

A Docker-based installer and runtime for Symfony. Install: download and `docker compose up`.
https://dunglas.dev/2021/12/symfonys-new-native-docker-support-symfony-world/
2.62k stars 779 forks source link

Call to undefined function dirname() #578

Closed tsantos84 closed 9 months ago

tsantos84 commented 9 months ago

I'm adapting an existing Symfony 7 application to use Docker + Frankenphp based on your setup. The dev environment works nicely but when I try to run the prod environment, which uses workers to handle the requests, I'm getting the strange error PHP Fatal error: Uncaught Error: Call to undefined function dirname() in /app/public/index.php. This is strange because dirname is a built-in PHP's function.

Full error:

{"level":"error","ts":1708965354.3759089,"msg":"PHP Fatal error:  Uncaught Error: Call to undefined function dirname() in /app/public/index.php:5\nStack trace:\n#0 {main}\n  thrown in /app/public/index.php on line 5","syslog_level":"err"}

Caddyfile:

{
    {$CADDY_GLOBAL_OPTIONS}

    frankenphp {
        {$FRANKENPHP_CONFIG}
    }

    # https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm
    order vulcain after reverse_proxy
    order php_server before file_server
}

{$CADDY_EXTRA_CONFIG}

{$SERVER_NAME:localhost} {
    log {
        # Redact the authorization query parameter that can be set by Mercure
        format filter {
            wrap console
            fields {
                uri query {
                    replace authorization REDACTED
                }
            }
        }
    }

    root * /app/public
    encode zstd br gzip

    vulcain

    {$CADDY_SERVER_EXTRA_DIRECTIVES}

    # Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
    header ?Permissions-Policy "browsing-topics=()"

    php_server
}

worker.Caddyfile

worker {
    file ./public/index.php
    num 2
    env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
}

FRANKENPHP_CONFIG

FRANKENPHP_CONFIG="import worker.Caddyfile"

Dockerfile

#syntax=docker/dockerfile:1.4

# Versions
FROM dunglas/frankenphp:1-php8.3-alpine AS frankenphp_upstream

# The different stages of this Dockerfile are meant to be built into separate images
# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage
# https://docs.docker.com/compose/compose-file/#target

# Base FrankenPHP image
FROM frankenphp_upstream AS frankenphp_base

# persistent / runtime deps
# hadolint ignore=DL3018
RUN apk add --no-cache \
        acl \
        file \
        gettext \
        git \
    ;

RUN set -eux; \
    install-php-extensions \
        @composer \
        apcu \
        intl \
        opcache \
        zip \
        pdo_mysql \
        gd \
    ;

# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
ENV COMPOSER_ALLOW_SUPERUSER=1

###> recipes ###
###< recipes ###

COPY --link docker/php/conf.d/app.ini $PHP_INI_DIR/conf.d/
COPY --link --chmod=755 docker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
COPY --link docker/caddy/Caddyfile /etc/caddy/Caddyfile

ENTRYPOINT ["docker-entrypoint"]

HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ]

# Dev FrankenPHP image
FROM frankenphp_base AS frankenphp_dev

ENV APP_ENV=dev XDEBUG_MODE=off
VOLUME /app/var/

RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"

RUN set -eux; \
    install-php-extensions \
        xdebug \
    ;

COPY --link docker/php/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/

CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ]

# Prod FrankenPHP image
FROM frankenphp_base AS frankenphp_prod

ENV APP_ENV=prod
ENV FRANKENPHP_CONFIG="import worker.Caddyfile"

RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

COPY --link docker/php/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/
COPY --link docker/caddy/worker.Caddyfile /etc/caddy/worker.Caddyfile

# prevent the reinstallation of vendors at every changes in the source code
COPY --link composer.* symfony.* ./
RUN set -eux; \
    composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress;

# copy sources
COPY --link . ./
RUN rm -Rf docker/

RUN set -eux; \
    mkdir -p var/cache var/log; \
    composer dump-autoload --classmap-authoritative --no-dev; \
    composer dump-env prod; \
    composer run-script --no-dev post-install-cmd; \
    chmod +x bin/console; sync;
7-zete-7 commented 9 months ago

@tsantos84 hello!

Can you show content of your public/index.php file?

tsantos84 commented 9 months ago

Sure.

<?php

use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

But, after debugging the problem I found that opcache is causing the error. I've just comment the opcache line on Dockerfile and the error stopped. I'm assuming that on production the absense of opcache wont impact on performance once the app is loaded in memory with Frankenphp worker mode, right?

dunglas commented 9 months ago

Yes, but having Opcache still has benefits (faster worker boot time etc), and that's a very weird issue. Are you able to provide a reproducer? That would be nice to report this issue to PHP.

tsantos84 commented 9 months ago

Sure, I will try to create a reproducer. Basically the same Dockerfile, it means based on dunglas/frankenphp:1-alpine, right?

dunglas commented 9 months ago

Anything allowing them to reproduce would likely interest them!

dunglas commented 9 months ago

Could you check if switching to Debian fixes the issue by the way? Moving to Debian is on the roadmap: #555

tsantos84 commented 9 months ago

Anything allowing them to reproduce would likely interest them!

@dunglas , I've found the issue. The problem happens when we provide a nonexistent file to opcache.preload (and that was my case).

I've managed to reproduce the issue by using this repo and make a small change on it. The step 3 is the offender.

  1. Install a fresh Symfony app following the dunglas/symfony-docker instructions and run the app on dev environment
  2. Stop the app with docker compose down
  3. Change the file frankenphp/conf.d/app.prod.ini and provide a nonexistent file to opcache.preload (eg. /foo/bar.php)
  4. Rebuild the image to prod env: docker compose -f compose.yaml -f compose.prod.yaml build --no-cache
  5. Provide JWT keys on compose.prod.yaml
  6. Run the app for production: docker compose -f compose.yaml -f compose.prod.yaml up -d

After that you will see the following log on app's container:

{"level":"error","ts":1709164144.1067986,"msg":"PHP Fatal error:  Uncaught Error: Call to undefined function dirname() in /app/public/index.php:5\nStack trace:\n#0 {main}\n  thrown in /app/public/index.php on line 5","syslog_level":"err"}
tsantos84 commented 9 months ago

Could you check if switching to Debian fixes the issue by the way? Moving to Debian is on the roadmap: #555

No, the weird behavior happens on Debian as well. I've tried to build an official PHP image, install opcache and provide a wrong filename for opcache.preload but the issue does not happen. 🫤

I don't know if the issue resides on Frankenphp + Opcache + wrong filename or just PHP + Opcache + wrong filename. In this case, I don't believe that dunglas/symfony-docker should fix the issue and this issue could be closed. I'll let you decide what to do with it, ok? (close it or keep it opened)

Let me know if I should open an issue directly on Frankenphp repository or official PHP.

dunglas commented 9 months ago

I think that official PHP is more appropriate.