Trying to build symfony app with docker / frankenphp / caddy

1. The problem I’m having:

Hello everyone, I’m trying to build a symfony app with caddy, frankenphp and docker. I can get caddy to work but I can’t get it to communicate with php. I tried a lot of things but none of them work, maybe you will see errors.

2. Error messages and/or full log output:

Here the error from the container caddy when i’m trying to access to https://localhost

{"level":"error","ts":1715438268.3024645,"logger":"http.log.error","msg":"dialing backend: dial tcp 172.26.0.6:9000: connect: connection refused","request":{"remote_ip":"172.26.0.1","remote_port":"58090","client_ip":"172.26.0.1","proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"Cache-Control":["max-age=0"],"Sec-Ch-Ua":["\"Microsoft Edge\";v=\"125\", \"Chromium\";v=\"125\", \"Not.A/Brand\";v=\"24\""],"Accept-Language":["fr,fr-FR;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"],"Priority":["u=0, i"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["none"],"Sec-Fetch-Dest":["document"],"Cookie":[],"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"]},"tls":{"resumed":false,"version":772,"cipher_suite":4867,"proto":"h2","server_name":"localhost"}},"duration":0.000932566,"status":502,"err_id":"0em2ykev8","err_trace":"reverseproxy.statusError (reverseproxy.go:1267)"}

3. Caddy version:

caddy --version
v2.7.6 h1:w0NymbG2m9PcvKWsrXO6EEkY9Ru4FJK8uQbYcev1p3A=

4. How I installed and ran Caddy:

This is my docker-compose.yml

  php:
    build:
      context: ./docker/frankenphp
      args:
        TIMEZONE: ${TIMEZONE}
    restart: always
    networks:
      - phpapp
    ports:
      - '9000:9000'
    volumes:
      - ./symfony/:/var/www/symfony/:cached
      - ./symfony/var/log/:/var/log/
    links:
      - db:db-host
      - redis
      - rabbitmq
    depends_on:
      - mailhog
      - rabbitmq
    env_file: symfony/.env

  caddy:
    image: caddy:2-alpine
    restart: always
    networks:
      - phpapp
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./docker/frankenphp/Caddyfile:/etc/caddy/Caddyfile
      - ./symfony:/usr/share/caddy/symfony:cached
      - ./docker/frankenphp/letsencrypt:/etc/caddy/letsencrypt
    links:
      - php:php-host
    command: /bin/sh -c "caddy fmt --overwrite /etc/caddy/Caddyfile && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile"

networks:
  phpapp:
    name: phpapp

My caddyfile

:443 {
	root * /usr/share/caddy/symfony/public
	file_server

	encode zstd gzip

	# Handle PHP files using php_fastcgi
	php_fastcgi php:9000 {
		root /usr/share/caddy/symfony/public
	}

	# CORS preflight requests setup
	@preflight method OPTIONS
	handle @preflight {
		header {
			Access-Control-Allow-Origin *
			Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS"
			Access-Control-Allow-Headers "Authorization, DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range"
			Access-Control-Max-Age 1728000
			Content-Type "text/plain; charset=utf-8"
			Content-Length 0
		}
		respond 204
	}

	# Regular PHP requests
	@php path *.php
	handle @php {
		header {
			Access-Control-Allow-Origin *
			Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS"
			Access-Control-Allow-Headers "Authorization, DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range"
			Access-Control-Expose-Headers "Content-Length,Content-Range"
		}
	}

	# SSL/TLS certificates (replace with actual paths)
	tls /etc/caddy/letsencrypt/starter.com.cert /etc/caddy/letsencrypt/starter.com.key

	# Security headers
	header {
		Strict-Transport-Security "max-age=31536000;"
		# More headers as needed
	}
}

My dockerfile

FROM dunglas/frankenphp:1-php8.2 AS frankenphp_upstream

ARG TIMEZONE
ARG JWT_PASSPHRASE

WORKDIR /var/www/symfony

