Локальный веб-сервер для разработки с помощью Docker

—

от автора

Семен Тюреньков

Старший разработчик ГК Юзтех

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

Если это Senior Full Stack разработчик с опытом администрирования Linux, то установка и настройка конфигов Nginx, PHP-fpm, MariaDB для него не будут проблемой (а может и с Docker даже знаком?). 

Разработчик Middle уровня (особенно без опыта с backend) возможно пользуется одним из готовых решений под Windows/MacOS.

Junior верстальщик, в свою очередь, раньше не запускал приложение работающее на PHP на своем компьютере вообще, и вот-вот попробует в первый раз.

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

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

Я начал регулярно сталкиваться с этими проблемами, когда стал ответственным за релизы и работу команды. Это было сравнительно недавно.Docker к этому времени уже был широко известен и даже использовался другими отделами в компании, в том числе отделом DevOps на production, но для разработки в образе с прода не хватало компонентов. 

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

Установка

Хотя в результате вы получите практически одинаковые локальные окружения, установка на Windows, Linux и Mac несколько отличается.

Я расскажу о двух способах установки — для Windows и Mac. 

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

Docker Desktop

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

Немного теории в контексте веб-разработки своими словами (для более официального описания посмотрите документацию):

  • Образ — это конфигурация системы с программами-компонентами и их зависимостями. Образы могут быть как отдельные для каждого компонента (Nginx, PHP-fpm, MariaDB, Redis, Postfix и т.п.), так можно создать и единый образ сразу со всеми компонентами и самим веб-сайтом внутри.

  • Том — это постоянное хранилище данных, используемое контейнерами. Зачем это нужно? Все данные появляющиеся в контейнере удаляются при перезапуске, кроме тех, что заложены в образ. Чтобы сохранить какие-то данные, папки их содержащие нужно подключить как тома.

  • Контейнер — это изолированная система, созданная из образа и, возможно, с подключенными к ней томами, в котором выполняются нужные вам программы.

Дополнительная информация для Windows: для выполнения шагов в этой статье, ваш компьютер должен поддерживать WSL2. 

При установке Docker Desktop спросит хотите ли вы использовать WSL2 и вам следует согласиться. К тому же это рекомендуется и самим Docker.

Если вы пропустили этот шаг и Docker у вас давно, попробуйте включите поддержку WSL2 через настройки.

Хранение данных и проекта

Далее вам нужно подготовить, где вы будете хранить данные — папки с проектами, базы данных, nginx конфиги сайтов и SSL сертификаты.

На Windows можно настроить хранение проектов в файловой системе Windows, но обращения к файловой системе от веб-сервера будут очень медленные, поэтому в этой статье мы делаем через WSL2. 

Лично мне сложно работать с проектом, когда страницы грузятся долго. Лучшим решением будет хранить файлы в Linux подсистеме, поэтому для пользователей Windows рекомендую поставить еще одну подсистему через WSL2, например Debian или Ubuntu и хранить проекты там. 

Создайте или определите 4 папки в проекте, которые вы будете использовать в .env при установке Docker окружения:

Я рекомендую все хранить внутри той же директории, что и проект:

SITE_DOMAIN=”localhost” SITE_PATH=”$PWD”  SSL_PATH=”$PWD/ssl” DB_PATH=”$PWD/db” NGINX_CONF_PATH=”$PWD/docker/nginx/site-conf”

Установочный скрипт

Создайте файл install.sh, и все, с кем вы поделитесь проектом, смогут запустить его, чтобы автоматически скачать базовые образы, сбилдить проектные образы и создать тома из папок указанных в .env

#!/bin/bash  # скрипт прочитает .env файл и возьмет переменные  if [ -f .env ]; then     export $(cat .env | xargs) fi  # небольшая функция которая будет проверять существует ли том, и если нет, то создаст  # его из указанных в .env путей. Если захотите переопределить тома, их надо будет   # удалить в интерфейсе Docker Desktop или через командную строку  create_volume() {     local volume_name=$1     local volume_path=$2              echo "Creating volume $volume_name..." \         && docker volume create --driver local --opt type=none --opt device=$volume_path --opt o=bind $volume_name }  # и, наконец, основное тело скрипта, создает папки из указанных путей, на случай если они # еще не существуют. Затем входит в подпапки nginx и php-fpm для сборки образа (об  # этом расскажу еще чуть позже). И создает тома используя указанную выше функцию.  mkdir -p $SSL_PATH \ && mkdir -p $SITE_PATH \ && mkdir -p $DB_PATH \ && mkdir -p $NGINX_CONF_PATH \ && openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout $SSL_PATH/nginx.key -out $SSL_PATH/nginx.crt \ && sed -i ‘’ "s|server_name localhost;|server_name ${SITE_DOMAIN};|g" "${NGINX_CONF_PATH}/website.conf" \ && cd docker/nginx \ && sh builddocker.sh \ && cd ../php-fpm \ && sh builddocker.sh \ && create_volume ssl $SSL_PATH \ && create_volume site $SITE_PATH \ && create_volume db $DB_PATH \ && create_volume nginx-conf $NGINX_CONF_PATH

