dunglas / frankenphp

🧟 The modern PHP app server
https://frankenphp.dev
MIT License
6.64k stars 218 forks source link

[QUESTION] Is it possible to run multiple FrankenPHP containers at the same time? #670

Open Natetronn opened 5 months ago

Natetronn commented 5 months ago

Hello!

Does anyone know how to run multiple containers of FrankenPHP at the same time?

I'd like to use it for local dev environment instead of XAMPP (WAMP/MAMP et al.) and this for a bunch of different projects; that all live in different directories and will all use different containers (if possible.)

If I'm not mistaken, after some research, Caddy requires the host's ports 80 and 443 for ACME Cert reasons. So changing those to, say, 8080:80 and 4443:443 doesn't work. (?) I'm using custom testing domains that I set in /etc/hosts (host side) and then in the Caddyfile. That works fine, as long as I keep the ports as is, but if I change the ports to allow for multiple containers it no longer works.

Aside: I tried, but don't know how to run php_server and reverse_proxy localhost:4443 at the same time or if it's even possible, thinking that might lead to a possible solution.

Turning off and on containers isn't the end of the world, but it's less than ideal. With that said, is there a better way to manage a bunch of projects with FrankenPHP all running at the same time?

Appreciate any suggestions, thank you!

sneycampos commented 5 months ago

maybe changing https_port in Caddyfile? https://caddyserver.com/docs/caddyfile/options#https-port

StephenMiracle commented 5 months ago

auto_https disable_redirects can be set as a global option in Caddy. Then you can set the server_name to something like :8095

Natetronn commented 5 months ago

Thanks for the replies!

I wasn't able to figure anything further using those suggestions, however, I got it working on Linux by setting the IP address in /etc/hosts using the container's IP address instead of 127.0.0.1, as I had before.

On Windows with Docker Desktop (in WSL2 mode) I couldn't get it working with changed host ports unless I used the port in the url too. So one could add the various ports to that url or just use one container at a time and stick to the default host ports like so:

   ports:
     - 80:80
     - 443:443
     - 443:443/udp

If anyone wants to play around here's what I have so far:

docker-compose.yml

version: "3.8"

services:
  frankenphp:
    container_name: mydomain
    # image: dunglas/frankenphp
    build: .
    restart: unless-stopped
    ports:
      - 8081:80
      - 4434:443
      - 4434:443/udp
    volumes:
      - ./public:/app/public
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./caddy/certs:/certs
      - ./caddy/log:/var/log
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
    environment:
      # sets the domain in Caddyfile - make sure to use the same domain when creating your self-signed certificates
      - DOMAIN=mydomain.loc

Dockerfile

FROM dunglas/frankenphp:latest-php8.3.4-alpine

# Add additional PHP extensions or Composer
# https://frankenphp.dev/docs/docker/#how-to-install-more-php-extensions
# https://github.com/mlocati/docker-php-extension-installer
# https://github.com/mlocati/docker-php-extension-installer?tab=readme-ov-file#supported-php-extensions
# https://github.com/mlocati/docker-php-extension-installer?tab=readme-ov-file#installing-composer
RUN install-php-extensions gd xdebug yaml

Caddyfile

{
    # Enable FrankenPHP
    frankenphp
    # Configure when the directive must be executed
    order php_server before file_server
}

{$DOMAIN} {
    # Enable logging; update as needed - https://caddyserver.com/docs/caddyfile/directives/log
    # log {
    #   output file /var/log/access.log
    # }

    # tls internal works, but https cert won't be valid; use your own self-signed certs instead if you want https
    # tls internal 

    # Generate your own self-signed certificates using mkcert or similar - set the URL environment variable to the same domain name
    tls /certs/{$DOMAIN}.cert.pem /certs/{$DOMAIN}.key.pem

    # Serve static files
    root * /app/public

    # Enable compression (optional)
    encode zstd br gzip

    # Execute PHP (FrankenPHP) in the aboven root directory and serve assets
    php_server
}

/etc/hosts 172.XXX.X.X mydomain.loc

or

