Задача — подготовить Docker образ с PHP Composer.
Ниже рассмотрим сначала сам Composer (от PHP далёк, и с Composer дела раньше не имел, хотя сам PHP потрогать довелось), потом — пример сборки Docker контейнера и его использование под разными пользователями.
Результат можно посмотреть в Github.
Содержание
PHP Composer
Composer предназначен для установки общих библиотек при создании PHP-проекта. Как ближайший аналог из «мира» NodeJS — npm
, который выполняет установку различных зависимостей.
Кроме Composer для PHP имеется PEAR, но он сейчас практически не используется (хотя Composer поддерживает установку пакетов из PEAR).
Как и для npm
— у Composer имеется файл, в котором перечисляются необходимые для установки зависимости и их версии, и репозитории.
Для Composer дефолтный репозиторий — https://packagist.org, кроме него в роли репозитория можно использовать любой VCS типа Github.
Установка Composer
Сначала — установим Composer локально.
На Linux выполняем:
[simterm]
$ curl -s https://getcomposer.org/installer | php All settings correct for using Composer Downloading... Composer (version 1.6.3) successfully installed to: /home/setevoy/Work/composer.phar Use it: php composer.phar
[/simterm]
Проверяем тип файла:
[simterm]
$ file composer.phar composer.phar: a /usr/bin/env php script executable (binary data)
[/simterm]
Проверяем его работу:
[simterm]
$ php composer.phar ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 1.6.3 2018-01-31 16:28:17 Usage: command [options] [arguments] Options: -h, --help Display this help message -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output ...
[/simterm]
Переносим в /usr/loca/bin
:
[simterm]
$ sudo mv composer.phar /usr/local/bin/composer $ sudo chmod +x /usr/local/bin/composer
[/simterm]
Создадим тестовый проект:
[simterm]
$ mkdir ~/Scripts/PHP/ComposerTest $ cd ~/Scripts/PHP/ComposerTest/
[/simterm]
Например у нас есть зависимость от PHP фрейморка Slim — в каталоге PHP проекта создадим файл composer.json
, в котором описываем зависимость:
{ "require": { "slim/slim": "2.*" } }
Для npm
дефолтным каталог является ~/node_modules
:
[simterm]
$ npm root /home/setevoy/node_modules
[/simterm]
А для composer
— vendor
в каталоге, из которого вызвается composer
.
Запускаем установку:
[simterm]
$ composer install Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 1 install, 0 updates, 0 removals - Installing slim/slim (2.6.3): Downloading (100%) slim/slim suggests installing ext-mcrypt (Required for HTTP cookie encryption) slim/slim suggests installing phpseclib/mcrypt_compat (Polyfil for mcrypt extension) Writing lock file Generating autoload files
[/simterm]
Проверяем каталог vendor
:
[simterm]
$ tree vendor/ vendor/ ├── autoload.php ├── composer │ ├── autoload_classmap.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_real.php │ ├── autoload_static.php │ ├── ClassLoader.php │ ├── installed.json │ └── LICENSE └── slim └── slim ├── composer.json ├── CONTRIBUTING.md ├── index.php ├── LICENSE ├── phpunit.xml.dist ├── README.markdown ├── Slim │ ├── Environment.php │ ├── Exception ...
[/simterm]
В случае, если установку надо выполнить от другого пользователя, что бы установленные файлы имели другой UID/GID — используем, например, sudo
:
[simterm]
$ sudo useradd phpcomposeruser $ rm -rf vendor/ $ ls -l total 12 -rw-r--r-- 1 setevoy setevoy 51 Mar 17 11:59 composer.json -rw-r--r-- 1 setevoy setevoy 2192 Mar 17 12:01 composer.lock $ sudo -u phpcomposeruser composer install Cannot create cache directory /home/phpcomposeruser/.composer/cache/repo/https---packagist.org/, or directory is not writable. Proceeding without cache Cannot create cache directory /home/phpcomposeruser/.composer/cache/files/, or directory is not writable. Proceeding without cache Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 1 install, 0 updates, 0 removals - Installing slim/slim (2.6.3): Downloading (100%) slim/slim suggests installing ext-mcrypt (Required for HTTP cookie encryption) slim/slim suggests installing phpseclib/mcrypt_compat (Polyfil for mcrypt extension) Generating autoload files
[/simterm]
Проверяем:
[simterm]
$ ls -l vendor/slim/ total 4 drwxr-xr-x 4 phpcomposeruser phpcomposeruser 4096 Mar 17 12:08 slim
[/simterm]
Docker multi-stage билд
Следующей задачей будет собрать Docker образ, который будет включать в себя PHP и Composer.
Тут можно использовать multi-stage билды, которые появились в Docker в версии 17.05.
Идея заключается в том, что мы запускаем контейнер с Composer, затем — контейнер с желаемой версией PHP, копируем из контейнера с Composer его исполняемый phar-файл, и создаём новый контейнер, из которого собираем свой образ.
Dockerfile
будет выглядеть так:
FROM composer:latest AS composer FROM php:7.2.3 COPY --from=composer /usr/bin/composer /usr/bin/composer RUN composer --version && php --v
В первой строке, в инструкции FROM
, используем оператор AS
, что бы задать имя запускаемому во время билда контейнеру, а затем — ссылаемся на это имя в COPY
, что бы из него скопировать необходимый файл, в данном случае — /usr/bin/composer
.
Собираем образ:
[simterm]
$ docker build -t php-composer:1.0 . Sending build context to Docker daemon 502.8kB Step 1/4 : FROM composer:latest AS composer ---> 2135a91c923b Step 2/4 : FROM php:7.2.3 ---> c8d1a5f14eb7 Step 3/4 : COPY --from=composer /usr/bin/composer /usr/bin/composer ---> Using cache ---> c6833892ea80 Step 4/4 : ENTRYPOINT ["/usr/bin/composer"] ---> Running in 0169f3c917ae Removing intermediate container 0169f3c917ae ---> 10b6c61224b9 Successfully built 10b6c61224b9 Successfully tagged php-composer:1.0
[/simterm]
Проверяем:
[simterm]
$ docker run -ti php-composer:1.0 --version Do not run Composer as root/super user! See https://getcomposer.org/root for details Composer version 1.6.3 2018-01-31 16:28:17
[/simterm]
ОК — всё работает.
Репозитории в composer.json
Попробуем установить библиотеку geoip2
, используя собранный образ.
Для этого потребуется указать репозиторий, из которого библиотеку можно скачать — используем repositories
, обновляем composer.json
:
{ "require": { "geoip2/geoip2": "~2.0" }, "repositories": [{ "type": "vcs", "url": "[email protected]:antimattr/GoogleBundle.git"} ] }
Обновим Dockerfile
— добавим WORKDIR
для указания каталога, в котором Composer будет искать composer.json
:
FROM composer:latest AS composer FROM php:7.2.3 COPY --from=composer /usr/bin/composer /usr/bin/composer WORKDIR /app
Убираем ENTRYPOINT
— composer
будем вызывать явно, при запуске контейнера.
Собираем v2:
[simterm]
$ docker build -t php-composer:2.0 . Sending build context to Docker daemon 7.68kB Step 1/4 : FROM composer:latest AS composer ---> 2135a91c923b Step 2/4 : FROM php:7.2.3 ---> c8d1a5f14eb7 Step 3/4 : COPY --from=composer /usr/bin/composer /usr/bin/composer ---> Using cache ---> c6833892ea80 Step 4/4 : WORKDIR /app ---> Using cache ---> 68ece553d2ef Successfully built 68ece553d2ef Successfully tagged php-composer:2.0
[/simterm]
Проверяем:
[simterm]
$ docker run -ti --volume $(pwd)/:/app php-composer:2.0 composer --version Do not run Composer as root/super user! See https://getcomposer.org/root for details Composer version 1.6.3 2018-01-31 16:28:17
[/simterm]
ОК, работает.
Удаляем composer.lock
, который остался от установки Slim в начале:
[simterm]
$ cat composer.lock ... "packages": [ { "name": "slim/slim", "version": "2.6.3", "source": { "type": "git", "url": "https://github.com/slimphp/Slim.git", ... $ rm composer.lock
[/simterm]
И запускаем контейнер, передавая composer install
:
[simterm]
$ docker run -ti --volume $(pwd)/:/app php-composer:2.0 composer install Do not run Composer as root/super user! See https://getcomposer.org/root for details Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 4 installs, 0 updates, 0 removals Failed to download composer/ca-bundle from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing composer/ca-bundle (1.1.0): Cloning 943b2c4fca [RuntimeException] Failed to clone https://github.com/composer/ca-bundle.git, git was not found, check that it is installed and in your PATH env. sh: 1: git: not found
[/simterm]
Отлично! Всё работает. Вот только git
-а нет 🙂
Т.к. просто скопировать исполняемый файл git
— не вариант из-за зависимостей от системных библиотек, добавим его установку.
Обновляем Dockerfile
:
FROM composer:latest AS composer FROM php:7.2.3 COPY --from=composer /usr/bin/composer /usr/bin/composer RUN apt update && apt install -y git WORKDIR /app
Собираем образ:
[simterm]
$ docker build -t php-composer:3.0 . ... Successfully tagged php-composer:3.0
[/simterm]
И запускаем composer install
:
[simterm]
$ docker run -ti --volume $(pwd)/:/app php-composer:3.0 composer install Do not run Composer as root/super user! See https://getcomposer.org/root for details Loading composer repositories with package information Reading composer.json of antimattr/google-bundle (v2.0.0) Updating dependencies (including require-dev) Package operations: 4 installs, 0 updates, 0 removals Failed to download composer/ca-bundle from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing composer/ca-bundle (1.1.0): Cloning 943b2c4fca from cache Failed to download maxmind/web-service-common from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing maxmind/web-service-common (v0.5.0): Cloning 61a9836fa3 from cache Failed to download maxmind-db/reader from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing maxmind-db/reader (v1.3.0): Cloning e042b4f8a2 from cache Failed to download geoip2/geoip2 from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing geoip2/geoip2 (v2.8.0): Cloning 63b0d87d47 from cache maxmind-db/reader suggests installing ext-bcmath (bcmath or gmp is required for decoding larger integers with the pure PHP decoder) maxmind-db/reader suggests installing ext-gmp (bcmath or gmp is required for decoding larger integers with the pure PHP decoder) maxmind-db/reader suggests installing ext-maxminddb (A C-based database decoder that provides significantly faster lookups) Writing lock file Generating autoload files
[/simterm]
ОК, теперь всё работает.
Composer user
Последняя проблема, которую осталось решить — это пользователь:
[simterm]
$ ls -l total 24 -rw-r--r-- 1 setevoy setevoy 142 Mar 17 14:18 composer.json -rw-r--r-- 1 root root 8286 Mar 17 14:18 composer.lock -rw-r--r-- 1 setevoy setevoy 155 Mar 17 14:14 Dockerfile drwxr-xr-x 6 root root 4096 Mar 17 14:18 vendor
[/simterm]
vendor
и composer.lock
создаются от рута, т.к. в самом контейнере используется пользователь root
, если не указано другое.
Проверим — добавляем RUN whoami
в Dockerfile
:
FROM composer:latest AS composer FROM php:7.2.3 COPY --from=composer /usr/bin/composer /usr/bin/composer RUN apt update && apt install -y git RUN whoami WORKDIR /app
Собираем:
[simterm]
$ docker build -t php-composer:3.1 . ... Step 5/6 : RUN whoami ---> Running in 8707eba18a3b root ... Successfully tagged php-composer:3.1
[/simterm]
А мы хотим, что бы каталог vendor
на хосте оставался за пользователем phpcomposeruser
, которого мы создали в начале.
Тут есть два варианта.
Dockerfile USER
Первый — добавить пользователя phpcomposeruser
во время сборки контейнера, и использовать инструкцию USER
.
Обновляем Dockerfile
:
FROM composer:latest AS composer FROM php:7.2.3 COPY --from=composer /usr/bin/composer /usr/bin/composer RUN apt update && apt install -y git RUN adduser phpcomposeruser USER phpcomposeruser RUN whoami RUN id WORKDIR /app
Собираем новый контейнер:
[simterm]
$ docker build -t php-composer:3.2 . ... Step 5/9 : RUN useradd phpcomposeruser ---> Using cache ---> f7003b3a0afb Step 6/9 : USER phpcomposeruser ---> Running in c4d318683899 Removing intermediate container c4d318683899 ---> ca2ce1f6854c Step 7/9 : RUN whoami ---> Running in 9f8b4339ff66 phpcomposeruser Removing intermediate container 9f8b4339ff66 ---> 9c2670aba71f Step 8/9 : RUN id ---> Running in 288eab068311 uid=1000(phpcomposeruser) gid=1000(phpcomposeruser) groups=1000(phpcomposeruser) ... Successfully tagged php-composer:3.2
[/simterm]
Удаляем каталог vendor
и composer.lock
:
[simterm]
$ sudo rm -rf vendor/ && sudo rm composer.lock
[/simterm]
Собираем проект, используя версию 3.2:
[simterm]
$ docker run -ti --volume $(pwd)/:/app php-composer:3.2 composer install
[/simterm]
Проверяем файлы:
[simterm]
$ ls -l total 24 -rw-r--r-- 1 setevoy setevoy 142 Mar 17 14:37 composer.json -rw-r--r-- 1 setevoy setevoy 8286 Mar 17 14:37 composer.lock -rw-r--r-- 1 setevoy setevoy 222 Mar 17 14:34 Dockerfile drwxr-xr-x 6 setevoy setevoy 4096 Mar 17 14:37 vendor
[/simterm]
Упс! Откуда взялся setevoy
, если мы указывали phpcomposeruser
?
Вернёмся к логу сборки образа:
…
Step 8/9 : RUN id
—> Running in 288eab068311
uid=1000(phpcomposeruser) gid=1000(phpcomposeruser) groups=1000(phpcomposeruser)
…
А теперь проверим — кому принадлежит UID 1000 на хосте, с которого мы билдим:
[simterm]
$ getent passwd 1000 setevoy:x:1000:1000::/home/setevoy:/bin/bash
[/simterm]
Отлично… Пользователь с UID 1000 в образе — это phpcomposeruser
(первый созданный в системе пользователь, после рута), а на хосте — это пользователь setevoy
.
Соответственно, на хосте, с которого запускаем билд, у пользователя phpcomposeruser
== UID 1001:
[simterm]
$ id phpcomposeruser uid=1001(phpcomposeruser) gid=1002(phpcomposeruser) groups=1002(phpcomposeruser)
[/simterm]
И особых прав на каталог vendor
у него нет:
[simterm]
$ sudo -u phpcomposeruser touch vendor/file touch: cannot touch 'vendor/file': Permission denied
[/simterm]
docker run --user
Другой вариант решить проблему — запускать контейнер от определённого пользователя, используя --user
.
Убираем USER
из Dockefile
, возвращаем его к виду:
FROM composer:latest AS composer FROM php:7.2.3 COPY --from=composer /usr/bin/composer /usr/bin/composer RUN apt update && apt install -y git WORKDIR /app
Собираем версию 3.3:
[simterm]
$ docker build -t php-composer:3.3 .
[/simterm]
Удаляем каталог и файл:
[simterm]
$ !685 sudo rm -rf vendor/ && sudo rm composer.lock
[/simterm]
И пробуем собрать проект, передав --user phpcomposeruser
:
[simterm]
$ docker run --user phpcomposeruser -ti --volume $(pwd)/:/app php-composer:3.3 composer install docker: Error response from daemon: linux spec user: unable to find user phpcomposeruser: no matching entries in passwd file.
[/simterm]
Снова «упс»? 🙂
Логично — мы ведь не создавали пользователя phpcomposeruser
в образе php-composer:3.3
:
[simterm]
$ docker run -ti php-composer:3.3 cut -d: -f1 /etc/passwd root daemon bin sys sync games man lp mail news uucp proxy www-data backup list irc gnats nobody _apt
[/simterm]
Поэтому как вариант решения — мы можем передать в контейнер UID и GID вместо имени пользователя — используем id -u
(print only the effective user ID) и -g
(print only the effective group ID).
Меняем владельца composer.json
на phpcomposeruser
, который будет владельцем проекта:
[simterm]
$ sudo chown phpcomposeruser composer.json
[/simterm]
Собираем проект:
[simterm]
$ docker run --user $(id -u phpcomposeruser):$(id -g phpcomposeruser) -ti --volume $(pwd)/:/app php-composer:3.3 composer install Cannot create cache directory /.composer/cache/repo/https---packagist.org/, or directory is not writable. Proceeding without cache Cannot create cache directory /.composer/cache/files/, or directory is not writable. Proceeding without cache Loading composer repositories with package information Cannot create cache directory /.composer/cache/repo/github.com/antimattr/GoogleBundle/, or directory is not writable. Proceeding without cache Updating dependencies (including require-dev) Package operations: 4 installs, 0 updates, 0 removals Failed to download composer/ca-bundle from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing composer/ca-bundle (1.1.0): Cloning 943b2c4fca Failed to download maxmind/web-service-common from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing maxmind/web-service-common (v0.5.0): Cloning 61a9836fa3 Failed to download maxmind-db/reader from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing maxmind-db/reader (v1.3.0): Cloning e042b4f8a2 Failed to download geoip2/geoip2 from dist: The zip extension and unzip command are both missing, skipping. A php.ini file does not exist. You will have to create one. Now trying to download from source - Installing geoip2/geoip2 (v2.8.0): Cloning 63b0d87d47 maxmind-db/reader suggests installing ext-bcmath (bcmath or gmp is required for decoding larger integers with the pure PHP decoder) maxmind-db/reader suggests installing ext-gmp (bcmath or gmp is required for decoding larger integers with the pure PHP decoder) maxmind-db/reader suggests installing ext-maxminddb (A C-based database decoder that provides significantly faster lookups) Writing lock file Generating autoload files
[/simterm]
Проверяем:
[simterm]
$ ls -l total 24 -rw-r--r-- 1 phpcomposeruser setevoy 142 Mar 17 14:54 composer.json -rw-r--r-- 1 phpcomposeruser phpcomposeruser 8286 Mar 17 14:59 composer.lock -rw-r--r-- 1 setevoy setevoy 155 Mar 17 14:44 Dockerfile drwxr-xr-x 6 phpcomposeruser phpcomposeruser 4096 Mar 17 14:59 vendor
[/simterm]
Готово.