Podman представляет собой альтернативу или даже замену Docker. Начиная с FreeBSD 14, его можно установить с помощью менеджера пакетов, а если что, то ему поможет подсистема эмуляции Linux. По крайней мере городить огород с виртуальными машинами не требуется. Тем не менее, пока что поддержка FreeBSD носит экспериментальный характер и предполагает лишь ознакомление и тестирование. Что мы и сделаем.
Честно говоря, я как-то пробовал запустить Докер через эмуляцию Rocky Linux, но не смог запустить containerd:
failed to start daemon: Devices cgroup isn't mounted
Возможно, конечно, не дожал, но Докер как бы вообще никоим образом не относится ко FreeBSD. А у Podman есть пакет и официальная инструкция по установке. Собственно, приступим. Для определенности я все делаю в виртуальной машине VirtualBox с формулой 2/2/16, FreeBSD у нас уже 14.3. Как обычно, всё совершенно недопустимым образом делаю под root, если явно не написал иное.
Установка
Начинаем с установки пакета:
pkg install podman
Пока не совсем понятно, потребуются ли buildah и skopeo (ну и названия, средства сборки и управления соответственно), поэтому ограничился Podman как таковым (иначе предлагается установить podman-suite).
conmon и инструкция сообщают, что должна быть смонтирована /dev/fd
– файловая система с файловыми дескрипторами fdescfs(5)
, что бы это ни значило. Команда mount ничего такого у меня не показала:
mount
/dev/ada0s1a on / (ufs, local, soft-updates, journaled soft-updates)
devfs on /dev (devfs)
Соответственно добавляем в /etc/fstab:
fdesc /dev/fd fdescfs rw 0 0
Теперь, если нет ZFS, а у меня в виртуалке как раз UFS, нужно изменить драйвер хранилища на vfs
в файле /usr/local/etc/containers/storage.conf:
driver = "vfs"
Можно заранее включить сервис:
service podman enable
Хотел было проверить работу, но увы – без настройки сети даже здороваться не хочет.
Настройка сети
Podman работает в сочетании с уже знакомым мне pf. Копируем шаблон конфигурации (предполагается, что pf до этого еще не настраивали):
cp /usr/local/etc/containers/pf.conf.sample /etc/pf.conf
# Change these to the interface(s) with the default route
v4egress_if = "ix0"
v6egress_if = "ix0"
nat on $v4egress_if inet from <cni-nat> to any -> ($v4egress_if)
nat on $v6egress_if inet6 from <cni-nat> to !ff00::/8 -> ($v6egress_if)
rdr-anchor "cni-rdr/*"
nat-anchor "cni-rdr/*"
table <cni-nat>
IPv6 в виртуалке, я думаю, можно смело убрать, а вот что касается интерфейса, то тут вопрос. У меня их два: NAT и виртуальная сеть. Казалось бы, нужен, второй, который первый (em1) – по идее входящие подключения через него идут, но не тут-то было. При сборке контейнера не сработало DNS (что тоже логично, ведь виртуальная сеть только «внутри» работает). Так что пишем em0:
v4egress_if = "em0"
nat on $v4egress_if inet from <cni-nat> to any -> ($v4egress_if)
rdr-anchor "cni-rdr/*"
nat-anchor "cni-rdr/*"
table <cni-nat>
Последние строчки отвечают за перенаправление портов. Чтобы это самое перенаправление заработало, дополнительно ставим загрузку модуля ядра в /boot/loader.conf:
pf_load="YES"
И соответствующую настройку в /etc/sysctl.conf:
net.pf.filter_local=1
Включаем сервис:
service pf enable
Я предлагаю перезагрузиться, чтобы все включилось как положено. И проверяем по аналогии с Docker, только путь к образу рекомендуется (а точнее без настройки реестров обязательно) прописывать полностью:
podman run --rm docker.io/dougrabson/hello
Вот, сейчас работает.
PHP
Вокруг да около
Давайте повысим ставки и развернем PHP-приложение. Для начала проверим, будет ли в принципе работать PHP в контейнере. Хотел было попробовать запустить контейнер под обычным пользователем, но увы:
Error: rootless mode is not supported on FreeBSD - run podman as root
Жаль, ведь этот самый rootless mode «из коробки» является отличительной чертой Podman. Что ж, отрицательный результат – тоже результат, так что продолжаем под root. Пробуем:
podman run --rm quay.io/lib/php:8.4-cli php --version
Более родным, что ли, для Podman является quay.io, поэтому образ решил взять оттуда. Первый блин комом:
Добавляем параметр --os=linux
:
podman run --rm --os=linux quay.io/lib/php:8.4-cli php --version
Чуть лучше – хотя бы пошла загрузка образа, вот только теперь у меня внезапно место на виртуалке закончилось. Очень уж, оказывается, Podman в режиме vfs к оному требователен. Отключил сервис podman и подкинул диск на 8 гигов (/dev/ada2 в моем случае). Удалил /var/db/containers, создал пул tank и файловую систему ZFS с точкой монтирования /var/db/containers и включил сервис:
rm -r /var/db/containers
zpool create tank ada2
zfs create -o mountpoint=/var/db/containers tank/containers
service zfs enable
Вернул обратно драйвер zfs в /usr/local/etc/containers/storage.conf, включил и запустил podman и попробовал запустить PHP в третий раз. В отличие от vfs/UFS, на ZFS (да еще и со сжатием по умолчанию) образ занимает считанные мегабайты. Увы, без эмуляции Linux не работает:
Уговорили.
service linux enable
service linux start
podman run --rm --os=linux quay.io/lib/php:8.4-cli php --version
Однако есть контакт!
PHP 8.4.4 (cli) (built: Feb 14 2025 02:30:31) (NTS)
Copyright (c) The PHP Group
Built by https://github.com/docker-library/php
Zend Engine v4.4.4, Copyright (c) Zend Technologies
Вот только версия PHP, оказывается, в quay.io какая-то уже древняя, так что придется пользоваться docker.io. Удаляем образ:
podman image rm quay.io/lib/php
Демо-приложение
Мое любимое (или «любимое»?..) от Symfony. Создадим директории, на сей раз больше в стиле FreeBSD:
mkdir -p /usr/local/www/demo/html
cd /usr/local/www/demo
Создадим Dockerfile простите, Containerfile:
FROM --platform=linux/amd64 docker.io/library/php:8.4-cli
# Fix E: Dynamic MMap ran out of room. Please increase the size of APT::Cache-Start. Current value: 25165824. (man 5 apt.conf)
RUN echo 'APT::Cache-Start "50331648";' >> /etc/apt/apt.conf.d/70debconf
# Install PHP extensions
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN install-php-extensions \
intl
# Install composer and unzip
RUN set -eux; \
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \
# smoke test
composer --version; \
apt-get update; \
apt-get install -y --no-install-recommends \
unzip \
; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*
# Use the default production configuration
RUN cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
WORKDIR /var/www/html
EXPOSE 8000
# Run under unprivileged user
ARG WWWUSER=80
RUN set -eux; \
useradd -d /var/www -M -s /usr/sbin/nologin -r -u $WWWUSER -c "World Wide Web Owner" www
USER www
Явная особенность в первой строке – указание платформы и полный путь к образу. cli – не ошибка, я хочу на сей раз показать встроенный PHP-шный веб-сервер, а он как раз запускается из командной строки. Также возникла внезапная проблема, с которой я никогда не сталкивался в Docker – не хватает объема кэша для команды apt-get update. В этих ваших интернетах о такой ошибке писали в начале-середине 2010-х, почему она вылезла сейчас под Podman, непонятно. Решил прямолинейно и экстенсивно: попросили увеличить начальный кэш – увеличил.
Остальное вроде все довольно обычное, в частности установка composer и дополнительных расширений PHP. Есть нюанс с пользователем. В Debian (базовой ОС контейнера) принят www-data:33:33, а во FreeBSD – www:80:80. Кому-то придется подвинутся, и это будет контейнер. Менять команду запуска на этом уровне не будем. Как минимум, нам сначала надо будет composer'ом поработать, а во-вторых, в случае чего это можно будет сделать через compose.yaml.
Который пока что совсем куцый:
services:
php:
build: .
volumes:
- ./html:/var/www/html
Запустить не получится, поскольку нужно установить podman-compose. К сожалению, в пакетах он староват, но что поделать:
pkg install py311-podman-compose
Поехали:
chown www:www html
podman-compose run --rm --build php bash
С предупреждением все понятно:
В контейнере:
cd /var/www
composer create-project symfony/symfony-demo html
exit
Composer благополучно отработал. Из немного необычного – права на файлы получились 600, а на директории 700. Дело в маске по умолчанию, я ее не стал менять, но в дальнейшем это будет слегка мешать и придется явно устанавливать права.
Linux'овый PHP не работает
На этом полоса везения закончилась. Увы: ни PHP-шный встроенный веб-сервер, ни FPM (пробовал было заменить 8.4-cli на 8.4-fpm) не заработали по причине ошибки:
Error Cannot create lock - Bad file descriptor (9)
Тоже вообще говоря очень древняя ошибка, но манипуляции с правами на /tmp в контейнере не помогли (вообще-то с ними изначально все в порядке). Как и отмена директивы USER, т.е. запуск под root. Также попробовал установить утилиту командной строки symfony и запустить сервер через нее – другая ошибка:
Web Server log file cannot be tailed: unable to watch log file: function not implemented
Сопровождающаяся такой:
syscall inotify_init1 not implemented
Похоже эмуляция Linux не вывозит. Предлагаю переименовать Containerfile в какой-нибудь setup.containerfile для истории и начать заново.
Контейнер FreeBSD
С версии 14.2 появились контейнеры FreeBSD в формате OCI. Изначально здесь – https://download.freebsd.org/releases/OCI-IMAGES/, а еще, к примеру, в хабе: https://hub.docker.com/u/freebsd. Документации примерно ноль.
Давайте на этой основе намутим контейнер, в который установим PHP через pkg. Новый Containerfile:
FROM docker.io/freebsd/freebsd-runtime:14.3
# Install software
ENV ASSUME_ALWAYS_YES=yes
RUN set -eux; \
pkg bootstrap; \
pkg install \
php84 \
php84-composer \
php84-dom \
php84-gd \
php84-iconv \
php84-opcache \
php84-pdo_sqlite \
php84-session \
php84-simplexml \
php84-tokenizer \
php84-xml \
unzip; \
pkg clean -a
# Application directory
RUN set -eux; \
[ ! -d /usr/local/www/html ]; \
mkdir -p /usr/local/www/html; \
chmod 755 /usr/local/www; \
chmod 1777 /usr/local/www/html
WORKDIR /usr/local/www/html
# Use the default production configuration
RUN set -eux; \
cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini; \
chmod 644 /usr/local/etc/php.ini
CMD ["php", "-a"]
Директиву CMD я бессовестным образом позаимствовал у php:8.4-cli, а еще надо было подправить права доступа не только к /usr/local/www/html как в Докере, но и к /usr/local/www, иначе веб-сервер не видел директорию (она создавалась с правами 700). Еще почему-то в исходном образе отсутствует chown
, что очень и очень странно.
Насчет ASSUME_ALWAYS_YES
, вместо этой переменной окружения потребовалось бы добавлять параметр -y
к каждой команде pkg. С другой стороны, она соответственно будет видна в PHP. Набор пакетов – расширений PHP, как я выразился в свое время, во FreeBSD мелко нарезан, поэтому их придется перечислить довольно много.
Правим compose.yaml, поскольку теперь уже нам нужно запустить веб-сервер, да и директория внутри контейнера поменялась:
services:
php:
build: .
restart: unless-stopped
command: "php -S 0.0.0.0:8000 -t /usr/local/www/html/public"
ports:
- "8000:8000"
user: "www"
volumes:
- ./html:/usr/local/www/html
И наконец-то запускаем:
podman-compose up -d
Надеюсь, у вас все получится (у меня сработало далеко не с первого раза). Идем проверять на IP-адрес виртуалки, например http://192.168.56.143:8000/
Самое главное теперь, чтобы контейнер запустился после перезагрузки.
reboot
И тишина… Оказывается, во FreeBSD автоматически стартуют контейнеры с правилом always
, но не unless-stopped
, как я по привычке написал. podman-compose down
, правим compose.yaml, podman-compose up -d
, reboot
.
services:
php:
build: .
restart: always
command: "php -S 0.0.0.0:8000 -t /usr/local/www/html/public"
ports:
- "8000:8000"
user: "www"
volumes:
- ./html:/usr/local/www/html
Должно заработать.
Нет DNS в сети контейнеров
В Docker, если мы определяем несколько сервисов в приложении (compose.yaml), они могут обращаться друг к другу по именам. То есть грубо говоря, если у нас есть сервис postgres, то в сервисе php мы подключаемся к БД как postgres:5432. К сожалению, CNI (текущая реализация сети контейнеров во FreeBSD) похоже так делать не умеет. Точнее даже когда-то умела (где-то в районе 3-го Podman и Linux, а сейчас уже пятый) с помощью плагина dnsname в сочетании с dnsmasq. Разумеется, толку от последнего (если его накатить) без поддержки со стороны CNI никакого.
Как проверить. Внезапно заработал линуксовый контейнер Caddy (предвосхищая вопрос – FrankenPHP не работает с той же самой ошибкой Cannot create lock - Bad file descriptor), хоть и с руганью на отсутствие /proc/self/cgroup (failed to set GOMAXPROCS). Поставим его в качестве обратного прокси для PHP:
services:
php:
build: .
restart: always
command: "php -S 0.0.0.0:8000 -t /usr/local/www/html/public"
user: "www"
volumes:
- ./html:/usr/local/www/html
web:
image: docker.io/library/caddy:2
platform: linux/amd64
restart: always
depends_on:
- php
ports:
- "80:80"
volumes:
- ./config:/etc/caddy
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
Соответствующий файл config/Caddyfile:
{
auto_https off
}
http:// {
reverse_proxy php:8000
handle_errors {
respond "{err.status_code} {err.status_text}"
}
}
Запускаем и проверяем:
podman-compose up -d
curl -v localhost
Вывод примерно такой:
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:80...
* Immediate connect fail for ::1: Connection refused
* Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 502 Bad Gateway
< Server: Caddy
< Date: Fri, 08 Aug 2025 17:50:51 GMT
< Content-Length: 15
<
* Connection #0 to host localhost left intact
502 Bad Gateway
А в логах…
podman logs demo_web_1
Обнаруживаем запись о незнании такого хоста (php):
{
"level": "error",
"ts": 1754675447.528783,
"logger": "http.log.error",
"msg": "dial tcp: lookup php on 10.0.2.3:53: no such host",
"request": {
"remote_ip": "10.89.0.1",
"remote_port": "50587",
"client_ip": "10.89.0.1",
"proto": "HTTP/1.1",
"method": "GET",
"host": "localhost",
"uri": "/",
"headers": {
"User-Agent": [
"curl/8.14.1"
],
"Accept": [
"*/*"
]
}
},
"duration": 0.033949077,
"status": 502,
"err_id": "hwwz48kbv",
"err_trace": "reverseproxy.statusError (reverseproxy.go:1390)"
}
Можно заодно проинспектировать сеть:
podman network inspect demo_default
[
{
"name": "demo_default",
"id": "ccd8f4882912ca0612621e4cbbef9844814620b8064ef9920b33d376065c4a58",
"driver": "bridge",
"network_interface": "cni-podman1",
"created": "2025-08-08T21:07:46.383424+03:00",
"subnets": [
{
"subnet": "10.89.0.0/24",
"gateway": "10.89.0.1"
}
],
"ipv6_enabled": false,
"internal": false,
"dns_enabled": false,
"labels": {
"com.docker.compose.project": "demo",
"io.podman.compose.project": "demo"
},
"ipam_options": {
"driver": "host-local"
},
"containers": {
"7a8563ca5b2a710869e002afb3eedb701b3a202d7b2eb608898f47c11a986056": {
"name": "demo_php_1",
"interfaces": {
"eth0": {
"subnets": [
{
"ipnet": "10.89.0.8/24",
"gateway": "10.89.0.1"
}
],
"mac_address": "02:74:e7:73:55:0b"
}
}
},
"94903c1425807ad4176fd91a8b9e7e4692cc078cd27b1a4ea9b60480a4ba8abb": {
"name": "demo_web_1",
"interfaces": {
"eth0": {
"subnets": [
{
"ipnet": "10.89.0.9/24",
"gateway": "10.89.0.1"
}
],
"mac_address": "02:87:73:98:db:0b"
}
}
}
}
}
]
Как видим, "dns_enabled": false
(строка 16). Тушите, как говорится, свет:
podman-compose down
Народ массово мигрирует на netavark и aardvark-dns, вот только нет таких во FreeBSD. Ждем-с…
Выводы
Пока скорее неутешительные. На эмуляцию Linux особо рассчитывать не стоит (хотя вот Caddy заработал в каком-то виде), а родных FreeBSD-шных образов, мягко говоря, немного. Есть еще некоторый скепсис в том плане, а нужен ли Podman, если есть «тюрьмы». Честно говоря, на мой взгляд jail'ы настраивать и потом обслуживать сложнее (по крайней мере врукопашную, без использования средств типа CBSD), плюс они так или иначе требуют почти полную копию FreeBSD. Образ же freebsd-runtime весьма компактный; с другой стороны, на vfs/UFS Podman сожрет столько места, что клеткам останется лишь молча завидовать в сторонке.
Отдельную головную боль доставляет старый podman-compose (версия 1.3.0 при актуальной 1.5.0). Я не стал об этом распространяться в основной статье, но работает он крайне странно. Например, нужно в любом случае вызывать podman-compose down
, даже если контейнеры не стартовали из-за ошибки или были запущены в интерактивном режиме (без -d
). Будем надеяться, с обновлениями ситуация улучшится.
Связанная с этим проблема обнаружения сервисов тоже оптимизма не добавляет. А если свернуть с пути и запихать все в один контейнер, то тогда отличия от jail почти окончательно теряются.
Тем не менее, кое-какие идеи по поводу Podman'а во FreeBSD у меня еще есть, может быть что-нибудь скомпилируем в контейнере или установим нечто совсем странное. Не переключайтесь!
Категория: Программирование, веб | Опубликовано 09.08.2025
Похожие материалы
Компиляция FrankenPHP во FreeBSD. Контейнер Podman
Воодушевленный созданием контейнера PHP для FreeBSD, я поставил перед собой более амбициозную цель: скомпилировать FrankenPHP. Напомню, что это веб-сервер на базе Caddy, который взаимодействует с PHP как с библиотекой и помимо этого предоставляет еще ряд различных оптимизаций. В целом все получилось, но, как говорится, есть нюансы.
Образ PHP для Podman во FreeBSD
При знакомстве с комбинацией Podman + FreeBSD я набросал Containerfile для PHP. Сейчас же я решил довести дело до ума и сделать образ, максимально приближенный к официальному в варианте FPM. Вынужден сразу предупредить, что интерпретатор, как и до этого, будет установлен с помощью пакетного менеджера, в результате невозможно будет гарантировать его точную версию.
Веб-сервер на FreeBSD с использованием клеток
Здесь вам не Докер, а клетки (jails) - будем говорить, это контейнеры FreeBSD, когда это еще не было мейнстримом (на минуточку, они появились еще во FreeBSD 4.x - 2000 год). Практический смысл в моем случае - неким образом изолированно использовать разные версии PHP, ну и чуть ближе познакомиться с технологией, с которой я уже сталкивался при обзоре TrueNAS. Основано, как говорится, на реальных событиях - я переносил сайты на Drupal 7.x и Yii с сервера на Linux.
FreeBSD на VPS
Продолжаю устанавливать что-нибудь этакое на VPS. На сей раз решил, так сказать, вернуться к истокам - ведь когда-то многие веб-сервера были на фряхе, а также посмотреть, насколько она компактна сама по себе и в плане ресурсоемкости.