Один бінарник, щоб правити всіма: Laravel як один виконуваний файл

AxVi
7 хв читання
8 переглядів
Один бінарник, щоб правити всіма: Laravel як один виконуваний файл
Глибоке занурення у деплой сучасного блогу розробника на Laravel 12 з використанням статичного бінарника FrankenPHP, багатоетапних збірок Docker, реверс-проксі Traefik з автоматичним SSL та CI/CD пайплайну на GitHub Actions. Практичний гайд зі створення ідеального PHP-деплою без залежностей.

Навіщо робити блог розробника у 2026 році?

В епоху соціальних мереж та контент-платформ створення власного блогу розробника може здатися надмірністю. Але є щось унікально цінне у тому, щоб мати свій куточок інтернету — місце, де ти контролюєш стек, дизайн і контент. Жодних алгоритмів, що вирішують, хто бачить твої пости, жодного ризику платформи, і найкраще — сам блог стає частиною портфоліо.

Це історія того, як я створив і задеплоїв axvi.dev — двомовний блог розробника на сучасному PHP-стеку. Це не просто черговий туторіал "як задеплоїти Laravel". Це гайд з досягнення того, що раніше було неможливим у світі PHP — пакування всього додатку в один самодостатній бінарник без жодних зовнішніх залежностей.

Технічний стек

Перш ніж зануритися в деплой, подивимося, що ми деплоїмо:

  • Laravel 12 — найновіша версія PHP-фреймворку
  • Livewire 4 / Volt — реактивні UI-компоненти без написання JavaScript
  • Filament 5 — красива адмін-панель для управління контентом
  • FrankenPHP через Laravel Octane — сучасний PHP-сервер, побудований на Caddy
  • PostgreSQL — база даних
  • Redis — кешування, сесії та черги
  • DaisyUI / Tailwind CSS — стилізація фронтенду з темами dracula та light
  • Spatie Translatable — мультимовний контент (англійська + українська)
  • Spatie Media Library — управління зображеннями
  • honeystone/laravel-seo — SEO-метадані
  • laravel-actions — бізнес-логіка, організована як класи дій

Чому FrankenPHP?

FrankenPHP — це сучасний PHP-сервер, написаний на Go, побудований поверх Caddy. На відміну від традиційних налаштувань PHP-FPM, FrankenPHP:

  • Тримає Laravel-додаток у пам'яті (через Octane), усуваючи вартість завантаження при кожному запиті
  • Працює одночасно як веб-сервер і PHP-рантайм — не потрібна комбінація nginx/Apache + PHP-FPM
  • Підтримує HTTP/3, early hints та автоматичний HTTPS з коробки
  • Може бути скомпільований у один статичний бінарник, що містить PHP, усі розширення та Caddy

Останній пункт і робить FrankenPHP справді революційним. Уявіть, що ви відправляєте весь PHP-додаток — фреймворк, залежності, веб-сервер і сам PHP — як один виконуваний файл. Жодного apt install php, жодного управління розширеннями, жодних конфліктів версій. Просто один бінарник, що працює будь-де.

Мультимовна архітектура

Блог підтримує англійську та українську мови через Spatie Translatable. Кожен елемент контенту — пости, категорії, теги — зберігає переклади як JSON у базі даних. Маршрути мають префікс локалі: /{locale}/blog, /{locale}/blog/{slug}.

Адмін-панель Filament робить управління перекладами безшовним — кожне перекладне поле має перемикач мови, тож можна писати контент обома мовами з однієї форми.

Налаштування сервера

Вибір інфраструктури

Я обрав Hetzner Cloud для хостингу. Для блогу розробника навіть їхній найменший інстанс — більш ніж достатньо:

  • CX23: 2 vCPU, 4 ГБ RAM, 40 ГБ SSD — близько $3.49/місяць
  • Локація: Нюрнберг, Німеччина — відмінна зв'язність по всій Європі
  • ОС: Ubuntu 24.04 LTS

Початкова конфігурація сервера

Після створення сервера перші кроки завжди однакові:

1. Створення не-root користувача:

