Как управлять Docker из PHP: от exec до Docker SDK
При автоматизации процессов с помощью PHP часто возникает необходимость взаимодействовать с Docker: запускать контейнеры, проверять их статус, управлять образами. Выполнение команд Docker из PHP открывает широкие возможности для создания инструментов развертывания, CI/CD и администрирования. В этой статье рассмотрены различные способы интеграции Docker в PHP-код, от простых вызовов shell до использования специализированных библиотек и прямого обращения к Docker API.
Основные подходы к выполнению команд Docker из PHP
Как наиболее эффективно выполнять команды Docker из PHP, обеспечив безопасность и удобство?
Наиболее эффективным решением является использование официальной библиотеки docker-php (Docker PHP SDK). Она предоставляет высокоуровневый интерфейс для управления Docker через его REST API, избавляя от необходимости вручную формировать команды и обрабатывать вывод.
Установка через Composer:
composer require docker-php/docker-phpPhp exec docker (выполнение команд docker из php)
Пример запуска контейнера с образом nginx:latest:
use Docker\DockerClient;
use Docker\Context\ContextBuilder;
$client = DockerClient::createUnixSocketClient();
$containerConfig = [
'Image' => 'nginx:latest',
'Cmd' => ['nginx', '-g', 'daemon off;'],
'ExposedPorts' => ['80/tcp' => []]
];
$container = $client->containers()->create($containerConfig);
$container->start();
echo "Контейнер создан с ID: " . $container->getId();Php генерация кода (генерация кода php)
Пояснение шагов:
- Создание клиента через Unix-сокет Docker (
/var/run/docker.sock). - Формирование конфигурации контейнера (образ, команда, порты).
- Создание контейнера через API.
- Запуск контейнера.
Типичные ошибки и решения:
- Permission denied при подключении к сокету: пользователь, под которым работает PHP, должен быть в группе
docker. Решение - добавить пользователя в группу. - Class not found: не установлен Composer или не выполнен
composer install. - Image not found: образ необходимо предварительно скачать через
$client->images()->pull().
Библиотека поддерживает все основные операции Docker: управление образами, контейнерами, томами, сетями. Это идеальный выбор для сложных проектов, где требуется надежность и полный контроль.
Как запустить команду docker с помощью exec() в PHP?
Функция exec() выполняет внешнюю команду и возвращает последнюю строку вывода. Пример получения списка контейнеров:
$output = [];
$returnCode = 0;
exec('docker ps -a', $output, $returnCode);
if ($returnCode === 0) {
echo implode("\n", $output);
} else {
echo "Ошибка выполнения";
}Пояснение:
$output- массив строк вывода.$returnCode- код возврата (0 успех).
Проблемы:
- Инъекция shell-команд: если часть команды формируется из пользовательского ввода, необходимо экранировать аргументы с
escapeshellarg(). - Отсутствие вывода ошибок:
exec()не перехватывает stderr. Для этого используйте2>&1или функциюproc_open(). - Длительные команды: блокировка скрипта до завершения команды. Для асинхронной работы лучше использовать
proc_open().
Этот вариант подходит для простых сценариев, где важна скорость реализации и не требуется сложное управление.
Как получить полный вывод команды docker в виде строки?
Функция shell_exec() возвращает весь вывод в виде строки. Пример проверки версии Docker:
$version = shell_exec('docker --version');
echo $version;Отличие от exec() - возвращается строка, а не массив. Удобно для коротких команд. Те же проблемы безопасности и отсутствия stderr.
Как выполнить команду docker с полным контролем над потоками ввода/вывода?
Функция proc_open() позволяет управлять stdin, stdout и stderr. Пример запуска контейнера в интерактивном режиме:
$descriptors = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'] // stderr
];
$process = proc_open('docker run -i alpine echo "Hello"', $descriptors, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$returnCode = proc_close($process);
echo "Вывод: $stdout";
echo "Ошибки: $stderr";
}Пояснение: создаются три потока. В примере stdin закрывается сразу, так как не нужен. Полученные выводы позволяют обрабатывать ошибки отдельно.
Типичные ошибки:
- Утечка ресурсов: если не закрыть все pipes и не вызвать
proc_close(), процесс зависнет. - Блокировка при чтении: если команда не завершилась,
stream_get_contents()будет ждать. Нужно настроить неблокирующий режим или использоватьproc_get_status().
Как безопасно выполнять команды docker в Symfony-приложении?
Компонент Symfony Process предоставляет удобную оболочку для управления внешними процессами. Установка:
composer require symfony/processПример запуска docker images:
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
$process = new Process(['docker', 'images']);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
echo $process->getOutput();Преимущества: автоматическое экранирование аргументов, поддержка таймаутов, возможность работы с переменными окружения. Рекомендуется для проектов на Symfony и любых PHP-приложений, где важна безопасность.
Как управлять Docker через его REST API из PHP без использования shell?
Можно напрямую обращаться к Docker API через Unix-сокет с помощью cURL. Пример создания контейнера:
$ch = curl_init('unix:///var/run/docker.sock:/containers/create');
$payload = json_encode(['Image' => 'alpine', 'Cmd' => ['echo', 'hello']]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_UNIX_SOCKET_PATH, '/var/run/docker.sock');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;Пояснение: cURL подключается к сокету, отправляет POST-запрос с JSON. Это низкоуровневый способ, требующий знания API Docker.
Проблемы:
- Сложность: необходимо самостоятельно обрабатывать все endpoint'ы.
- Обработка ошибок: нужно разбирать JSON-ответы, смотреть статус-коды.
- Версионирование: API Docker может меняться.
Этот метод полезен, когда библиотека docker-php недоступна или требуется нестандартное взаимодействие.
Расширенные примеры выполнения команд Docker из PHP
Пример 1: Управление контейнерами через Docker PHP SDK
Полный пример: создание контейнера из образа, запуск, ожидание и получение логов.
use Docker\DockerClient;
use Docker\API\Exception\ContainerCreateConflictException;
$client = DockerClient::createUnixSocketClient();
// 1. Скачиваем образ, если его нет
$images = $client->images();
try {
$images->pull('busybox', 'latest');
} catch (\Exception $e) {
echo "Образ уже есть или не удалось скачать: " . $e->getMessage() . "\n";
}
// 2. Создаем контейнер
$config = [
'Image' => 'busybox',
'Cmd' => ['/bin/sh', '-c', 'for i in 1 2 3; do echo "Line $i"; sleep 1; done']
];
$container = $client->containers()->create($config);
$containerId = $container->getId();
echo "Создан контейнер $containerId\n";
// 3. Запускаем
$container->start();
echo "Контейнер запущен\n";
// 4. Ждем завершения
$container->wait();
// 5. Получаем логи
$logs = $container->logs()->getContents();
echo "Логи:\n" . $logs . "\n";
// 6. Удаляем контейнер
$container->remove(['force' => true]);Создан контейнер abc123def Контейнер запущен Логи: Line 1 Line 2 Line 3
Пример 2: Запуск docker-compose через exec с обработкой ошибок
Предположим, есть файл docker-compose.yml. Запускаем сборку и получаем вывод.
$projectDir = '/var/www/app';
$command = "docker-compose -f {$projectDir}/docker-compose.yml up -d 2>&1";
$output = [];
$retCode = 0;
exec($command, $output, $retCode);
if ($retCode !== 0) {
echo "Ошибка выполнения docker-compose:\n" . implode("\n", $output);
} else {
echo "Сервисы запущены:\n" . implode("\n", $output);
}Сервисы запущены: Container nginx Started Container php-fpm Started
Важно: использование 2>&1 для перенаправления stderr в stdout, иначе ошибки не отобразятся.
Пример 3: Асинхронный запуск нескольких контейнеров через proc_open
Запускаем два контейнера параллельно и собираем их выводы.
$commands = [
'docker run alpine echo "First"',
'docker run alpine echo "Second"'
];
$processes = [];
foreach ($commands as $index => $cmd) {
$descriptors = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
];
$process = proc_open($cmd, $descriptors, $pipes);
$processes[] = ['process' => $process, 'pipes' => $pipes, 'index' => $index];
}
// Закрываем stdin для всех процессов
foreach ($processes as &$p) {
fclose($p['pipes'][0]);
// Неблокирующее чтение
stream_set_blocking($p['pipes'][1], false);
stream_set_blocking($p['pipes'][2], false);
}
// Собираем вывод
$outputs = [];
$errors = [];
foreach ($processes as &$p) {
$out = stream_get_contents($p['pipes'][1]);
$err = stream_get_contents($p['pipes'][2]);
fclose($p['pipes'][1]);
fclose($p['pipes'][2]);
proc_close($p['process']);
$outputs[] = $out;
$errors[] = $err;
}
foreach ($outputs as $i => $o) {
echo "Вывод процесса $i: $o\n";
}
foreach ($errors as $i => $e) {
if ($e) echo "Ошибка процесса $i: $e\n";
}Вывод процесса 0: First Вывод процесса 1: Second
Обратите внимание: вывод включает перевод строки. При неблокирующем чтении данные могут быть неполными, если процесс ещё не завершился. В реальных сценариях необходимо ожидание завершения через proc_get_status().
Пример 4: Получение информации о контейнерах через REST API Docker с помощью cURL
Запрос списка всех контейнеров (включая остановленные) и форматированный вывод.
$socketPath = '/var/run/docker.sock';
$url = 'http://localhost/containers/json?all=true';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_UNIX_SOCKET_PATH, $socketPath);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
if ($response === false) {
echo "cURL error: " . curl_error($ch) . "\n";
} else {
$containers = json_decode($response, true);
foreach ($containers as $ct) {
echo "ID: " . substr($ct['Id'], 0, 12) . "\n";
echo "Image: " . $ct['Image'] . "\n";
echo "State: " . $ct['State'] . "\n";
echo "Names: " . implode(', ', $ct['Names']) . "\n";
echo str_repeat('-', 30) . "\n";
}
}ID: abc123def456 Image: nginx:latest State: running Names: /webserver ------------------------------ ID: 789012345678 Image: redis:alpine State: exited Names: /cache ------------------------------
Этот подход даёт максимум гибкости, но требует знания Docker REST API и обработки потоковых ответов.