laravel / octane

Supercharge your Laravel application's performance.
https://laravel.com/docs/octane
MIT License
3.74k stars 291 forks source link

HTTPS doesn't work when running Octane & FrankenPHP inside Docker #811

Closed philbates35 closed 7 months ago

philbates35 commented 7 months ago

Octane Version

2.2.7

Laravel Version

10.40.0

PHP Version

8.3.1

What server type are you using?

FrankenPHP

Server Version

FrakenPHP 1.0.3; Caddy v2.7.6

Database Driver & Version

No response

Description

When running Octane & FrakenPHP in a Docker container, accessing the app via HTTPS doesn't work:

curl -k https://localhost
curl: (35) OpenSSL/3.0.9: error:0A000438:SSL routines::tlsv1 alert internal error

However, using HTTP works just fine, which rules out a host and / or port misconfiguration and leads me to believe the issue is on the Octane side - especially because when running vanilla FrankenPHP without Octane, I am able to access the app on HTTPS just fine which also rules out issues with the certificate.

To summarise:

Let me know if anything is unclear or if you need any more info from me.

Steps To Reproduce

1. HTTPS with Octane & FrankenPHP in Docker :x:

  1. Create a new Laravel app in example-app/ and install octane (type yes to accept still in Beta):

    docker run --rm -it -u $(id -u $USER) -v $PWD:/app composer:latest composer create-project laravel/laravel example-app
    cd example-app
    docker run --rm -it -u $(id -u $USER) -v $PWD:/app composer:latest composer require laravel/octane
    docker run --rm -it -u $(id -u $USER) -v $PWD:/app dunglas/frankenphp:1.0-php8.3 php artisan octane:install --server=frankenphp
  2. Create the following Dockerfile:

    FROM dunglas/frankenphp:1.0-php8.3
    
    RUN install-php-extensions pcntl;
    
    ENTRYPOINT ["php", "artisan", "octane:start", "--server=frankenphp", "--host=0.0.0.0", "--port=443", "--admin-port=2019", "--https"]
  3. Create the following docker-compose.yml, as per the FrakenPHP docs:

    services:
      php:
        build: .
        ports:
          - 8000:8000
          - 443:443
        volumes:
          - ./:/app:z
          - caddy_data:/data:z
          - caddy_config:/config:z
        tty: true
    
    volumes:
      caddy_data:
      caddy_config:
  4. Run the following command to start octane

    docker-compose up --build

    and see the output:

    ...
    Creating laravel-starter_php_1 ... done
    Attaching to laravel-starter_php_1
    php_1  | 
    php_1  |    INFO  Server running…
    php_1  | 
    php_1  |   Local: https://0.0.0.0:443 
    php_1  | 
    php_1  |   Press Ctrl+C to stop the server
    php_1  | 
    php_1  | 
    php_1  |    WARN  Site block has an unspecified IP address which only matches requests having that Host header; you probably want the 'bind' directive to configure the socket
    php_1  | 
    php_1  |    WARN  stapling OCSP
    php_1  | 
    php_1  |    WARN  installing root certificate (you might be prompted for password)
    ...
  5. Try to access the app from the host machine on HTTPS on https://localhost and watch it fail:

    curl -k https://localhost
    curl: (35) OpenSSL/3.0.9: error:0A000438:SSL routines::tlsv1 alert internal error

    Octane is running https://0.0.0.0:443 inside the container, which should be accessible on the host machine on https://localhost thanks to the 443:443 port mapping and the fact that octane is running on 0.0.0.0 inside the container.

2. HTTP with Octane & FrankenPHP in Docker :heavy_check_mark:

  1. After following the steps above, make the following changes to Dockerfile:
    - ENTRYPOINT ["php", "artisan", "octane:start", "--server=frankenphp", "--host=0.0.0.0", "--port=443", "--admin-port=2019", "--https"]
    + ENTRYPOINT ["php", "artisan", "octane:start", "--server=frankenphp"]
  2. Run:
    docker-compose down && docker-compose up --build
  3. Try to access the app from your host machine on http://localhost:8000 and see that everything works fine:

     curl http://localhost:8000
    
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
    
            <title>Laravel</title>
        ...

    Note that we didn't even set the --host=0.0.0.0 option in the octane:work command, and it still works.