c:\Windows\System32\drivers\etc\hosts 127.0.0.1 mydomain.loc

Note: you can find the IPAddress using docker inspect <Container ID or Name> | grep IPAddress or just docker inspect <Container ID or Name>. You can also find it in Portainer or Docker Desktop (I think under Inspect tab > network).

To create certs you can use one of these scripts:

create-certs.sh (requires mkcert and possibly nss)

#!/usr/bin/env bash

# Parameters

# Set DOMAIN variable to the value of the first positional parameter,
# or default to "localhost" if no parameter is provided
DOMAIN="${1:-localhost}"

# Set CERTS_DIR variable to the value of the second positional parameter,
# or default to "./certs" if no parameter is provided
CERTS_DIR="${2:-./certs}"

# File names
CERT_PEM_FILE="${CERTS_DIR}/${DOMAIN}.cert.pem"
KEY_PEM_FILE="${CERTS_DIR}/${DOMAIN}.key.pem"

# first ensure required executables exists:
if [[ `which mkcert` == "" ]] || [[ `nss-config nss --version` == "" ]]; then
    echo "Requires: mkcert & nss"
    echo
    echo "Run: sudo pacman -S mkcert nss"
    exit 1
fi

# finally install certificates
echo "-- Installing mkcert ..."
mkcert -install

mkdir -p ${CERTS_DIR}

echo "-- Creating and installing local SSL certificates for domain: ${DOMAIN} ..."
mkcert -cert-file ${CERT_PEM_FILE} -key-file ${KEY_PEM_FILE} "${DOMAIN}"

echo "-- Complete!"
echo
echo "- Now you can run: docker compose up -d"
echo "- Open browser to domain: https://${DOMAIN}"

create-certs.bat (requires choco and mkcert)

@echo off

REM parameters
SET DOMAIN=%1
IF "%DOMAIN%"=="" SET DOMAIN=localhost
SET CERTS_DIR=%2
IF "%CERTS_DIR%"=="" SET CERTS_DIR=.\certs
SET CERT_PEM_FILE=%CERTS_DIR%\%DOMAIN%.cert.pem
SET KEY_PEM_FILE=%CERTS_DIR%\%DOMAIN%.key.pem

REM Check if mkcert is installed
where mkcert >nul 2>nul
IF %ERRORLEVEL% NEQ 0 (
    echo Requires: mkcert
    echo.
    echo Run: choco install mkcert
    exit /b 1
)

REM Finally install certificates
echo -- Installing mkcert ...
mkcert -install

mkdir %CERTS_DIR%

echo -- Creating and installing local SSL certificates for domain: %DOMAIN% ...
mkcert -cert-file %CERT_PEM_FILE% -key-file %KEY_PEM_FILE% "%DOMAIN%"

echo -- Complete!
echo.
echo - Now you can run: docker compose up -d
echo - Open browser to domain: https://%DOMAIN%

Either script will take a domain (mydomain.loc for example) for the first parameter and an output path for the second.

StephenMiracle commented 5 months ago

Interesting. I use Windows Docker with WSL. But I typically just map different ports to different apps that I want to run.

App 1: 8090:80

App 2: 8091:80

etc then I just run localhost:8090 or localhost:8091

Is there a reason why you need certs locally? Doesn't seem like you would need to manage a custom caddyfile if you could avoid the cert requirement for local dev. Every place is unique but I try to avoid local cert management on windows whenever possible.

Asociateone commented 5 months ago

@Natetronn I use DDEV instead of XAMPP/MAMP that seems allot simpeler just for a dev environment.

https://ddev.com/

But it is also possible with caddy reverse proxy and your host file i think.

With a reverse proxy from caddy it uses a internal port of the container not an exposed port in the host machine:

so in the Caddyfile from your caddy network there would be something like this:

url.com { reverse_proxy dockercontainername:80 }

for the frankenphp container make sure it is not creating an ssl certificate because your caddy network does that for you.

services: php: image: dunglas/frankenphp restart: always networks:

  • caddy_caddy <- use the network that your caddy router is listening on environment: SERVER_NAME: :80
Natetronn commented 5 months ago

