В этой статье я расскажу как можно быстро поднять связку PHP + MySql используя Docker на компьютере без установки чего-либо на хост (docker, конечно, уже должен быть установлен)

Требования

  • легкость в использовании
  • возможность запуска нескольких сайтов
  • легкость развертывания нового сайта
  • общий сервер базы данных для всех сайтов
  • запуск одной строкой
  • доступ к исходному коду сайта
  • возможность использования разных версий PHP

В результате потребуется запустить общие для всех сайтов сервисы (это можно поставить в автозапуск)

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

Все исходные файлы доступны на GitHub

Общие сервисы

Все общие сервисы будут лежать в отдельной директории и запускаться одним docker-compose файлом

Используемые docker сети

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

1
2
3
4
5
networks:
  proxy:
    driver: bridge
  lamp:
    driver: bridge

Контейнер разрабатываемого сайта должен иметь открытый 80 порт и уметь отдавать HTML страницы. Так же он должен быть добавлен в сеть proxy что бы nginx-proxy мог получить доступ к контейнеру к которому проксирует трафик.

В сеть lamp добавляем бекендовые контейнеры, в данном случае это только MySql. Однако если потребуется еще какие-либо сервисы, например Rabbit-MQ, Redis или Centrifugo то их легко добавить аналогично MySql.

Так же в сеть lamp добавляем контейнеры, которым требуется доступ к бекендовым сервисам. В данном случае это PhpMyAdmin

Proxy

Важной частью сервиса является Nginx Proxy

Он позволяет легко проксировать запросы с одного порта (в нашем случае 80) на другие контейнеры. Притом контейнеры он выбирает на основании переменной окружения VIRTUAL_HOST в контейнере назначения. Выполняет он это автоматические т.к. он подключается к хостовому докеру через проброшенный сокет файл.

Так же добавим доступ к сети proxy, внутри которой он и будет проксировать.

1
2
3
4
5
6
7
8
9
services:
  proxy:
    image: jwilder/nginx-proxy
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      - proxy

MySql

Используем готовый образ MySql, можно выбрать необходимую версию.

Я буду использовать последнюю версию

1
2
3
4
5
6
7
8
9
services:
  mysql:
    image: mysql
    volumes:
      - ./data/mysql/data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
    networks:
      - lamp

В этой конфигурации я указал рутовый пароль базы данных используя переменную окружения MYSQL_ROOT_PASSWORD и папку, где будут храниться сами данные. Контейнер подключим к сети lamp т.к. это бекендовый сервис.

PhpMyAdmin

Так же используем готовый образ PhpMyAdmin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
services:
  phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    environment:
      - VIRTUAL_HOST=phpmyadmin.local
      - PMA_HOST=mysql
      - PMA_USER=root
      - PMA_PASSWORD=root
    networks:
      - proxy
      - lamp

В переменной окружения VIRTUAL_HOST указываем домен, по которому будет доступен PhpMyAdmin. Это требуется что бы nginx-proxy перенаправил запрос к этому домену к этому контейнеру

Контейнер подключим и к сети proxy т.к. к этому контейнеру потребуется доступ из браузера и к сети lamp т.к. ему потребуется доступ к базе данных.

Базовый контейнер PHP

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

Все необходимые файлы для образа поместим в папку php, в ней же создадим файл Dockerfile с инструкциями по сборке, например такими:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
FROM php:7.3.2-fpm

RUN apt-get update && apt-get install -y \
        libzip-dev \
        zip \
	&& docker-php-ext-configure zip --with-libzip \
	&& docker-php-ext-install zip \
	&& docker-php-ext-install gd \
	&& docker-php-ext-install mysqli

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

Так же я создал bash скрипт для сборки образа с нужным тегом:

1
2
3
4
5
cd php
touch build.sh
chmod 755 build.sh
echo "#\!/bin/bash" >> build.sh
echo "docker build . -t lamp-php" >> build.sh