3. HTTPS with vanilla FrankenPHP (no Octane) in Docker :heavy_check_mark:

  1. After following the steps above, remove the ENTRYPOINT entirely from Dockerfile so that we use the default FrakenPHP entry point:

    - ENTRYPOINT ["php", "artisan", "octane:start", "--server=frankenphp"]

    And change

  2. Run:

    docker-compose down && docker-compose up --build
  3. Access the app via HTTPS on https://localhost:

    curl -k https://localhost
    
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
    
            <title>Laravel</title>
    ...
driesvints commented 7 months ago

Hi there,

Thanks for reporting but it looks like this is a question which can be asked on a support channel. Please only use this issue tracker for reporting bugs with the library itself. If you have a question on how to use functionality provided by this repo you can try one of the following channels:

However, this issue will not be locked and everyone is still free to discuss solutions to your problem!

Thanks.

driesvints commented 7 months ago

@dunglas maybe one for you ^

philbates35 commented 7 months ago

@dunglas let me know if you need anything from me, or if there's anything that you'd like me to try to see if it resolves the issue. I'm happy to do whatever if needed to get this working :smile:

dunglas commented 7 months ago

You need to pass explicitly the domain name you want FrankenPHP to generate a certificate for. For instance, for https://localhost, use artisan octane:start --host=localhost --port=443.

Should we add this to the documentation @driesvints?

philbates35 commented 7 months ago

@dunglas I can confirm that changing --host=0.0.0.0 to --host=localhost works, thanks! However interestingly using --host=127.0.0.1 (which is the default) doesn't work when accessing https://127.0.0.1 from the host, I'm not sure if that's intentional or something I'm doing wrong.

To replicate, based on the reproducible steps in my original message, set the Dockerfile ENTRYPOINT to be this:

- # Using this command, the app is accessible at https://localhost on the host 
- ENTRYPOINT ["php", "artisan", "octane:start", "--server=frankenphp", "--host=localhost", "--port=443", "--admin-port=2019", "--https"]
+ # Changing from localhost to 127.0.0.1 causes SSL errors when accessing https://127.0.0.1 on the host
+ ENTRYPOINT ["php", "artisan", "octane:start", "--server=frankenphp", "--host=127.0.0.1", "--port=443", "--admin-port=2019", "--https"]

Then try to connect from the host machine:

curl -k https://127.0.0.1
curl: (35) OpenSSL/3.0.9: error:0A000438:SSL routines::tlsv1 alert internal error

Let me know if you want to try anything to debug this, or whether this is expected behaviour.


Should we add this to the documentation @driesvints?

I just want to mention that the reason I assumed it had to be --host=0.0.0.0 to access a Dockerized Octane app from the host is because in the https://laravel.com/docs/10.x/octane#frankenphp-via-laravel-sail docs section, it has the following as the supervisor script (specifically the --host=0.0.0.0 option):

SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port=80"

I work with Docker a lot, and its fairly common that in order to access a containerized service, sometimes you need to have the service running on0.0.0.0 inside the container to make it accessible outside the container, so I assumed that was also the case here based on those docs. However as you now correctly point out that assumption was wrong, and --host can in fact be anything on in a Dockerized Octane app.

So based on that I have some suggestions to enhance the docs from from the point of view of someone new to Octane & FrankenPHP, feel free to ignore :sweat_smile:

  1. We could change --host=0.0.0.0 to --host=localhost in the above SUPERVISOR_PHP_COMMAND to avoid giving readers the impression it has to be 0.0.0.0 when running Octane in Docker in order to be accessible on the host.
  2. We could expand the https://laravel.com/docs/10.x/octane#frankenphp-via-laravel-sail section a little:
    • Additionally show an example SUPERVISOR_PHP_COMMAND that demonstrates how to use HTTPS. Or maybe this should be added to https://laravel.com/docs/10.x/octane#serving-your-application-via-https which I personally felt was a little sparse (for example, nowhere in that section, or anywhere at all on the Octane docs page in fact, does it mention passing the --https option needed to get the app running on HTTPS which seems like an omission).
    • Tell the reader something along the lines of "the app will now be accessible on HTTPS on the host machine at https://localhost".
    • To demonstrate what all the different options do, I wonder if there's value in even having an example that uses non-standard hosts and ports e.g. using --host=foo.test --port=1234 --https now makes your app accessible at https://foo.test:1234 on the host machine, something like that?
driesvints commented 7 months ago

Should we add this to the documentation @driesvints?

Yeah could do that.