Podman представляет собой альтернативу или даже замену Docker. Начиная с FreeBSD 14, его можно установить с помощью менеджера пакетов, а если что, то ему поможет подсистема эмуляции Linux. По крайней мере городить огород с виртуальными машинами не требуется. Тем не менее, пока что поддержка FreeBSD носит экспериментальный характер и предполагает лишь ознакомление и тестирование. Что мы и сделаем.

Честно говоря, я как-то пробовал запустить Докер через эмуляцию Rocky Linux, но не смог запустить containerd:

Failed to parse cgroup information: open /proc/self/cgroup: no such file or directory
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, поэтому образ решил взять оттуда. Первый блин комом:

Error: internal error: unable to copy from source docker://quay.io/lib/php:8.4-cli: choosing an image from manifest list docker://quay.io/lib/php:8.4-cli: no image found in image index for architecture "amd64", variant "", OS "freebsd"

Добавляем параметр --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 не работает:

ocijail: error executing container command: Exec format error

Уговорили.

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

С предупреждением все понятно:

WARNING: image platform (linux/amd64) does not match the expected platform (freebsd/amd64)

В контейнере:

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/

symfony.png

Самое главное теперь, чтобы контейнер запустился после перезагрузки.

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 у меня еще есть, может быть что-нибудь скомпилируем в контейнере или установим нечто совсем странное. Не переключайтесь! smile


Категория: Программирование, веб | Опубликовано 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. На сей раз решил, так сказать, вернуться к истокам - ведь когда-то многие веб-сервера были на фряхе, а также посмотреть, насколько она компактна сама по себе и в плане ресурсоемкости.


Комментарии, обсуждение