Это дает возможность не вспоминать необходимое имя тега а просто выполнить скрипт с говорящим названием

И сразу же соберем базовый образ выполнив этот скрипт

./build.sh

Запуск общих сервисов

Осталось настроить сети Настройка docker-compose.yml завершена общих сервисов завершена, можно их запускать:

docker-compose up

Эта команда может занять некоторое время пока скачивает и собирает образы и вскоре на экране отобразится отчет о создании сетей и запуске контейнеров

1
2
3
4
5
Creating network "lamp_proxy" with driver "bridge"
Creating network "lamp_lamp" with driver "bridge"
Creating lamp_mysql_1      ... done
Creating lamp_proxy_1      ... done
Creating lamp_phpmyadmin_1 ... done

А так же вывод работы контейнеров, который будет выглядеть примерно так:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Attaching to lamp_mysql_1, lamp_proxy_1, lamp_phpmyadmin_1
mysql_1       | 2020-07-17 09:20:54+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.21-1debian10 started.
mysql_1       | 2020-07-17 09:20:54+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
mysql_1       | 2020-07-17 09:20:54+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.21-1debian10 started.
mysql_1       | 2020-07-17T09:20:54.840913Z 0 [Warning] [MY-011070] [Server] 'Disabling symbolic links using --skip-symbolic-links (or equivalent) is the default. Consider not using this option as it' is deprecated and will be removed in a future release.
mysql_1       | 2020-07-17T09:20:54.841010Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.21) starting as process 1
mysql_1       | 2020-07-17T09:20:54.849671Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
phpmyadmin_1  | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.24.0.3. Set the 'ServerName' directive globally to suppress this message
phpmyadmin_1  | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.24.0.3. Set the 'ServerName' directive globally to suppress this message
phpmyadmin_1  | [Fri Jul 17 09:20:55.484132 2020] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.38 (Debian) PHP/7.4.6 configured -- resuming normal operations
phpmyadmin_1  | [Fri Jul 17 09:20:55.484179 2020] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
proxy_1       | WARNING: /etc/nginx/dhparam/dhparam.pem was not found. A pre-generated dhparam.pem will be used for now while a new one
proxy_1       | is being generated in the background.  Once the new dhparam.pem is in place, nginx will be reloaded.
proxy_1       | forego     | starting dockergen.1 on port 5000
proxy_1       | forego     | starting nginx.1 on port 5100

Посмотрим список запущенных контейнеров командой docker ps в другом терминале

1
2
3
4
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                 NAMES
ff89f0056bf2        mysql:5.7                      "docker-entrypoint.s…"   25 hours ago        Up 22 seconds       3306/tcp, 33060/tcp   lamp_mysql_1
3cd6df98ca07        jwilder/nginx-proxy            "/app/docker-entrypo…"   45 hours ago        Up 22 seconds       0.0.0.0:80->80/tcp    lamp_proxy_1
b4fe66dd1b1b        phpmyadmin/phpmyadmin:latest   "/docker-entrypoint.…"   45 hours ago        Up 22 seconds       80/tcp                lamp_phpmyadmin_1

В списке должны быть все три контейнера, конечно же CONTAINER ID у них будет другой.

Если хотя бы одного контейнера не хватает, потребуется искать в выводе команды docker-compose up ошибки, возникшие в этом контейнере. После исправления ошибки нужно остановить и заново запустить сбоку docker-compose up

После успешного запуска можно будет запускать эти контейнеры командой docker-compose start которая запустит контейнеры и завершится, освободив терминал. Остановить контейнеры можно будет командой docker-compose stop в этой же директории.

Развертывание проекта

К этому моменту все общие контейнеры запущены и пора переходить к развертыванию проекта. Все проекты развертываются одинаково, пример имеется в репозитории в директории example и всего лишь выполняет подключение к базе данных.

У каждого сайта будет следующая структура директорий:

  • nginx настройки nginx (как ни странно)
  • php настройки php (внезапно)
  • html файлы самого сайта. Именно в этой директории будет находиться корневой index.php