@Asociateone thanks for the suggestion!

I am familiar with ddev (and Devilbox et al.), but I wanted to move in a different direction; one that is a bit more minimal, where you only add the things you need, not a full blown system with opinions, where I won't use half the stuff included. Also, I want it more portable, where I can use the same or mostly the same setup locally and then also on the production side (like on DO or Linode etc.)

Aside: this reminds me, I maintain the Docksal AUR package even though I don't personally use it. I should check on that.

I already had a "normal" docker php / caddy setup working that is similar to frankenphp, but frankenphp was slightly less complicated (or so I thought) in that it was all wrapped up in one container, so I was trying to move to that for the sake of simplicity and others. I even had docker php extension installer working, which is one of the pros of frankenphp.

With that said, not sure it would make sense at that point to even use frankenphp (if I was moving to a standalone caddy). But I'll think about it...and now that I'm thinking of it, frankenphp does have some other features to consider, like performance, for example, so maybe still something to consider.

@StephenMiracle sorry for the late response. Yeah, for sure...I don't really NEED local certs, but it was more of a nice to have kind of WANT; trying to get as close to the real thing as possible kind of thing.

In regards to Windows, yeah, I use Linux myself lol. I do, however, have Windows (dual boot) setup for testing this kind of stuff. And I have some friends and colleagues who use Windows and my attempt to resolve this was really for their sake. Everything was working as I wanted on my side, but not on the Windows side. And, well, sometimes I get going down a path and just don't know when to give up, I guess lol

And on that note, I'll leave this open for now, in case anyone wants to continue the conversation, but this is mostly a non-issue at this point, so don't feel the need to keep trying to solve this; unless you want to, of course. I've mostly moved on, though.

Anyway, I really appreciate everyone suggestions and insights!

sneycampos commented 4 months ago

Anyway, I really appreciate everyone suggestions and insights!

Did you found any solution? what you think about this:

docker-compose.yml

services:

    php:
        working_dir: /app
        environment:
            - ENV REQUEST_MAX_EXECUTION_TIME=0
            - HTTP_PORT=8080
            - HTTPS_PORT=8443
            - SERVER_NAME=localhost:${HTTP_PORT}
        build:
            context: .
            dockerfile: Dockerfile
        volumes:
            - ./:/app
            - ./Caddyfile:/etc/caddy/Caddyfile
        ports:
            - 8080:8080
            - 8443:8443

    php-2:
        working_dir: /app
        environment:
            - ENV REQUEST_MAX_EXECUTION_TIME=0
            - HTTP_PORT=8081
            - HTTPS_PORT=8444
            - SERVER_NAME=localhost:${HTTP_PORT}
        build:
            context: .
            dockerfile: Dockerfile
        volumes:
            - ./:/app
            - ./Caddyfile:/etc/caddy/Caddyfile
        ports:
            - 8081:8081
            - 8444:8444

Dockerfile

FROM dunglas/frankenphp:latest-alpine

RUN apk add --no-cache git

RUN install-php-extensions \
    pdo_mysql \
    gd \
    intl \
    zip

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

ARG USER=application
ARG UID=1000

RUN addgroup -g ${UID} ${USER}; \
    adduser -D -u ${UID} -G ${USER} ${USER}; \
    chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy;

USER ${USER}

ARG HTTP_PORT=8080
ARG HTTPS_PORT=8443

EXPOSE ${HTTP_PORT} ${HTTPS_PORT}

ENV HTTP_PORT=${HTTP_PORT}
ENV HTTPS_PORT=${HTTPS_PORT}

Caddyfile

{
    {$CADDY_GLOBAL_OPTIONS}

    http_port {$HTTP_PORT:80}
    https_port {$HTTPS_PORT:443}

    frankenphp {
        {$FRANKENPHP_CONFIG}
    }

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

{$CADDY_EXTRA_CONFIG}

{$SERVER_NAME:localhost} {
    root * public/
    encode zstd br gzip

    {$CADDY_SERVER_EXTRA_DIRECTIVES}

    php_server
}

Using this way you can have multiple https://localhost (using different ports)