Comments 80

Зачем настраивать DBGp Proxy если 1) вы его нигде не используете и 2) он вам попросту не нужен здесь?

Спасибо. Это называется инерция мышления. DBGp Proxy настраивать абсолютно ни к чему. Чуть позже внесу правки в статью.

Мне кажется таких статей тысяча и одна и все с одним посылом — как запустить hello-world.php.
Было бы круто описать работу с k8s, какие-нибудь траблы по пути познания докера и способы решения.

Облачные дебагеры в k8s для ловли багов по куберподам?
Месье знает толк в извращениях! Но вообще классно что даже такую задачу можно решить докером и настроить рабочее окружение на свой вкус. Другое дело что в принципе такой дебагинг — занятие крайне сомнительно и лучше покрывать тестами узкие места. На то приложения и находятся отдельно для разделения логики. А так выходит логику разнесли, а дебажить все равно будем в едином флоу привязывая их друг к другу?! Почему тогда не слить вместе в одну репу и усложнять?

Мой посыл в том, что статей вроде "Docker + php" достаточно много.
Но мало статей, как развернуть все это в проде, как масштабировать, как обеспечить "целостность данных", элементарно взаимодействие между сервисами и т.д.

Спасибо за рекомендацию. Пробовал, лично у меня не пошла работа с Makefile. Не зря же я написал в статье, что это очень спорный момент.
Наши же приложения под гитом, и если мы дадим права 777 локально, то гит всё это запишет, и передаст на боевой сервер.

git config core.fileMode false решает проблему с правами и гитом
Так оно так. Спасибо за комментарий. Но само действие давать на что-то права 777 меня глубоко нервирует.

До тех пор пока не понадобиться залить какой-нибудь исполняемый bash скрипт. Можно включить, залить и выключить, но как правило все проблемы с правами доступа решаются без chmod.

Недавно решал похожую задачу — органзиовать идентичную среду разработки нескольких проектов для всей команды, используюя Docker.


С правами на файлы закостылил точно так же, как и Вы. Лучше решения тоже не смог найти, буду очень рад, если кто-то подскажет.


Правда под каждый отдельный проект я решил поднимать свои контейнеры (nginx + php-fpm + mysql). Да, в этом случае появляется необходимость использования разных портов, но за счет этого появляется больше гибкости и изолированности. Долго размышлял над этим вопросов, и решил, что при использовании разных конфигов уменьшается связанность проектов между собой.
Например, в вашем варианте, если допустить ошибку в конфиге nginx (например, в файле first.conf), то ляжет весь контейнер с nginx, а вместе с ним и все проекты, которые зависят от него.
Тоже самое касается и контейнера с mysql. Например, разные проекты могут использовать разные версии MySQL (5.7 или 8.0). Имея отдельный контейнеры можно более гибко управлять отдельными проектами, не нарушая работу остальных.


В docker-compose.yml вы передаете "XDEBUG_CONFIG", но в Dockerfile я не вижу использования этого. А как тогда это попадает внутрь контейнера? Специальный проверил базовый образ php:7.1-fpm, он тоже не ожидает увидеть такую перменную окружения.

Огромное спасибо за развернутый комментарий. Да, действительно, когда мы запускаем каждое приложение отдельно, мы можем быть более гибкими. Но, тут уже дело вкуса. В нашей команде все конфиги вылизаны, у нас очень непросто с конфигами для nginx, например. Но, мы спокойны — если к нам придет джун, то мы дадим ему 100% рабочую конфигурацию.

А как попадает XDEBUG_CONFIG внутрь контейнера? Точно так же как все другие переменные окружения (environment). Как это происходит на уровне приложения я не знаю. Я знаю только то, что докер нам позволяет настраивать контейнеры на лету, за что ему огромное спасибо. Изначально Xdebug в наших контейнерах не настроен вообще никак. Если бы мы развернули рабочее окружение без докера, нам мы пришлось прописывать настройки для Xdebug явно в файле php.ini, например. А тут докер нам позволяет всё это сделать на лету.
А как попадает XDEBUG_CONFIG внутрь контейнера? Точно так же как все другие переменные окружения (environment). Как это происходит на уровне приложения я не знаю. Я знаю только то, что докер нам позволяет настраивать контейнеры на лету, за что ему огромное спасибо. Изначально Xdebug в наших контейнерах не настроен вообще никак. Если бы мы развернули рабочее окружение без докера, нам мы пришлось прописывать настройки для Xdebug явно в файле php.ini, например. А тут докер нам позволяет всё это сделать на лету.