# Install system dependencies
RUN apt-get update && apt-get install -y \
    acl \
    file \
    gettext \
    git \
    vim \
    nano \
    wget \
    unzip \
    cron \
    curl \
    rsync \
    sudo \
    openssh-server \
    ocaml \
    expect \
    libmcrypt-dev \
    libicu-dev \
    libxml2-dev libxslt1-dev \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libxrender1 \
    libfontconfig1 \
    libx11-dev \
    libxtst6 \
    libpng-dev \
    zlib1g-dev \
    libjpeg-dev \
    libonig-dev \
    libwebp-dev \
    libqt5svg5 \
    jpegoptim \
    optipng \
    webp \
    gnupg2 \
    libpq-dev \
    libzip-dev \
    && docker-php-ext-configure hash --with-mhash \
    && docker-php-ext-configure zip \
    && docker-php-ext-install -j$(nproc) intl xsl zip pdo_mysql opcache soap bcmath \
    && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && mkdir /var/run/sshd \
    && apt-get install -y gnupg2 \
    && curl -sL https://deb.nodesource.com/setup_18.x | bash - \
    && apt-get install -y nodejs \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update && sudo apt-get install -y yarn \
    && echo 'alias sf="php bin/console"' >> ~/.bashrc \
    && echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Node.js and Yarn
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - \
    && apt-get install -y nodejs \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update \
    && apt-get install -y yarn

# Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && composer --version

# PHP and Webserver Configurations
COPY ./conf.d/app.ini /usr/local/etc/php/php.ini
COPY ./conf.d/auth.json /var/www/.composer/
COPY ./docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# Set up Xdebug
RUN pecl install xdebug-3.3.1 \
    && docker-php-ext-enable xdebug \
    && echo "xdebug.mode=debug,coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

# Set up GD and PostgreSQL
RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
    && docker-php-ext-install -j$(nproc) gd \
    && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
    && docker-php-ext-install pdo pdo_pgsql

# Set timezone
RUN ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo ${TIMEZONE} > /etc/timezone \
    && printf '[PHP]\ndate.timezone = "%s"\n', ${TIMEZONE} > /usr/local/etc/php/conf.d/tzone.ini

# Security tools
RUN curl -L https://github.com/fabpot/local-php-security-checker/releases/download/v2.0.6/local-php-security-checker_2.0.6_linux_amd64 --output local-php-security-checker \
    && mv local-php-security-checker /usr/local/bin/local-php-security-checker && chmod 755 /usr/local/bin/local-php-security-checker

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]

EXPOSE 9000

my docker-entrypoint.sh

#!/bin/sh
set -e

if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
	# Install the project the first time PHP is started
	# After the installation, the following block can be deleted
	if [ ! -f composer.json ]; then
		rm -Rf tmp/
		composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install

		cd tmp
		cp -Rp . ..
		cd -
		rm -Rf tmp/

		composer require "php:>=$PHP_VERSION" runtime/frankenphp-symfony
		composer config --json extra.symfony.docker 'true'

		if grep -q ^DATABASE_URL= .env; then
			echo "To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build -d --wait"
			sleep infinity
		fi
	fi

	if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then
		composer install --prefer-dist --no-progress --no-interaction
	fi

	if grep -q ^DATABASE_URL= .env; then
		echo "Waiting for database to be ready..."
		ATTEMPTS_LEFT_TO_REACH_DATABASE=60
		until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
			if [ $? -eq 255 ]; then
				# If the Doctrine command exits with 255, an unrecoverable error occurred
				ATTEMPTS_LEFT_TO_REACH_DATABASE=0
				break
			fi
			sleep 1
			ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1))
			echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left."
		done

		if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
			echo "The database is not up or not reachable:"
			echo "$DATABASE_ERROR"
			exit 1
		else
			echo "The database is now ready and reachable"
		fi

		if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then
			php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing
		fi
	fi

	setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var
	setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var
fi

exec docker-php-entrypoint "$@"

Despite the network, my php dockers and caddy cannot communicate with each other and I don’t know why.

Thanks for your help !

FrankenPHP isn’t php-fpm, you can’t use it that way.

FrankenPHP is a custom distribution of Caddy which runs PHP directly. You don’t use php_fastcgi if you use FrankenPHP. You don’t need a regular Caddy container beside your FrankenPHP one, because FrankenPHP is Caddy.

See the FrankenPHP docs, it explains how to modify your Caddyfile inside your FrankenPHP container as needed.

2 Likes

Thank you for your answer, it was clear I managed to get my app running!!

1 Like