В этой статье я расскажу, как я решил проблему с правами на файлы, которая проявляется при разработке веб-приложений с использованием Docker и контейнеров php-fpm, nginx, node.
Суть проблемы заключается в следующем. Предположим, что у вас выполнены следующие условия:
- на вашей машине установлена ОС из семейства Linux, например, Ubuntu
- вы логинитесь и работаете не root-пользователем, например, пользователь ubuntu с id = 1000
- используете образы php-fpm, nginx, node из официального репозитория Docker Hub
- исходный код вы редактируете не в докере, а на хост-машине, монтируя директорию с ним в докер-контейнеры
Решение
Суть решения в том, что мы меняем пользователя, под которым работает nginx, php-fpm, node на пользователя с тем же id, что и локальный пользователь, который редактирует файлы.Вынесем id пользователя в build arguments для возможности указывать разных пользователей в разных окружениях. Команда Dockerfile `RUN` не поддерживает ENV-переменные, поэтому используем именно build arguments. Для development подойдет локальный пользователь, для stage и CI нужен тот пользователь, от имени которого запускаются build-скрипты в CI, например `gitlab-runner`, а на production лучше оставить www-data (id=33) и сделать выполнение консольных команд от его имени.
Привожу команды Dockerfile для каждого образа, которые создают нового пользователя внутри образа и прописывают его в конфиги приложений.
# PHP-FPMАргументы имеют значения по умолчанию, которые соответствуют production окружению. Мы создаем пользователя в системе, потому что конфигурация nginx и php-fpm требуют указания имени пользователя, а не uid. С помощью `sed` меняем конфигурацию приложения, указывая, что сервис должен работать от имени указанного пользователя. Последней командой мы указываем, что контейнер, созданный на основе этого образа, должен работать от имени указанного пользователя.
ARG WEB_USER_ID=33
ARG WEB_USER_NAME=www-data
RUN echo "Building for web user: id=${WEB_USER_ID} name=${WEB_USER_NAME}"
RUN useradd -m -u ${WEB_USER_ID} ${WEB_USER_NAME} || echo "User exists"
RUN sed -i -- "s/user = www-data/user = ${WEB_USER_NAME}/g" /usr/local/etc/php-fpm.d/www.conf
USER ${WEB_USER_ID}
#NGINX
ARG WEB_USER_ID=33
ARG WEB_USER_NAME=www-data
RUN echo "Building for web user: id=${WEB_USER_ID} name=${WEB_USER_NAME}"
RUN useradd -m -u ${WEB_USER_ID} ${WEB_USER_NAME} || echo "User exists"
RUN sed -i -- "s/user nginx;/user ${WEB_USER_NAME};/" /etc/nginx/nginx.conf
#NODE
ARG WEB_USER_ID=33
ARG WEB_USER_NAME=www-data
RUN echo "Building for web user: id=${WEB_USER_ID} name=${WEB_USER_NAME}"
RUN useradd -m -u ${WEB_USER_ID} ${WEB_USER_NAME} || echo "User exists"
USER ${WEB_USER_ID}
Сервисы php и nginx удобно поднимать с помощью `docker-compose`. Для указания пользователя в build arguments на разных окружениях можно использовать docker-compose.override.yml:
app:Контейнер с node, который используется с целью сборки через npm, не останется поднятым через docker-compose, поэтому отдельно соберем для него образ с аргументами ‘—build-arg’ и будем его запускать:
build:
context: ./app
args:
WEB_USER_ID: 1000
WEB_USER_NAME: roman
nginx:
build:
context: ./nginx
args:
WEB_USER_ID: 1000
WEB_USER_NAME: roman
docker build -t project_node ./node --build-arg WEB_USER_ID="$WEB_USER_ID" --build-arg WEB_USER_NAME="$WEB_USER_NAME"
docker run --rm \Здесь используются переменные bash-скрипта `$WEB_USER_ID` и `$WEB_USER_NAME`, определить которые можно несколькими способами. В разделе ниже о скрипте первой настройки есть пример получения значений этих переменных из ранее поднятого контейнера.
-v `pwd`:/var/www/project \
-w /var/www/project/app \
-e "HOME=/var/node" \
project_node bash
После сборки образов с указанием id и именем пользователя не требуется указывать дополнительно еще раз пользователя при использовании команд `docker-compose exec` и `docker run`, если только не нужно выполнить команду от имени root-пользователя.
Права на доступ к записи в некоторые каталоги
Некоторые программы внутри контейнера могут пытаться писать в директории, доступа к которым у них нет у нового пользователя. Это директории не внутри домашней директории нового пользователя.Если эта директория не монтируется с хост-системы, то права на запись можно дать отдельной командой в Dockerfile:
RUN mkdir -m 777 -p /var/nodeили
RUN chown -R ${WEB_USER_ID} /var/nodeЕсли же директория монтируется, то права на запись в нее будет только у root-пользователя. В этом случае изменять права нужно отдельной командой сразу после старта контейнера. Например, добавить в скрипт первой настройки:
docker-compose exec --user root app chown -R "$WEB_USER_ID" ./
Скрипт первой настройки
При первом развертывании проекта на машине после сборки образов и поднятии сервиса я предлагаю выполнять init-скрипт:#!/bin/bash
echo "Permissions"
WEB_USER_ID="$(docker-compose exec -T app id -u | tr -d '[:space:]')"
WEB_USER_NAME="$(docker-compose exec -T app id -u -n | tr -d '[:space:]')"
if [ -z "${WEB_USER_ID}" ] || [ -z "${WEB_USER_NAME}" ]; then
echo "Cannot get user id or name. Exit."
exit 1
fi;
echo "Using WEB_USER_ID=$WEB_USER_ID, WEB_USER_NAME=$WEB_USER_NAME"
docker-compose exec -T --user root app chown -R "$WEB_USER_ID" ./
Скрипт получает из образа id пользователя, под которым работает сервис, затем устанавливает для всех файлов проекта владельца по этому uid.
При регулярном развертывании на production следует учесть, что при вышеуказанном подходе владельцем всех файлов проекта должен быть тот же пользователь, что указан в аргументах сборки, например, www-data (33). Здесь многое зависит о того, как организован deploy. Например, либо сразу запускать команды git от имени того же пользователя, либо потом менять владельца полученных новых файлов.
Мне помогло! спасибо!
ОтветитьУдалить