Docker Compose

Создайте файл docker-compose.yml, а также добавьте в .env еще одну переменную — 

MYSQL_ROOT_PASSWORD=password

Содержимое docker-compose файла ниже, о синтаксисе можете прочитать в документации, а я поясню, что именно будет выполняться.

  1. На основе указанных образов запустяться контейнеры

  2. В контейнеры пробросятся созданные нами ранее тома

  3. В контейнерах откроются порты

  4. Создается внутренняя сеть, которая упростит взаимодействие контейнеров друг с другом.

version: '3.1'  services:   db:     image: mariadb:11.2.2     container_name: db     restart: always     volumes:       - db:/var/lib/mysql     ports:       - 3306:3306     environment:       MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}     networks:       - localhost_net    php-fpm-custom:     image: php-fpm-custom:latest     container_name: php-fpm-custom     restart: always     volumes:       - www:/var/www     ports:       - 9000:9000     networks:       - localhost_net    redis:     image: redis:7.2.4     container_name: redis     restart: always     ports:       - 6379:6379     networks:       - localhost_net       expose:       - '6379'    nginx-custom:     depends_on:       - db       - php-fpm-custom     image: nginx-custom:latest     container_name: nginx-custom     restart: always     volumes:       - ssl:/etc/nginx/ssl       - www:/var/www       - nginx-conf:/etc/nginx/sites-enabled     ports:       - 80:80       - 443:443     networks:       localhost_net:         aliases:           - ${SITE_DOMAIN}  node:     image: node:21.5     container_name: node     restart: always     networks:       - localhost_net     command: "tail -f /dev/null"    phpmyadmin:     depends_on:       - db     image: phpmyadmin:5.2.1     container_name: phpmyadmin     restart: always     ports:       - 8081:80     environment:       - PMA_ARBITRARY=1     networks:       - localhost_net  networks:   localhost_net:  volumes:   ssl:     external: true   nginx-conf:     external: true   www:     external: true   db:     external: true 

В результате у нас будут контейнеры с Nginx, PHP-fpm, MariaDB, Redis, Phpmyadmin и NodeJS

Небазовые образы

Если по каким-то причинам базового образа недостаточно, вы можете создать папки в проекте с Dockerfile для вашего индивидуального образа. В моем случае мне потребовалось изменить некоторые настройки в Nginx и PHP-fpm образах, поэтому создайте папки nginx и php-fpm соответственно.

В каждой папке создайте Dockerfile и builddocker.sh скрипт.

Nginx Dockerfile

# Используем этот базовый образ FROM nginx:1.25.3  # Установим дополнительные пакеты, которые оказались нужны в этом контейнере RUN apt-get update \     && apt-get -y install lsb-release libssl-dev ca-certificates curl openssl   # Если хотите пробросить кастомный основной конфиг nginx, то это можно сделать здесь COPY nginx.conf /etc/nginx/nginx.conf  # Откроем порты EXPOSE 80 443  # Запустим Nginx как foreground процесс, чтобы контейнер не выключался CMD ["nginx", "-g", "daemon off;"]

Nginx builddocker.sh

Этот скрипт будет создавать образ с тегом :latest в вашей локальной системе, а также удалять старый образ, если вдруг вы обновите конфигурацию, который потом будет использовать docker compose.

#!/bin/bash  docker build -t nginx-custom --label nginx-custom . \ && docker image prune --force --filter='label=nginx-custom'

Основной конфиг nginx.conf

В моем случае я только увеличил client_max_body_size, возможно еще gzip и tcp директивы, не помню точно стандартный конфиг, но настройка конфига nginx под себя — это тема отдельных статей. 