adduser deploy
usermod -aG sudo deploy
cp -r /root/.ssh /home/deploy/.ssh
chown -R deploy:deploy /home/deploy/.ssh

2. Налаштування файрволу:

sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable

3. Встановлення Docker:

sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
    -o /etc/apt/keyrings/docker.asc
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] \
    https://download.docker.com/linux/ubuntu noble stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker deploy

Просто, перевірено часом, нудно. Саме те, що потрібно для інфраструктури.

Архітектура Docker

Ось тут стає цікаво. Dockerfile використовує багатоетапну збірку з чотирма стейджами, кожен з яких відповідає за конкретну частину пайплайну збірки:

Стейдж 1: PHP-залежності

FROM php:8.5-cli-alpine AS composer-builder

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

WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --ignore-platform-reqs --no-dev -a --no-scripts
COPY . .
RUN composer dump-autoload --optimize

Окремий стейдж для Composer-залежностей забезпечує чисту, продакшн-only директорію vendor з оптимізованим автолоадером. Цей стейдж йде першим, бо збірка фронтенду залежить від нього.

Стейдж 2: Збірка фронтенду

FROM node:22-alpine AS frontend-builder

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
COPY --from=composer-builder /app/vendor vendor
RUN npm run build

Цей стейдж компілює Tailwind CSS та JavaScript-ассети через Vite. Зверніть увагу на COPY --from=composer-builder — Tailwind CSS v4 з Vite-плагіном резолвить CSS-імпорти з директорії vendor/ (наприклад, теми підсвітки синтаксису з Composer-пакетів), тому vendor має бути доступним під час збірки фронтенду.

Стейдж 3: Збірка статичного бінарника

Тут відбувається магія:

FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu AS static-builder

WORKDIR /go/src/app/dist/app
COPY --from=composer-builder /app .
COPY --from=frontend-builder /app/public/build public/build

WORKDIR /go/src/app/
RUN BUILDROOT=/go/src/app/dist/static-php-cli/buildroot \
    && cd dist/app && tar cf /go/src/app/app.tar . && cd /go/src/app \
    && sha256sum app.tar | cut -d' ' -f1 > app_checksum.txt \
    && export CGO_ENABLED=1 \
    && export CGO_CFLAGS="$($BUILDROOT/bin/php-config --includes) -I$BUILDROOT/include" \
    && ALL_LIBS=$(find $BUILDROOT/lib -name '*.a' -exec basename {} .a \; \
       | sed 's/^lib/-l/' | tr '\n' ' ') \
    && export CGO_LDFLAGS="-L$BUILDROOT/lib \
       -Wl,--start-group -lphp $ALL_LIBS -Wl,--end-group \
       -lpthread -lm -ldl -lrt -lresolv -lutil -lstdc++" \
    && export PATH="$BUILDROOT/../pkgroot/x86_64-linux/go-xcaddy/bin:$PATH" \
    && XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'" \
       xcaddy build --output dist/frankenphp-linux-x86_64 \
       --with github.com/dunglas/frankenphp=/go/src/app \
       --with github.com/dunglas/frankenphp/caddy=/go/src/app/caddy \
       --with github.com/dunglas/caddy-cbrotli

Зверніть увагу — ми додаємо лише caddy-cbrotli (Brotli-стиснення) як додатковий модуль Caddy. Стандартні приклади FrankenPHP також включають Mercure (real-time push) та Vulcain (HTTP/2 preload), але якщо ваш додаток їх не використовує, вони лише збільшують розмір бінарника та спричиняють несподівані попапи дозволів у браузері. Тримайте бінарник легким — додавайте модулі тільки коли вони дійсно потрібні.

Ключовий інсайт: образ static-builder-gnu вже містить PHP та всі 60+ розширень, попередньо скомпільованих як libphp.a у buildroot. Стандартний підхід через build-static.sh ігнорує це й перекомпілює все з нуля (~20 хвилин). Замість цього ми викликаємо xcaddy напряму, лінкуючи з попередньо скомпільованими бібліотеками — це скорочує збірку до ~1-2 хвилин.

Флаги лінковщика --start-group/--end-group вирішують циклічні залежності між статичними бібліотеками, а ми динамічно збираємо всі .a файли з buildroot, щоб нічого не пропустити.

Результат? Один виконуваний файл frankenphp-linux-x86_64 (~240 МБ), що містить:

  • Веб-сервер Caddy
  • PHP 8.5 інтерпретатор з усіма 60+ розширеннями
  • Весь код Laravel-додатку та vendor-залежності
  • Фронтенд-ассети

Фінальний продакшн Docker-образ важить ~336 МБ. Для порівняння:

ПідхідОрієнтовний розмірПримітки
nginx + PHP-FPM~700-800 МБphp:8.5-fpm (516 МБ) + nginx:alpine (62 МБ) + розширення + код додатку + vendor
FrankenPHP динамічний~400-450 МБdunglas/frankenphp:php8.5-alpine (203 МБ) + розширення (pdo_pgsql, redis, gd, intl...) + код додатку + vendor
FrankenPHP статичний (наш)~336 МБВсе включено: PHP, Caddy, 60+ розширень, код додатку, фронтенд-ассети — нічого не потрібно встановлювати в рантаймі

Підхід зі статичним бінарником не лише найлегший — він ще й найпередбачуваніший. Класична зв'язка nginx + PHP-FPM починається з 578 МБ лише базових образів і росте з кожним docker-php-ext-install. Динамічний образ FrankenPHP позбавляє від nginx, але все ще потребує встановлення розширень та управління залежностями. У нашому статичному образі все вже вбудовано — нічого не потрібно встановлювати, налаштовувати чи завантажувати в рантаймі.

Стейдж 4: Продакшн-образ

FROM debian:bookworm-slim AS static-prod

RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/* \
    && groupadd -r app && useradd -r -g app -d /app app

WORKDIR /app

COPY --from=static-builder /go/src/app/dist/frankenphp-linux-x86_64 \
    /usr/local/bin/frankenphp

RUN mkdir -p storage/logs storage/framework/cache/data \
    storage/framework/sessions storage/framework/views \
    storage/app/public bootstrap/cache \
    /data/caddy /config/caddy \
    && chown -R app:app storage bootstrap/cache /data /config

USER app

EXPOSE 80

CMD ["frankenphp", "php-cli", "artisan", "octane:frankenphp", \
     "--host=0.0.0.0", "--port=80"]

Фінальний образ мінімальний: slim-базова ОС + один бінарник + записувані директорії для кешу та сесій Laravel. Зверніть увагу на директорії /data/caddy та /config/caddy — Caddy потребує їх для внутрішнього сховища та автозбереження конфігурації, і оскільки ми запускаємо процес від не-root користувача app, їх потрібно створити й призначити заздалегідь. Виділений не-root користувач app запускає процес. Жодної інсталяції PHP, Composer чи Node.js. Площа атаки мінімальна, і ламатися просто нічому.

Чому debian:bookworm-slim, а не Alpine? Наш статичний бінарник скомпільований за допомогою static-builder-gnu, який використовує glibc. Хоча бінарник статично злінкований, деякі системні виклики (DNS-резолюція через getaddrinfo, NSS-запити) можуть залежати від glibc під час виконання. Alpine використовує musl libc, що може спричинити неочевидні проблеми з DNS та локалями. bookworm-slim гарантує сумісність ціною ~80 МБ — прийнятний компроміс для надійності в production.

Збірка статичного бінарника: глибоке занурення

Збірка статичного PHP-бінарника — це подорож, і PHP 8.5 робить її ще захоплюючішою. Ось ключові інсайти, які я здобув.

Пастка build-static.sh

Офіційна документація FrankenPHP рекомендує використовувати build-static.sh зі змінними PHP_EXTENSIONS та PHP_EXTENSION_LIBS. Те, чого вона не повідомляє — образ static-builder-gnu вже містить усі 60+ розширень, попередньо скомпільованих як статичні бібліотеки в buildroot.

Можете перевірити самі:

docker run --rm dunglas/frankenphp:static-builder-gnu \
    cat /go/src/app/dist/static-php-cli/buildroot/build-extensions.json

Проблема? build-static.sh викликає spc build, який завжди перекомпілює PHP з нуля незалежно від наявних артефактів buildroot. У static-php-cli немає підтримки інкрементальних збірок. Це означає ~20 хвилин, витрачених на кожну збірку на перекомпіляцію того, що вже є.

Рішення: пряма збірка через xcaddy

Замість build-static.sh ми викликаємо xcaddy напряму — інструмент збірки на Go, що створює бінарник Caddy з модулем FrankenPHP. Попередньо скомпільований libphp.a та всі бібліотеки розширень вже є в buildroot, тому потрібно лише:

  1. Запакувати додаток в app.tar (механізм //go:embed у Go)
  2. Встановити CGO-флаги з існуючого php-config
  3. Злінкувати всі статичні бібліотеки через --start-group/--end-group

Результат: ~1-2 хвилини замість ~20 хвилин. Покращення у 10 разів.

Підводні камені статичної лінковки

При лінковці з попередньо скомпільованими статичними бібліотеками зверніть увагу на:

  • Відсутні бібліотеки у php-config --libs — згенеровані флаги можуть не включати всі бібліотеки з buildroot (наприклад, libhashkit, потрібний для libmemcached). Безпечний підхід — динамічно зібрати всі .a файли з buildroot.

  • Циклічні залежності — статичні бібліотеки посилаються одна на одну складним чином. Лінковщик обробляє бібліотеки зліва направо і може пропустити символи. Обертання всього у -Wl,--start-group ... -Wl,--end-group змушує лінковщик резолвити всі символи незалежно від порядку.

  • Build constraints — Go-збірка потребує CGO_ENABLED=1, щоб тег unix був активним, інакше внутрішні пакети FrankenPHP не скомпілюються.

Поради для статичних збірок

  1. Пропускайте build-static.sh — викликайте xcaddy напряму з попередньо скомпільованими бібліотеками. Образ static-builder-gnu вже має все скомпільоване — не витрачайте хвилини CI на перекомпіляцію.

  2. Використовуйте кеш GitHub Actions — директива cache-from: type=gha у docker/build-push-action кешує проміжні шари, тож незмінені стейджі не перебудовуються.

  3. GITHUB_TOKEN не потрібен — при прямій збірці через xcaddy Go-модулі завантажуються з Go proxy (не з GitHub API), тож обмеження запитів не є проблемою.

  4. Додайте public/hot до .dockerignore — коли запускаєте npm run dev локально, Vite створює файл public/hot з адресою http://localhost:5173. Якщо цей файл потрапить у статичний бінарник через COPY . ., Laravel Vite helper вважатиме, що dev-сервер запущений у production, і завантажуватиме ассети з localhost замість скомпільованого build. Це спричиняє зламані стилі/скрипти та попап Chrome "Access other apps and services". Завжди виключайте його з контексту Docker-збірки.

  5. Створіть обгортку php та задайте PHP_BINARY — статичний бінарник має лише виконуваний файл frankenphp, але планувальник Laravel, обробник черги та Symfony PhpExecutableFinder очікують бінарник php. Без нього заплановані команди падають з помилкою 'php': No such file or directory. Додайте це у ваш Dockerfile:

RUN printf '#!/bin/sh\nexec frankenphp php-cli "$@"\n' > /usr/local/bin/php \
    && chmod +x /usr/local/bin/php
ENV PHP_BINARY=/usr/local/bin/php

Змінна PHP_BINARY критично важлива — у вбудованій файловій системі PHP повідомляє PHP_BINARY як шлях до скрипту artisan замість справжнього виконуваного файлу PHP. Без неї планувальник не зможе коректно запускати команди. З такою конфігурацією ви також зможете використовувати звичні команди на кшталт docker exec app php artisan migrate замість багатослівного frankenphp php-cli artisan migrate.

Продакшн Docker Compose

Продакшн-налаштування використовує шість сервісів, оркестрованих Docker Compose:

services:
  traefik:
    image: traefik:v3
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
    ports:
      - "80:80"
      - "443:443"

  app:
    image: ghcr.io/your-org/your-app:latest
    healthcheck:
      test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/80'"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`${APP_DOMAIN}`)"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"
      - "traefik.http.routers.app-www.rule=Host(`www.${APP_DOMAIN}`)"
      - "traefik.http.routers.app-www.middlewares=www-redirect"
      - "traefik.http.middlewares.www-redirect.redirectregex.permanent=true"

  worker:
    image: ghcr.io/your-org/your-app:latest
    command: ["frankenphp", "php-cli", "artisan", "queue:work"]
    healthcheck:
      test: ["CMD-SHELL", "php artisan queue:monitor redis:default,email --max=100"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  scheduler:
    image: ghcr.io/your-org/your-app:latest
    command: ["frankenphp", "php-cli", "artisan", "schedule:work"]
    healthcheck:
      test: ["CMD-SHELL", "php artisan schedule:list --no-interaction > /dev/null"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  db:
    image: postgres:17

  redis:
    image: redis:alpine

Traefik як реверс-проксі

Traefik — невоспітаний герой цього налаштування. Він автоматично:

  • Отримує та оновлює SSL-сертифікати Let's Encrypt через HTTP challenge
  • Перенаправляє HTTP на HTTPS
  • Перенаправляє www.your-domain.com на your-domain.com
  • Маршрутизує трафік до правильного контейнера
  • Забезпечує перевірку здоров'я

Все це без жодних конфігураційних файлів — все оголошується через Docker-лейбли. Більше ніякого ручного управління SSL-сертифікатами чи написання nginx-конфігів.

Health Checks без curl

Оскільки наш мінімальний продакшн-образ не містить curl чи wget, healthcheck для app використовує вбудований у Bash механізм /dev/tcp для перевірки відкритого порту. Це дозволяє уникнути встановлення додаткових пакетів лише заради health checking — образ залишається легким, а площа атаки — мінімальною.

Статичний бінарник у дії

Зі статичним бінарником запуск Artisan-команд виглядає трохи інакше:

# Замість: php artisan migrate
frankenphp php-cli artisan migrate

# Всередині Docker:
docker compose exec app frankenphp php-cli artisan tinker

Префікс frankenphp php-cli замінює php — бінарник містить PHP-інтерпретатор, тому окрема інсталяція PHP не потрібна. Один бінарник керує всім.

CI/CD пайплайн

GitHub Actions воркфлоу обробляє все автоматично при пуші в master:

jobs:
  build:
    steps:
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          target: static-prod
          push: true
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          script: |
            cd /opt/axvi
            COMPOSE="docker compose -f docker-compose.prod.yml"
            ARTISAN="$COMPOSE exec -T app frankenphp php-cli artisan"

            $COMPOSE pull
            $COMPOSE up -d

            $ARTISAN storage:link --force
            $ARTISAN migrate --force
            $ARTISAN db:seed --force
            $ARTISAN optimize:clear
            $ARTISAN optimize

            docker image prune -f

Пуш у master → збірка статичного бінарника → пуш Docker-образу в GHCR → SSH на сервер → pull і рестарт. Повністю автоматизований деплой без простою. З підходом прямого xcaddy весь пайплайн збірки завершується менш ніж за 3 хвилини.

Зверніть увагу на крок storage:link --force — оскільки статичний бінарник вбудовує файли додатку, симлінк public/storage не зберігається між перезбірками контейнера. Ця команда перестворює симлінк, що вказує на постійний Docker volume, забезпечуючи доступність завантажених медіа-файлів (наприклад, обкладинок постів, які керуються Spatie Media Library).

Оптимізації продуктивності

OPcache + JIT

Продакшн php.ini вмикає OPcache з JIT-компіляцією:

opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0
opcache.jit = 1255
opcache.jit_buffer_size = 128M

У поєднанні з Octane, що тримає додаток у пам'яті, це забезпечує час відповіді, що конкурує з Go та Node.js додатками.

Laravel Octane

FrankenPHP з Octane тримає Laravel-додаток завантаженим у пам'яті. Перший запит завантажує фреймворк, а наступні пропускають весь процес завантаження. Лише це може зменшити час відповіді на 50-70% порівняно з традиційним PHP-FPM.

Статичний бінарник робить це ще кращим — оскільки сам PHP скомпільований з усіма розширеннями, зв'язаними статично, немає накладних витрат на завантаження динамічних бібліотек. Все знаходиться в одному memory-mapped бінарнику.

Бенчмарки в реальних умовах

Щоб побачити, як це все працює на практиці, я запустив навантажувальні тести з локальної машини (Україна) на продакшн-сервер (Hetzner, Нюрнберг, Німеччина) за допомогою hey. Ці цифри включають повний мережевий round-trip (~3000 км), DNS-резолюцію та TLS-хендшейк — це те, що відчувають реальні користувачі, а не синтетичні localhost-бенчмарки.

Головна сторінка (/en) — 1000 запитів, 50 одночасних:

МетрикаЗначення
Запитів/сек35.5
Середня затримка1.4с
P50 затримка1.3с
P99 затримка2.0с
Успішність100%

Сторінка блог-посту (/en/blog/...) — 500 запитів, 25 одночасних:

МетрикаЗначення
Запитів/сек20.8
Середня затримка1.2с
P50 затримка1.2с
P99 затримка2.0с
Успішність100%

Стрес-тест — шукаємо межі на сервері за $3.49/місяць (2 vCPU, 4 ГБ RAM):

Одночасних з'єднаньУспішністьRPSСередня затримкаP99 затримка
50100%351.4с2.0с
100100%302.9с4.5с
15090%333.8с5.6с
20080%315.1с7.7с

Сервер впевнено тримає 100 одночасних з'єднань без жодної помилки та підтримує ~30-35 запитів/секунду. Втрати починаються лише при 150+ одночасних з'єднаннях. Цифри затримки включають ~500мс мережевих накладних витрат (DNS, TLS, передача даних через Європу) — фактичний час відповіді сервера становить близько 500-600мс.

Для блогу розробника на найдешевшому інстансі Hetzner — це серйозний запас. Потрібні тисячі одночасних читачів, щоб це налаштування хоча б напружилось.

Чому статичний бінарник того вартий

Статичний бінарник FrankenPHP — це зміна парадигми у PHP-деплої:

  1. Нуль залежностей рантайму — жодної інсталяції PHP для управління, жодних конфліктів розширень, жодних розбіжностей версій між середовищами
  2. Іммутабельні деплої — той самий бінарник працює в CI, staging і production. Що тестуєш — те й відправляєш
  3. Мінімальна площа атаки — жодного пакетного менеджера, жодних shell-утиліт, жодних зайвих системних бібліотек. Тільки ваш додаток
  4. Самодостатність — бінарник включає PHP, Caddy та весь ваш додаток. Скопіюйте його на будь-яку Linux-машину — і він працює
  5. HTTP/3 + QUIC з коробки — з скомпільованими ngtcp2 та nghttp3 ваш додаток нативно підтримує найновіші протоколи

З підходом прямого xcaddy збірка така ж швидка, як стандартний Docker-образ — менше 3 хвилин загалом. Ваше продакшн-середовище отримує куленепробивний, мінімальний артефакт деплою, якого неможливо досягти з традиційними PHP-налаштуваннями.

Висновок

Деплоїти Laravel-блог у 2026 році зі статичним бінарником FrankenPHP — це майбутнє PHP-деплою. Поєднання статичних збірок FrankenPHP, автоматичного SSL від Traefik та CI/CD через GitHub Actions створює пайплайн деплою, який є одночасно потужним та елегантним.

Блог працює на axvi.dev. Весь стек крутиться на сервері Hetzner за $3.49/місяць, легко обробляє трафік і деплоїться автоматично при кожному git push. Один бінарник, один сервер, нуль компромісів.


Маєте питання про це налаштування? Знайдіть мене на GitHub, LinkedIn або X.

Поділитися статтею