Все, я понял почему это работает. Это сам xdebug поддерживает передачу параметров через переменную окружения XDEBUG_CONFIG.
Из оф. документации xdebug:


All settings that you can set through the XDEBUG_CONFIG setting can also be set with normal php.ini settings.
Может быть параметры xdebug`а установлены в его локальном php.ini
ADD ./php.ini /usr/local/etc/php/php.ini

Нет, тогда какой смысл конфиг xdebug передавать через переменные окружения, и хардкодить их в php.ini?
В предыдущем комментарии я уже расписал почему это работает :)

с правами на файлы:
— в Dockerfile: ARG USERID=1000
— в docker-compose.yml USERID: $USERID
— В папке с докером надо сделать export USERID=$UID (баш подставляет в UID юзерайди. direnv у меня подхватывает конфиг с env)
— Дальше в Dockerfile useradd с нужным UID, USER название_юзера

И контейнер начинает делать файлы с тем же uid что и хостовой пользователь. Если не хочется заморачиваться то в Dockerfile ручками ARG менять. В такой схеме еще и контейнер не от рута работает, что поидее безопаснее (при подтягивании зависимостей, например).

Дело в том, что мне не понравился подход, в котором каждый член команды должен локально билдить образы. А в случае обновления Dockerfile (который можно положить под git) еще и потом перебилдивать заново. Для упрощения, я поднял свой приватный реестр для образов, один раз сбилдил свои образы и запушил туда, а членам команды предоставил docker-compose.yml конфиги, в которых уже просто указывается ссылка на наш реестр.
При таком подходе, тиммейтам не нужно делать никаких лишних телодвижений, достаточно просто выполнить одну команду "docker-compose up" и готовые образы просто выкачаются с нашего реестра. Это на много быстрее и проще для всех. А в случае обновления, я просто сам ребилдю образы, снова пушу в реестр и обновляю версии образов в docker-compose.yml (который находится под git). Таким образом, у всех членов команды автоматически обновятся их контейнеры (после git pull и перезапуска контейнеров, естественно).


Так что, на этапе сборки образов мне просто неизвестны UID будущих пользователей. Сейчас просто надеюсь, что там будут 1000, в инном случае решаю проблему по ходу их появления.

Очень интересный подход, спасибо. Именно это и будет следующим этапом на пути развития создания единой среды разработки для членов команды. Отлично!

Я потратил не мало времени, пока пришел к такой схеме.
Если вдруг понадобится совет — обращайтесь в ЛС, может чем смогу помочь ;)

Следующим этапом будет подключение этого реестра в цепочку ci\cd, чтобы сборки сами сохраняли в него артефакты и оттуда же их брали для тестирования. А потом можно будет оттуда же и деплоиться :) Тогда мало того что у вас локально у всей команды будут одни и те же образы (которые регулярно обновляются в реестре), так у вас еще и на проде будут те же самые образы, что даст вам еще больше уверенности при отладке — если что-то идет не так, вы просто выкачиваете из реестра образ и дебажите конкретный образ, это супер-удобно
Тоже так делал, потом понял, что можно то же самое, но без ARG и ребилда. Используя ENTRYPOINT, т.е. добавлять юзера и приводить в соответствие UID и GID не на стадии сборки, а на стадии запуска.

Кстати, хорошая идея. Я и так использую ENTRYPOINT и скрипты-хелперы для своих нужд (автоматизирую установку composer зависимостей и т.п. вещи). На этапе старта контейнеров можно чекать UID пользователя и если что апдейтить пользователя в контейнере. Спасибо за наводку.

Anton_Zh, у меня сейчас руки дошли до апдейта своих конфигов Docker. Решил попробовать заменить свой хардкод в Dockerfile (где я просто делал usermod -u 1000 www-data) на обновлении UID и GID при запуске контейнера.


Но тут у меня возник вопрос, ты пишешь, что можно без ARG, но каким образом тогда прокидывать UID и GID хостового пользователя внутрь ENTRYPOINT скрипта? Этот же скрипт не имеет возможности доставать хост ENV переменные, если их не указать в args (в docker-compose.yml).


UPD: все, вопрос снят, нашел ответ в твоем "шаблончике". Просто создаешь кастомный скрипт, где перед docker-compose up делается export ENV переменных с UID и GID запускающего пользователя.

Да, в этом случае появляется необходимость использования разных портов

В нашей фирме мы делаем огромное количество проектов и запоминать для каждого порт было бы ужасно, еще одиним решением было держать запущенным только один проект. И оба эти решения имеют свои минусы.
На помощь нам пришел Traefik, мы назначили каждому контейнеру домен вида project.domain.localhost и теперь когда нам надо работать над отдельным проектом достаточно написать его домен в браузере + .localhost что бы обратиться к бегущему локально контейнеру этого проекта.

Для того что бы не было заморочек с настройками сети, в контейнер с php прописываем XDEBUG_CONFIG: "remote_host= host.docker.internal remote_enable=1", а в конфиг Xdebug, в секции IP адреса так же указываем host.docker.internal. Это полностью решает проблему с разными IP

Спасибо за комментарий. К великому сожалению я не знаю как будет работать данный мануал не под Линукс. Вся наша команда сидит на Ubuntu-подобных OS, данный мануал писался именно для Линукса.

Это понятно. Я спрашивал о host.docker.internal, работает ли оно в Linux (так как на Mac & Windows работает).

В Линуксе не работает.
Я сделал костыль в виде промежуточного контейнера, который пытается отрезолвить host.docker.internal, если не выходит — берет ip default gw, и nat-ит на него весь диапазон портов.

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

Мы делаем примерно так в докер-композе:
    environment:
      PHP_IDE_CONFIG: serverName=${XDEBUG_SERVER_NAME:-some_project.docker}
      XDEBUG_CONFIG: remote_host=${XDEBUG_HOST:-192.168.99.1}

192.168.99.1 — это стандартный айпишник у коллег под маком для докера в virtualbox
Если айпишник отличается, то в .env файле можно задать свой локальный айпишник, на котором ваш шторм (или другая IDE) будет слушать дебаг

XDEBUG_HOST=10.22.33.44
ну, тут много причин. первая — раньше работало только так, под мак не было нормально гипервизора, как и под винду и станартный docker4win и docker2mac поднимали все в виртуалбоксе. Сейчас с гипервизорами все хорошо, но остался второй момент — в виртуалбоксе можно иметь несколько докер-хостов, что удобно при параллельной разработке нескольких проектов. если у них общий пул портов (например 80+9000 в каждом проекте), то гораздо проще гасить и ресторить целиком докер хост, чем каждый раз гасить и ресторить.
В Linux xdebug.remote_connect_back чудесно работает! Не нужно ничего отдельно резолвить.

Все так, но не в консоли. Если не нужно дебажить консольные скрипты, то при включенной директиве xdebug.remote_connect_back, директива xdebug.remote_host будет проигнорирована
Докер очень медленный, не подходит для проектов с 1к+ файлов. Если на Симфони или Ларавеле еще можно работать с ним, то Magento 2 и Ко неюзабельны вообще.
Пока не поправят скорость работы файловой системы рассматривать докер для разработки я бы не стал.

Это в какой ОС? На линуксе все прекрасно. На маке — ну, да, есть такое, но вполне терпимо с opcache. Насчёт винды я не в курсе, может, действительно все совсем плохо.

На маке все очень плохо. Ждать 5 минут до загрузки страницы — норма. Более-менее решил проблему через локальный nfs, который монтируется в контейнере.
Тут даже не проблема php, nginx отдает статические файлы по 30-40 секунд.

Статические?.. Надо разбираться, во что упирается. У меня медленнее, чем с хостноды, раза в 2-3, но чтобы по 30 секунд… Наблюдал такое только в проекте, где scss на лету компилировался php-библиотекой, но это из разряда «не делайте так».

Сейчас еще подумал — может, в конфигурации вебсервера включен direct io/aio? Надо выключить.

На Винде. Именно для Винды и Мака Докер является вынужденной мерой, для них в первую очередь решается проблема окружения.
Уже сказали на счет Мака, я скажу на счет Винды. Реально первую загрузку страницы когда еще нет кэшей можно ждать минутами на системе 9900k samsung nvme 970 pro. На условном i3 и hdd наверное можно успеть пообедать пока загрузится страница.
С кэшами и прочими оптимизациями получится так: в Линуксе 0,5 сек, в Винде 10 сек. И это не преувеличение. Работать с таким невозможно.

Проблема глобальная, про нее всем известно, но пока решений нет. Возможно, вопрос решится с wsl 2.0.
Да, да, помню этот Issue. Оттуда копировал команды и проверял у себя.
Падение производительности получается настолько большое что проще уже поставить мини-сервер рядом и работать через сеть. Что, в прочем, я и сделал.
К меня не самый мощный ноутбук, но все проекты, включая БД на ssd диске.Все летает. Так что я бы не стал так категорично утверждать, что докер медленный сам по себе.
Используем в проекте примерно такое же решение, но так как uid у всех разный (часть сидит на никсах, часть на маке, где uid первого пользователя 501, часть на компах, оставшихся после ушедших людей), то сделали следующий костыль:
* Есть файл из репозитория, например, composer.json.
* Получаем uid/gid файла
* Создаем пользователя user с нужными uid/gid
* Стартовый скрипт запускается от рута, но тут же делает su user -c <скрипт для старта проекта>

Тоже неудобно местами, иногда определяет неправильно права, но лучше решения не нашли

Иметь удобное локальное окружение — это удобство разработчика. Зачем унифицировать рабочий компьютер, снижая производительность труда? Унифицируйте лучше тестовые и продакшен среды.
С использованием докера как раз можно позволить работать локально так, как нравится.


Просто это не получится конкретно у вас, например потому что вы uid/gid прибиваете гвоздями в значение 1000.
К тому же чем стандартный 33 тоже будет работать, если в коде позаботитесь. Популярные фреймворки в дев-режиме к докеру привыкшие и кеш с umask пишут.


composer лучше устанваливать через COPY --from.


Вы делаете слишком много лишнего, от чего можно избавиться.

Спасибо за комментарий. Сколько людей, столько и мнений. Нашу команду, пока всё устраивает в плане удобства. А по правам доступа — я же честно написал в статье, что это костыль. Можно вообще с ними не заморачиваться, дать 777 на файлы с кешем, папку assets и т.д.

И все будет читаться и писаться.

Но композер, например, ругается на работу под рутом. Все миграции, или иные файлы, которые вы создадите, когда зайдете в контейнер, будут принадлежать не вам, что очень неудобно. Так, что я пока работаю с костылем для решения проблемы прав доступа.
А по правам доступа — я же честно написал в статье, что это костыль.

Так почему не написать хотя бы для статьи (если самим не надо) нормальный способ. который не будет костылем?

proctoleha, для того что бы не было проблем локально на хостовой машине с правами, как уже писали выше — достаточно удобно использовать Makefile. Вот маленький пример того, что сможет помочь с отсутствием файлов и папок с неправильными правами:
CURRENT_ID=$([[ $(id -u) -gt 9999 ]] && echo "root" || id -u)
CURRENT_GROUP=$([[ $(id -g) -gt 9999 ]] && echo "root" || id -g)
DC := CURRENT_USER=${CURRENT_ID}:${CURRENT_GROUP} docker-compose
build:
            @$(DC) build
start:
            @$(DC) up -d
stop:
            @$(DC) down

Данная конструкция работает как на Linux так и на Windows — правильно подставляя id пользователя и группы от имени которого идет работа.
Если Windwos то uid и gid будут выше 9999, и там подставляем рута — с Windows это вполне себе ок, если нет — используем uid и gid текущего пользователя.
Спасибо за комментарий. Есть только одно маленькое но: наши контейнеры ничего не знают о пользователе от имени которого мы их пытаемся запустить. И также мне, лично, не надо запускать все контейнеры от имени текущего пользователя. Мне нужно запустить только контейнеры с php-fpm

концепция контейнеризации нам говорит: один контейнер — один процесс.
один контейнер на nginx, один на php-fpm. Вроде все логично и хорошо.


Но что первому что второму надо доступ к одним и тем же ресурсам (файлам) и мы подключаем том с ними


volumes:
        - ./www:/var/www

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


И тут приходишь к мысли что ну никак не выйдет "12 factor app". Его не прикрутишь к условному куберу легко, без костылей.


Может лучше плюнуть и запустить nginx и fpm в одном контейнере, а в замен получить артефакт какому хоть и требуются другие сервисы, но минимально он самодостаточен?


если что сорри, я ненастоящий сварщик, просто не так давно пытался разобраться в теме php-fpm и так остались открытие вопросы по контейнеризации этого стека

Если статика и php-код разнесены по разным репозиториям, то вполне себе выйдет.
Если нет, то, наверное, логичнее использовать вместо php-fpm nginx unit (да, он недавно научился раздавать статику), а nginx-у оставить маршрутизацию, балансировку, терминирование SSL и вот это все (для разработки, кстати, не факт что это все вообще нужно).

Для проброса в докер uid & gid можно использовать:
# docker-compose.yml
web:
  image: ruby:2.4
  user: "${UID}:${GID}"

Где переменные берутся из окружения
Вместо
RUN wget https://getcomposer.org/installer -O - -q \ | php -- --install-dir=/bin --filename=composer --quiet

вы можете использовать зависимость из другого образа:
FROM composer:latest AS composer
FROM php:7.3.9-alpine3.9

COPY --from=composer /usr/bin/composer /usr/bin/composer
......
Для решения проблемы с правами я использую Docker entrypoint, переменные окружения и команду runuser:

docker-php-entrypoint:
#!/usr/bin/env bash
set -e

if [ -z "${UID}" ]; then
    echo "Необходимо задать значение переменной окружения UID"
    exit 1
fi

if [ -z "${GID}" ]; then
    echo "Необходимо задать значение переменной окружения GID"
    exit 1
fi


groupmod -g ${GID} hostuser
usermod -u ${UID} hostuser  > /dev/null 2>&1

exec runuser -u hostuser -- "$@"

Dockerfile:

COPY docker-php-entrypoint /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-php-entrypoint
RUN useradd -m -U -s /bin/bash hostuser
ENTRYPOINT ["docker-php-entrypoint"]
CMD ["php", "-a"]


Команды будут выполняться внутри контейнера под hostuser, uid и gid которого настраиваются при запуске контейнера. Если не указать их — не запустится. С консольными командами помогает, с веб-сервером, возможно, тоже поможет. Можно попробовать в Entrypoint модифицировать uid и gid www-data (я пробовал, но не помню чем кончилось)))).

Юзаю один контейнер — один процесс (группа схожих процессов). Даже под консоль и веб-сервер разные контейнеры). docker-compose хорош.

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

А чем 777 на ассеты и кеш не устраивают? По моему это хуже чем когда владельцем файлов с кодом является пользователь для запуска веб-сервера. Это гораздо более опасно. Из каталогов с ассетами и кешем просто выполнение php запретить и всё.
Огромное спасибо за комментарий, обязательно покручу ваше решение.
А чем 777 на ассеты и кеш не устраивают? По моему это хуже чем когда владельцем файлов с кодом является пользователь для запуска веб-сервера. Это гораздо более опасно. Из каталогов с ассетами и кешем просто выполнение php запретить и всё.


Почему не устраивает. Все устраивает кроме одного: вот мы зашли в контейнер, и создали миграцию, или иной файл, находясь в контейнере. И от того, что на нужные папки у нас стоят права 777, нам легче не станет. Если не шаманить с правами, то владельцем этого файла будет суперпользователь root, и когда мы вернемся на машину хоста, то нам придется совершать определенные действия, чтобы работать с такими файлами.
Поигрался я с вашим рецептом, в принципе всё написано верно. Но, давайте конкретизируем задачу, о которой речь идет в статье.
Для контейнеров на образе php-fpm необходимо изменить права доступа таким образом, чтобы мы могли заходить в эти контейнеры, и создавать файлы с правами, которые должны совпадать с правами текущего пользователя.
Важно: в таком контейнере уже есть пользователь www-data, от имени которого и работает демон php-fpm.

Что произойдет, если использовать ваш рецепт в данном конкретном случае? Контейнер запустится от имени суперпользователя root, после запуска отработает docker-php-entrypoint, будет создан пользователь hostuser c нужными правами, и php-fpm попытается запуститься от имени этого пользователя, и вылетит с ошибкой. Данный процесс, php-fpm, может запустить только пользователь www-data. Можно конечно как-то подшаманить, но зачем?

Проще просто передать uid и gid текущего пользователя в докерфайл, и использовать
RUN usermod -u ${USER_ID} www-data && groupmod -g ${GROUP_ID} www-data при сборке. Т.е. не жестко приколачивать гвоздями uid и gid в докерфайле, как у меня, сейчас в статье, а передать их извне.

А, понял. Нужно чтобы веб-сервер в контейнере писал файлы с uid/gid что и текущий пользователь хозяйской системы. В этом случае можно usermod www-data (а hostuser вообще не добавлять) в ENTRYPOINT скрипте делать, тогда пересборка не потребуется. Я посмотрю, как это сделать, чтоб Apache или php-fpm в этом случае норм запускались, отпишусь попозже.
Сделал маленький шаблончик. php-fpm пишет под uid и gid юзера, в консоли полноценный юзер, чтоб композер работал без проблем.
Отличный и очень познавательный пример. Очень. Спасибо огромное. Может тогда подскажешь (убедительно прошу: давай перейдем на ты) — как оптимально масштабировать твой пример на несколько проектов? С твоей точки зрения? У меня пока просто тупая мысль — прописывать отдельно php-fpm, и php-cli для каждого проекта.
У меня такие же мысли) Я копирую эти файлы на старте, ведь часто в проектах бывают свои оригинальные docker-зависимости, напрмер в одном проекте может быть mysql, в другом postgres, в третьем еще нужен redis или очереди. Все это добавляется в docker-compose.yml, так что делать библиотеку какую-то смысла особого не вижу, если только под что-то наподобие composer create-project, чтобы консольная команда клонировала репозиторий и производила начальную настройку.
Только такой маленький вопрос ): а зачем использовать такую экзотическую команду в docker-php-entrypoint:
exec runuser -u hostuser -- "$@"

Чем не устраивает простое
su hostuser

У меня баш ругался на нее. Хотя и работал.
Вот не подскажу, не помню, давно пользуюсь runuser. Может просто не догадался использовать su)

Реализация с адресацией выглядит слегка костыльной, рассматривали ли вы traefik если да то почему он вам не подошёл?

Спасибо за комментарий. Мы не рассматривали traefik. Буду очень благодарен, если вы объясните в чем костыльность адресации?
Прошу прощения за сумбурный коментарий, писал его еще до первой кружки кофе.
У меня стояла подобная задача и мне показалось ваше решение слегка усложненным и недостаточно автоматическим. Поэтому хочу написать отличия от вашего решения и спросить что вы о них думаете.

1.
Например, мы можем явно сконфигурировать сеть — 192.168.220.0/28.

Можно избежать конфигурации подсети, если вы собираетесь использовать makefile. Тогда достаточно на команду поднятия контейнеров передать что-то подобное: XDEBUG_REMOTE_HOST := $(shell hostname -I | grep -o "^\S*") и заменить в компоузе на remote_host=${XDEBUG_REMOTE_HOST}
Минусы: Не будет работать на windows.

2.
2. Прописываем узлы first.loc и two.loc в файле /etc/hosts

Если использовать трафик и домены типа your.domain.localhost, то можно избежать пункта с изменением файла хостс.
Плюсы: При добавлении/удалении сервисов не надо будет заставлять всех менять файл хостс т.к. вся конфигурация трафика будет прямо в репозитории.
Спасибо за развернутый ответ. У нас немного разные задачи, и разные, соответственно, подходы. В нашей компании, на сейчас, 6 боевых проектов, и их никогда не будет много. И нам проще делать так, как написано в статье. Но, у вас очень интересный подход. Спасибо еще раз. Для полного ответа мне не хватает квалификации. Буду думать, учиться.

а Вы пытались настроить xdebug используя xdebug.remote_connect_back, а не вот это вот всё с ip?

как-то так:


xdebug.remote_connect_back = 1 # включаем обратное подключение
xdebug.remote_enable = 1
xdebug.remote_port = 9000 # изменяем на нужный порт

не пробовал в такой конфигурации как у Вас, но для одного сервиса работает хорошо и не надо выдумывать ничего с сетью внутри контейнера и биндить ip адрес

Может глупый вопрос. А чем плох запуск nginx и php-fpm от рута? Просто у меня 3 сервиса nginx не помню с каким юзером, php-fpm c www-data и один консольный воркер php-cli который от рута запускается.

Конкретно в вашем случае: php-cli работает от рута. Вы заходите и делаете Composer update, например. Он начнет ругаться, и правильно, с моей точки зрения, что композер под рутом запускать крайне не рекомендуется. Это первое.
Второе: вы заходите в php-cli контейнер, и создаете из консоли, какой-либо файл, например, файл миграции. Кто будет его владельцем? Суперпользователь root.
Сможете вы с этим файлом просто так работать, когда вернетесь на машину хоста? Нет. Вам придется делать лишние движения

В php-cli даже нет composer, он просто использует vendor из php-fpm.
Все миграции, composer install я выполняю через такую команду docker-compose exec -u (id -u) php-fpm <комманда> все файлы имеют владельца текущего пользователя.

Only those users with full accounts are able to leave comments. Log in, please.