user  nginx; worker_processes  auto;  error_log  /var/log/nginx/error.log notice; pid        /var/run/nginx.pid;   events {     worker_connections  1024; }  http {     include       /etc/nginx/mime.types;     default_type  application/octet-stream;      log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '                       '$status $body_bytes_sent "$http_referer" '                       '"$http_user_agent" "$http_x_forwarded_for"';      access_log  /var/log/nginx/access.log  main;      sendfile        on;     tcp_nodelay on;     tcp_nopush on;      keepalive_timeout  35;      gzip  on;     client_max_body_size 20M;     include /etc/nginx/conf.d/*.conf; }

PHP-fpm Dockerfile

# В моем случае я использую 8.1 т.к. WordPress ещё слабо поддерживает версии выше, но вы можете указать нужную вам версию PHP. FROM php:8.1-fpm  # добавим в PATH композер, который мы установим далее. ENV PATH="/root/.composer/vendor/bin:${PATH}"  # А вот здесь устанавливаем довольно много всего - рекомендуемые php-extensions для WordPress, Composer, WP-CLI,  RUN apt-get update \     && apt-get install -y libzip-dev unzip libpq-dev libmagickwand-dev libpng-dev libjpeg-dev libfreetype6-dev less \     && rm -rf /var/lib/apt/lists/* \     && docker-php-ext-install zip pdo_pgsql pgsql bcmath opcache \     && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \     && chmod +x /usr/local/bin/composer \     && curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \     && chmod +x wp-cli.phar \     && mv wp-cli.phar /usr/local/bin/wp \ && pecl install imagick && docker-php-ext-enable imagick \ && docker-php-ext-configure gd --with-freetype --with-jpeg \     && docker-php-ext-install -j$(nproc) gd  # Если хотите пробросить кастомный php.ini, то это можно раскомментировать # COPY php.ini /usr/local/etc/php/php.ini  # Expose port 9000 for PHP-FPM EXPOSE 9000  # Start PHP-FPM CMD ["php-fpm"]

Кастомизированный php.ini

Не буду публиковать весь огромный файл конфига, но стандартно я в нем меняю следующие настройки:

upload_max_filesize = 16M

post_max_size = 16M

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

Nginx конфиги для проектов

В указанной в .env папке для nginx конфигов сайтов создайте файл website.conf

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

В моем случае, конфиг для сайта на WordPress вот такой:

server {     listen 80;     server_name localhost;      return 301 https://$host$request_uri; }  server {     listen 443 ssl;     server_name localhost;      gzip on;     gzip_comp_level 5;     gzip_min_length 256;     gzip_proxied any;     gzip_vary on;      ssl_certificate /etc/nginx/ssl/nginx.crt;     ssl_certificate_key /etc/nginx/ssl/nginx.key;      root /var/www/site;     index index.php;      location / {             proxy_request_buffering off;             try_files $uri $uri/ /index.php?$args;     }      location ~ \.php$ {         include fastcgi_params;         fastcgi_pass php-fpm-custom:9000;         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;         fastcgi_index index.php;          fastcgi_buffer_size 128k;         fastcgi_buffers 4 256k;         fastcgi_busy_buffers_size 256k;     } }

Важно! Выбранные вами домены надо также указать в hosts родительской системы или прописать в DNS, если разворачиваете на сервере!

Например:

127.0.0.1 mysite.test

Запуск окружения

Если вы еще этого не сделали, то выполните

sh install.sh в вашем терминале.

Затем вы можете запустить окружение командой
docker-compose up -d

И остановить командой
docker-compose down

Алиасы

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

Привычные вам короткие команды потребуется запускать через docker exec или docker run, поэтому предлагаю использовать алиасы. Да и линтеры не будут выполняться если в родительской системе их нет и не созданы алиасы на контейнер.

Добавьте следующее содержимое в файл ~/.bashrc (если используете bash в качестве терминала):

alias composer='docker exec -it -w /var/www/site php-fpm-custom composer' alias phpcs='docker run --rm -it -v $(pwd):/app -w /app php-fpm-custom vendor/bin/phpcs' alias phpcbf='docker run --rm -it -v $(pwd):/app -w /app php-fpm-custom vendor/bin/phpcbf' alias wp='docker exec -it -w /var/www/site php-fpm-custom wp' alias node='docker run --rm -it -v $(pwd):/app -w /app node:21.5 node' alias npm='docker run --rm -it -v $(pwd):/app -w /app node:21.5 npm' alias npx='docker run --rm -it -v $(pwd):/app -w /app node:21.5 npx' alias yarn='docker run --rm -it -v $(pwd):/app -w /app node:21.5 yarn'

Алиас позволит вам привычно писать команду, но выполняться будет другая, указанная вами. То есть написав npm run build, вы фактически выполните команду docker run --rm -it -v $(pwd):/app -w /app node:21.5 npm run build

Если у вас в родительской системе уже стоят свои версии ПО и вы хотите оставить их доступными, вы можете изменить алиасы, добавив суффиксы в команды: -docker

Таким образом, вы сможете выполнять npm или npm-docker в зависимости от того, хотите ли вы использовать родительские программы или те, что находятся в контейнере.

IDE

Я использую VScode, поэтому могу рассказать только про эту IDE.

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

В моем случае это коснулось линтеров PHP и JS. 

В рекомендациях своего проекта я указал следующие:

"bradlc.vscode-tailwindcss",

"dbaeumer.vscode-eslint",

"bmewburn.vscode-intelephense-client",

"mtbdata.vscode-phpsab-docker"

Также при использовании подсистемы для хранения файлов необходимо будет установить WSL extension и запускать VScode в режиме WSL.

Заключение

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

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

Также эти образы можно использовать в пайплайнах GitLab для автоматических линтов и тестов MR.


ссылка на оригинал статьи https://habr.com/ru/articles/808025/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *