Локальный веб-сервер для разработки с помощью 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/companies/usetech/articles/808025/


Интересные статьи

Интересные статьи

Стриминг. Это слово мы сегодня часто слышим. Большинство из нас ежедневно используют Netflix или YouTube. Это настолько стало частью жизни каждого, может быть, даже слишком для нас самих.Но люди редко...
С развитием информационных технологий профессия DS стала чрезвычайно популярна. Сейчас почти каждый может имея ПК и установленный на нем стандартный пакет Python, анализировать данные и строить на их ...
Компании, которые хотят улучшить качество обслуживания клиентов и расширить свои возможности в современном, мире, все больше полагаются на технологии NLP. Одним из наибол...
В этой статье мы подготовим окружение для запуска контейнеров в Windows 10 и создадим простое контейнеризированное приложение .NET Читать далее
В этом пошаговом руководстве я расскажу, как настроить Mikrotik, чтобы запрещённые сайты автоматом открывались через этот VPN и вы могли избежать танцев с бубнами: один раз настр...