---
title: "Создание кастомных Docker-образов"
sidebar:
  label: "Docker-образы"
  order: 0
seo:
  description: "Docker-образы FrankenPHP основаны на официальных PHP-образах. Доступны варианты для Debian и Alpine Linux для популярных архитектур. Рекомендуется…"
---
[Docker-образы FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) основаны на [официальных PHP-образах](https://hub.docker.com/_/php/).
Доступны варианты для Debian и Alpine Linux для популярных архитектур.
Рекомендуется использовать Debian-варианты.

Доступны версии для PHP 8.2, 8.3, 8.4 и 8.5.

Теги следуют следующему шаблону: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`.

- `<frankenphp-version>` и `<php-version>` — версии FrankenPHP и PHP соответственно, начиная с мажорных (например, `1`), минорных (например, `1.2`) и заканчивая патч-версиями (например, `1.2.3`).
- `<os>` может быть `trixie` (для Debian Trixie), `bookworm` (для Debian Bookworm) или `alpine` (для последней стабильной версии Alpine).

[Просмотреть доступные теги](https://hub.docker.com/r/dunglas/frankenphp/tags).

## Как использовать образы

Создайте `Dockerfile` в вашем проекте:

```dockerfile
FROM dunglas/frankenphp

COPY . /app/public
```

Затем выполните следующие команды для сборки и запуска Docker-образа:

```console
docker build -t my-php-app .
docker run -it --rm --name my-running-app my-php-app
```

## Как настроить конфигурацию

Для удобства в образе предоставлен [Caddyfile по умолчанию](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile), содержащий полезные переменные окружения.

## Как установить дополнительные PHP-расширения

Скрипт [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) предоставляется в базовом образе. Установка дополнительных PHP-расширений осуществляется просто:

```dockerfile
FROM dunglas/frankenphp

# Добавьте дополнительные расширения здесь:
RUN install-php-extensions \
	pdo_mysql \
	gd \
	intl \
	zip \
	opcache
```

## Как установить дополнительные модули Caddy

FrankenPHP построен на базе Caddy, и все [модули Caddy](https://caddyserver.com/docs/modules/) можно использовать с FrankenPHP.

Самый простой способ установить пользовательские модули Caddy — использовать [xcaddy](https://github.com/caddyserver/xcaddy):

```dockerfile
FROM dunglas/frankenphp:builder AS builder

# Копируем xcaddy в образ сборки
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy

# Для сборки FrankenPHP необходимо включить CGO
RUN CGO_ENABLED=1 \
    XCADDY_SETCAP=1 \
    XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
    CGO_CFLAGS=$(php-config --includes) \
    CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
    xcaddy build \
        --output /usr/local/bin/frankenphp \
        --with github.com/dunglas/frankenphp=./ \
        --with github.com/dunglas/frankenphp/caddy=./caddy/ \
        --with github.com/dunglas/caddy-cbrotli \
        # Mercure и Vulcain включены в официальный билд, но вы можете их удалить
        --with github.com/dunglas/mercure/caddy \
        --with github.com/dunglas/vulcain/caddy
        # Добавьте дополнительные модули Caddy здесь

FROM dunglas/frankenphp AS runner

# Заменяем официальный бинарный файл на пользовательский с добавленными модулями
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
```

Образ `builder`, предоставляемый FrankenPHP, содержит скомпилированную версию `libphp`.
[Образы builder](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) доступны для всех версий FrankenPHP и PHP, как для Debian, так и для Alpine.

<div class="fp-callout" data-callout="tip">
<p class="fp-callout-title">Совет</p>

Если вы используете Alpine Linux и Symfony,
возможно, потребуется [увеличить размер стека](/ru/docs/compile#использование-xcaddy).

</div>

## Активировать worker режим по умолчанию

Установите переменную окружения `FRANKENPHP_CONFIG`, чтобы запускать FrankenPHP с рабочим скриптом:

```dockerfile
FROM dunglas/frankenphp

# ...

ENV FRANKENPHP_CONFIG="worker ./public/index.php"
```

## Использование тома в разработке

Для удобной разработки с FrankenPHP смонтируйте директорию с исходным кодом приложения на хосте как том в Docker-контейнере:

```console
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
```

<div class="fp-callout" data-callout="tip">
<p class="fp-callout-title">Совет</p>

Опция `--tty` позволяет получать удобочитаемые логи вместо JSON-формата.

</div>

С использованием Docker Compose:

```yaml
# compose.yaml

services:
  php:
    image: dunglas/frankenphp
    # раскомментируйте следующую строку, если хотите использовать собственный Dockerfile
    #build: .
    # раскомментируйте следующую строку для работы в production-окружении
    # restart: always
    ports:
      - "80:80" # HTTP
      - "443:443" # HTTPS
      - "443:443/udp" # HTTP/3
    volumes:
      - ./:/app/public
      - caddy_data:/data
      - caddy_config:/config
    # закомментируйте следующую строку в production — в dev она обеспечивает удобочитаемые логи
    tty: true

# Тома, необходимые для сертификатов и конфигурации Caddy
volumes:
  caddy_data:
  caddy_config:
```

## Запуск под обычным пользователем

FrankenPHP может запускаться под обычным пользователем в Docker.

Пример `Dockerfile` для этого:

```dockerfile
FROM dunglas/frankenphp

ARG USER=appuser

RUN \
	# Для дистрибутивов на основе Alpine используйте "adduser -D ${USER}"
	useradd ${USER}; \
	# Добавить возможность привязки к портам 80 и 443
	setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
	# Предоставить доступ на запись в /config/caddy и /data/caddy
	chown -R ${USER}:${USER} /config/caddy /data/caddy

USER ${USER}
```

### Запуск без дополнительных прав

Даже при запуске без root-прав, FrankenPHP требуется возможность `CAP_NET_BIND_SERVICE` для привязки веб-сервера к зарезервированным портам (80 и 443).

Если вы открываете доступ к FrankenPHP на непривилегированном порту (1024 и выше), можно запустить веб-сервер от имени обычного пользователя без необходимости предоставления дополнительных возможностей:

```dockerfile
FROM dunglas/frankenphp

ARG USER=appuser

RUN \
	# Для дистрибутивов на основе Alpine используйте "adduser -D ${USER}"
	useradd ${USER}; \
	# Удалить стандартные возможности
	setcap -r /usr/local/bin/frankenphp; \
	# Предоставить доступ на запись в /config/caddy и /data/caddy
	chown -R ${USER}:${USER} /config/caddy /data/caddy

USER ${USER}
```

Затем установите переменную окружения `SERVER_NAME`, чтобы использовать непривилегированный порт.
Пример: `:8000`.

## Обновления

Docker-образы собираются:

- при выпуске новой версии;
- ежедневно в 4 утра UTC, если доступны новые версии официальных PHP-образов.

## Укрепление образов

Чтобы ещё больше уменьшить поверхность атаки и размер ваших Docker-образов FrankenPHP, также возможно создавать их на основе [Google Distroless](https://github.com/GoogleContainerTools/distroless) или [Docker Hardened](https://www.docker.com/products/hardened-images) образов.

<div class="fp-callout" data-callout="warning">
<p class="fp-callout-title">Предупреждение</p>

Эти минимальные базовые образы не включают оболочку или менеджер пакетов, что значительно усложняет отладку.
Поэтому они рекомендуются только для продакшена, если безопасность является высоким приоритетом.

</div>

При добавлении дополнительных PHP-расширений вам потребуется промежуточная стадия сборки:

```dockerfile
FROM dunglas/frankenphp AS builder

# Добавьте дополнительные PHP-расширения здесь
RUN install-php-extensions pdo_mysql pdo_pgsql #...

# Скопировать общие библиотеки frankenphp и всех установленных расширений во временное место
# Этот шаг можно также выполнить вручную, проанализировав вывод ldd для бинарного файла frankenphp и каждого файла расширения .so
RUN apt-get update && apt-get install -y libtree && \
    EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
    FRANKENPHP_BIN="$(which frankenphp)"; \
    LIBS_TMP_DIR="/tmp/libs"; \
    mkdir -p "$LIBS_TMP_DIR"; \
    for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
        libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
            [ -z "$lib" ] && continue; \
            base=$(basename "$lib"); \
            destfile="$LIBS_TMP_DIR/$base"; \
            if [ ! -f "$destfile" ]; then \
                cp "$lib" "$destfile"; \
            fi; \
        done; \
    done


# Базовый образ Distroless Debian — убедитесь, что версия Debian совпадает с базовым образом
FROM gcr.io/distroless/base-debian13
# Альтернатива: Docker Hardened Image
# FROM dhi.io/debian:13

# Путь к приложению и Caddyfile для копирования в контейнер
ARG PATH_TO_APP="."
ARG PATH_TO_CADDYFILE="./Caddyfile"

# Скопировать приложение в /app
# Для дополнительного усиления убедитесь, что только записываемые пути принадлежат пользователю nonroot
COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile

# Скопировать frankenphp и необходимые библиотеки
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=builder /tmp/libs /usr/lib

# Скопировать конфигурационные файлы php.ini
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini

# Директории данных Caddy — должны быть доступны для записи пользователю nonroot даже в файловой системе только для чтения
ENV XDG_CONFIG_HOME=/config \
    XDG_DATA_HOME=/data
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy

USER nonroot

WORKDIR /app

# Точка входа для запуска frankenphp с указанным Caddyfile
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]
```

## Версии для разработки

Версии для разработки доступны в Docker-репозитории [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev).
Сборка запускается автоматически при каждом коммите в основную ветку GitHub-репозитория.

Теги `latest*` указывают на голову ветки `main`.
Также доступны теги в формате `sha-<git-commit-hash>`.