Снова сети

В docker-compose.yml сайта будем использовать три сети:

  • frontend сеть синоним сети proxy. Именно к включенным к этой сети контейнерам сможет проксировать nginx-proxy
  • в lamp сети находятся общие сервисы, в данном случае база данных
  • backend это внутренняя сеть этого сайта, используется что бы другие сайты не могли получить доступ к этому сайту, не засоряли пространство друг друга.

Nginx

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

Так же добавляем этот контейнер к сети, в которой работает nginx-proxy и бекенд этого сайта

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
services:
  nginx:
    image: nginx
    environment:
      - VIRTUAL_HOST=example.local
    depends_on:
      - php
    volumes:
      - ./nginx/default.nginx:/etc/nginx/conf.d/default.conf
      - ./html/:/var/www/html/
    networks:
      - frontend
      - backend

Согласно такой конфигурации, конфиг сайта должен находится в директории nginx и называться default.nginx

Пункт depends_on указывает что контейнер надо запустить после запуска указанных контейнеров. Если этого не сделать, то при старте nginx может сразу завершить работу т.к. контейнер с php мог еще не запуститься (что случается практически всегда).

PHP

Наконец-таки занимаемся самим php

Подключим локальный php.ini из директории php и файлы проекта.

1
2
3
4
5
6
7
8
9
services:
  php:
    image: lamp-php:latest
    volumes:
      - ./php/php.ini:/usr/local/etc/php/php.ini
      - ./html/:/var/www/html/
    networks:
      - backend
      - lamp

Автозапуск через systemd

Создаем файл сервиса:

sudo touch /etc/systemd/system/lamp.service

И выставляем ему права доступа:

sudo chmod 664 /etc/systemd/system/lamp.service

И поместим в него все необходимые настройки

sudo vim /etc/systemd/system/lamp.service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Unit]
Description=LAMP service
After=docker.target

[Service]
WorkingDirectory=/srv/lamp
Type=single
ExecStart=/usr/bin/docker-compose up

[Install]
WantedBy=multi-user.target

В файле сервиса указаны

  • Description - название сервиса
  • Type=single - systemd запустит процесс и будет считать что пока он жив, сервис запущен. Для остановки сервиса он отправит процессу сигнал завершения
  • ExecStart - команда для запуска docker-compose. Используется команда up потому как в этом случае процесс не будет завершаться пока выполяются контейнеры
  • WorkingDirectory - устанавливаем директорию, где хранятся файлы общих сервисов, в моем случае это /srv/lamp. Именно в этой директории храниться docker-compose.yml общих сервисов

После этого требуется обновить конфигурацию systemd:

sudo systemctl deamon-reload

И уже можно запускать сервис:

sudo systemctl start lamp

То, что сервис запустился, можно проверить, посмотрев список запущеных docker-контейнеров:

docker ps

Результат этой команды будет примерно таким:

1
2
3
4
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                 NAMES
408b01017fd5        mysql:5.7                      "docker-entrypoint.s…"   10 days ago         Up 30 minutes       3306/tcp, 33060/tcp   lamp_mysql_1
0d19774097c7        phpmyadmin/phpmyadmin:latest   "/docker-entrypoint.…"   10 days ago         Up 30 minutes       80/tcp                lamp_phpmyadmin_1
1cc5aaa40f9b        jwilder/nginx-proxy            "/app/docker-entrypo…"   10 days ago         Up 30 minutes       0.0.0.0:80->80/tcp    lamp_proxy_1

По завершению работы LAMP сервис можно остановить:

sudo systemctl stop lamp

Также можно настроить автозапуск сервиса:

sudo systemctl enable lamp

Итоги

Таким образом легко можно поднять сервер разработки

Если сайт требует особого сервера или версии базы данных, их можно подключить в docker-compose файле этой сайта - это докер, комбинировать можно почти как угодно!

Все исходные файлы доступны на GitHub