FrankenPHP ve Caddy ile Docker Compose Üzerinde 103 Early Hints Destekli Sunucu Yapısı

2025 PHP konferansında Frankenphp ve Caddy web server ile tanıştım, Frankenphp standartta kullandığımız php alınarak üzerine bir takım eklemeler yapılan bir php servisi, Caddy web server ile birlikte 103 Early Hints web response desteğine sahip oluyor. Bu 103 kodu standartta kullandığımız Nginx ya da Apache sunucularda mevcut değil.
103 Early Hints Nedir?
Bu response kodu tarayıcımıza web sitesi daha yüklenmeden bizim daha önceden belirlediğimiz css, javascript ya da bazı görselleri direkt olarak indirmeye başlıyor, yani biz siteye girmek için bir istekte bulunduğumuzda sitenin kendi html yanıtı gelmeden bu dosyalar indirilmeye başlıyor ve sitenin önyükleme hızını artırmış oluyor. headers_send(103); PHP koduyla bunu yapıyoruz ancak standart kullandığımız PHP içinde bu yoktur, bu fonksiyonu çağırmak istediğinizde PHP Fatal error: Uncaught Error: Call to undefined function headers_send() hatasıyla karşılaşırsınız. Bu fonksiyon Frankenphp içerisinde geliyor.
Laravel web sitemde aşağıdaki gibi bir middleware hazırladım test etmek için, kısa bir süre sonra burayı dinamik bir hale getirerek sitede aktif tema ne ise dosyaları oradan alacağı şekilde düzenleme yapacağım buraya.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EarlyHintsMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$this->frankenphp_send_early_hints([
'; rel=preload; as=style',
'; rel=preload; as=style',
'<'.asset('themes/fontawesome/css/all.min.css').'>; rel=preload; as=style',
'<'.asset('theme/Cryptograph/css/animate.min.css').'>; rel=preload; as=style',
'<'.asset('theme/Cryptograph/css/bootstrap.min.css').'>; rel=preload; as=style',
'<'.asset('theme/Cryptograph/css/slick.min.css').'>; rel=preload; as=style',
'<'.asset('theme/Cryptograph/css/default.min.css').'>; rel=preload; as=style',
'<'.asset('theme/Cryptograph/css/style.min.css').'>; rel=preload; as=style',
'<'.asset('theme/Cryptograph/css/responsive.min.css').'>; rel=preload; as=style',
'<'.asset('theme/Cryptograph/css/custom.min.css').'>; rel=preload; as=style',
'<'.asset('theme/Cryptograph/js/vendor/jquery-3.6.0.min.js').'>; rel=preload; as=script',
'<'.asset('theme/Cryptograph/js/bootstrap.min.js').'>; rel=preload; as=script',
'<'.asset('theme/Cryptograph/js/main.min.js').'>; rel=preload; as=script',
'; rel=preload; as=style',
'; rel=preload; as=script',
'<'.asset('themes/fontawesome/webfonts/fa-solid-900.woff2').'>; rel=preload; as=font; type=font/woff2; crossorigin',
'<'.asset('themes/fontawesome/webfonts/fa-brands-400.woff2').'>; rel=preload; as=font; type=font/woff2; crossorigin',
'<'.asset('themes/fontawesome/webfonts/fa-duotone-900.woff2').'>; rel=preload; as=font; type=font/woff2; crossorigin',
'<'.asset('themes/fontawesome/webfonts/fa-regular-400.woff2').'>; rel=preload; as=font; type=font/woff2; crossorigin',
]);
return $next($request);
}
private function frankenphp_send_early_hints(array $links): void
{
foreach ($links as $link) {
header('Link: '.$link);
if(function_exists('headers_send')){
headers_send(103);
}
}
}
}
Asıl gelmek istediğim konu ise şu, sunucu yapımı tamamen docker-compose ile çalıştıracağım bir yapıya taşımayı düşünüyordum zaten, Frankenphp ile de tanıştığımda bu yapıyı oluşturmaya başladım. Yani standart Nginx, Apache, PHP Fpm dışına çıkarak böyle bir yapı oluşturmaya başladım, bu yapıyı oluştururken de karşılaştığım bazı problemler oldu, sunucumda bazı web sitelerinin yazılımı güncel değil .htaccess
kuralları ile çalışıyor bazıları bu da Caddy tarafında desteklenmediği için sitenin anasayfası haricinde diğer sayfalar 404e düşüyordu mecbur docker compose içerisine bir de Apache eklemek zorunda kaldım.docker-compose.yaml
dosyası aşağıdaki gibi
services:
alpha_panel_web:
build:
context: ./alpha-panel/web
dockerfile: Dockerfile
container_name: alpha_panel_web
hostname: alpha_panel_web
restart: always
volumes:
- ./alpha-panel/web/httpdocs:/var/www/AlphaPanel/httpdocs
- ./alpha-panel/web/logs:/var/log/caddy
- ./alpha-panel/web/ssl:/var/www/ssl
- ./alpha-panel/web/Caddyfile:/etc/frankenphp/Caddyfile
- ./alpha-panel/web/caddy_data:/data
- ./vhosts:/var/www/vhosts
environment:
PANEL_DOMAIN: ${PANEL_DOMAIN}
CF_API_TOKEN: ${CF_API_TOKEN}
ADMIN_EMAIL: ${ADMIN_EMAIL}
ports:
- "${PRIVATE_NETWORK_IP}:7443:443"
networks:
- vhost_network
alpha_panel_webhook:
build:
context: ./alpha-panel/webhook
dockerfile: Dockerfile
container_name: alpha_panel_webhook
hostname: alpha_panel_webhook
restart: always
volumes:
- ./alpha-panel/webhook:/app
- ./vhosts:/var/www/vhosts
- ./alpha-panel/webhook/ssh_key:/root/.ssh
networks:
- vhost_network
frankenphp:
build:
context: ./frankenphp
dockerfile: Dockerfile
container_name: frankenphp
hostname: frankenphp
restart: always
environment:
CF_API_TOKEN: ${CF_API_TOKEN}
ADMIN_EMAIL: ${ADMIN_EMAIL}
PUBLIC_NETWORK_IP: ${PUBLIC_NETWORK_IP}
PRIVATE_NETWORK_IP: ${PRIVATE_NETWORK_IP}
volumes:
- ./frankenphp/Caddyfile:/etc/frankenphp/Caddyfile
- ./frankenphp/sites-enabled:/etc/frankenphp/sites-enabled
- ./frankenphp/caddy_data:/data
- ./vhosts:/var/www/vhosts
- ./php-code-server/run/:/run/php/
- ./frankenphp/logs:/var/log/caddy/
networks:
- vhost_network
depends_on:
- mysql
- redis
- mongodb
- ftp
- meilisearch
ports:
- "${PUBLIC_NETWORK_IP}:80:80"
- "${PUBLIC_NETWORK_IP}:443:443"
php-code-server:
build:
context: ./php-code-server
dockerfile: Dockerfile
container_name: php-code-server
hostname: php-code-server
restart: always
user: root
environment:
- PASSWORD=${CODE_SERVER_PASSWORD}
- SUDO_PASSWORD=${CODE_SERVER_SUDO_PASSWORD}
- PROXY_DOMAIN=${CODE_SERVER_DOMAIN}:7443
- DEFAULT_WORKSPACE=/var/www/vhosts
- TZ=Etc/UTC
- ALLOW_ROOT=true
volumes:
- ./php-code-server/run/:/run/php/
- ./vhosts:/var/www/vhosts
- ./php-code-server/supervisor.d:/etc/supervisor/conf.d/
- ./php-code-server/8.4/fpm.d/:/etc/php/8.4/fpm/pool.d/
- ./php-code-server/8.3/fpm.d:/etc/php/8.3/fpm/pool.d/
- ./php-code-server/8.2/fpm.d:/etc/php/8.2/fpm/pool.d/
- ./php-code-server/8.1/fpm.d:/etc/php/8.1/fpm/pool.d/
- ./php-code-server/8.0/fpm.d:/etc/php/8.0/fpm/pool.d/
- ./php-code-server/php.ini:/etc/php/8.4/fpm/conf.d/99999-custom.ini
- ./php-code-server/php.ini:/etc/php/8.3/fpm/conf.d/99999-custom.ini
- ./php-code-server/php.ini:/etc/php/8.2/fpm/conf.d/99999-custom.ini
- ./php-code-server/php.ini:/etc/php/8.1/fpm/conf.d/99999-custom.ini
- ./php-code-server/php.ini:/etc/php/8.0/fpm/conf.d/99999-custom.ini
- ./ftp-config/users.env:/etc/users.env:ro
- ./code-server/data:/root
- ./apache/sites-enabled:/etc/apache2/sites-enabled
- ./apache/conf-enabled/remote_ip.conf:/etc/apache2/conf-enabled/remote_ip.conf
- ./apache/conf-enabled/cloudflare.conf:/etc/apache2/conf-enabled/cloudflare.conf
- ./apache/conf-enabled/security.conf:/etc/apache2/conf-enabled/security.conf
- ./apache/conf-enabled/deflate.conf:/etc/apache2/conf-enabled/deflate.conf
- ./vhosts:/var/www/vhosts
- ./php-code-server/run/:/run/php/
depends_on:
- mysql
- mongodb
- redis
- meilisearch
- ftp
networks:
- vhost_network
meilisearch:
image: getmeili/meilisearch:v1.14
container_name: meilisearch
hostname: meilisearch
volumes:
- ./meilisearch/data:/meili_data
- ./meilisearch/tmp:/tmp
restart: always
environment:
- MEILI_ENV=production
- TMPDIR=/tmp
ports:
- "${PRIVATE_NETWORK_IP}:7700:7700"
networks:
- vhost_network
command: ["meilisearch", "--master-key", "${MEILISEARCH_MASTER_KEY}"]
mysql:
image: mysql:9.3.0
container_name: mysql
hostname: db
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf.d:/etc/mysql/conf.d
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
ports:
- "${PRIVATE_NETWORK_IP}:3306:3306"
networks:
- vhost_network
# aliases:
# - db.niyazi.org
redis:
image: redis:latest
container_name: redis
hostname: redis.niyazi.org
restart: always
volumes:
- ./redis:/data
ports:
- "${PRIVATE_NETWORK_IP}:6379:6379"
networks:
- vhost_network
mongodb:
image: mongodb/mongodb-community-server:8.0.8-ubuntu2204
container_name: mongodb
hostname: mongodb.niyazi.org
restart: always
user: root
volumes:
- ./mongodb:/data/db
ports:
- "${PRIVATE_NETWORK_IP}:27017:27017"
networks:
- vhost_network
backlink_service:
build:
context: ./vhosts/backlink.name/service
dockerfile: Dockerfile
container_name: backlink_service
hostname: backlinkdb
restart: always
volumes:
- ./vhosts/backlink.name/service:/app
networks:
- vhost_network
password:
image: "vaultwarden/server:latest"
hostname: "${VAULTWARDEN_DOMAIN}"
container_name: password
restart: always
volumes:
- "./vaultwarden/data:/data/"
environment:
- "DATABASE_URL=mysql://${VAULTWARDEN_DB_USER}:${VAULTWARDEN_DB_PASSWORD}@${VAULTWARDEN_DB_HOST}/${VAULTWARDEN_DB_NAME}"
- "RUST_BACKTRACE=1"
networks:
- vhost_network
phpmyadmin:
image: phpmyadmin:latest
container_name: phpmyadmin
hostname: pma.niyazi.org
restart: always
environment:
PMA_HOST: mysql
PMA_PORT: 3306
# PMA_USER: root
# PMA_PASSWORD: ${MYSQL_ROOT_PASSWORD}
networks:
- vhost_network
ftp:
image: delfer/alpine-ftp-server # Alpine + vsftpd, FTPS destekli
container_name: ftp-server
hostname: ftp-server
restart: always
ports:
- "${PRIVATE_NETWORK_IP}:21:21" # komut kanalı
- "${PRIVATE_NETWORK_IP}:21000-21010:21000-21010" # pasif mod port aralığı
environment:
# Dış IP veya hostname (PASV yanıtlarında kullanılır)
ADDRESS: server.niyazi.org
MIN_PORT: 21000
MAX_PORT: 21010
env_file:
- ./ftp-config/users.env
entrypoint: ["/init.sh"]
volumes:
- ./vhosts:/var/www/vhosts:rw
- ./ftp-config/users.env:/config/users.env:ro
- ./ftp-config/init.sh:/init.sh:ro
networks:
- vhost_network
networks:
vhost_network:
name: vhost_network
driver: bridge
Burada çoğunlukla ek olarak kendi kullandığım bazı servisler de mevcut, servisleri çalıştırmadan önce kullanmayacağınız servisleri buradan çıkarabilirsiniz. Örneğin alpha_panel_web
, alpha_panel_webhook
, ve backlink_service
bunları yaml dosyasından kaldırarak kullanabilirsiniz. projenin github linkini aşağıya bırakıyorum.
https://github.com/niyazialpay/AlphaWebServer
English: https://niyazi.net/en/docker-compose-server-setup-with-frankenphp-caddy-supporting-103-early-hints
Muhammed Niyazi ALPAY - Cryptograph
Senior Software Developer & Senior Linux System Administrator
Meraklı
PHP MySQL MongoDB Python Linux Cyber Security
Hiç yorum yok