Полный текст — более 10 000 слов, после прочтения этой статьи вам может понадобиться чашечка кофе☕️~
авторАрахис ПЭА, пользователь интерфейса Baidu Ван, ACGer. Личный блог pea3nut.blog Профиль pea3nut.info
Если вы сохраните этот абзац, не стесняйтесь перепечатывать его. Пожалуйста, убедитесь, что этот абзац находится в верхней части статьи.
В этой статье представлено подробное и краткое введение в процесс докеризации всего сайта фронтенд-программистом, который совсем не понимает Docker. Содержание в основном включает в себя:
- Основные понятия докера
- Реальный процесс миграции сайта:
- статический сайт
- Сайт Nodejs (Экспресс)
- WordPress(PHP)
- Некоторые необходимые навыки: запуск, общая оболочка
В статье будут описаны все технологические стеки (Github CI, обратный прокси Nginx, docker-compose), используемые в процессе использования Docker, и никогда не будет «см.:http://xxx" кидаю ссылки
Не нужно сверяться с другими документами, просто прочитайте один!
Какие текущие проблемы
Ручное развертывание слишком дорого
Автор поддерживает ряд веб-сайтов, в том числе:
- мое резюме:pea3nut.info, одностраничное приложение SPA, созданное с помощью Vuejs, чисто статическое
- мой блог:pea3nut.blog, используя известную сборку WordPress (PHP+Apache+MySQL)
- Проект с открытым исходным кодом - Pxer:pxer.pea3nut.org, официальный сайт построен на Nodejs + Express SSR
И каждый раз, когда я хочу изменить содержимое веб-сайта, это очень хлопотно. Возьмем в качестве примера знакомый чистый статический сайт, процесс модификации выглядит следующим образом:
- Скачать: Загрузите код с Github, затем локально
npm install
- Разработка:
npm run dev
Измените код локально, протестируйте - Скомпилировать:
npm run build
Скомпилируйте с помощью Webpack для создания статических ресурсов - Загрузить: Откройте программное обеспечение FTP и загрузите файл замены.
- Тест: посмотрите, нормально ли сайт работает в Интернете.
- commit: зафиксировать код на Github
Даже если я просто исправлю опечатку, это займет десять минут
Слишком много сайтов, слишком много изменений, и каждое изменение, каким бы маленьким оно ни было, обременительно. Это просто заставляет меня чувствовать, что я поддерживаю крупномасштабный проект с 10 000 уровней запросов в секунду.
Служба не работает, я не понимаю Linux и не могу устранить неполадки
Недавно я обнаружил, что мой процесс MySQL всегда зависает, что приводит к зависанию всех сайтов, зависящих от MySQL.
не знаю почему, раньше было нормально
Я попытался перезапустить процесс, перезапустить сервер и получить журнал ошибок Baidu, но это не сработало.
Ну, на самом деле я мало что знаю о Linux или MySQL, я просто хочу использовать их для создания сайта на WordPress. И в последнее время всегда были проблемы, которые заставили меня осознать:
Я не только должен поддерживать сайт, я также должен поддерживать окружающую среду.
Это слишком сложно для фронтенда, а установка nvm уже мой предел. MySQL зависает без причины, у меня просто нет возможности обнаружить раз, два, три, четыре и исправить
Мне нужно не только обеспечить локальную работу сайта, но и развернуть его на удаленном VPS для стабильной работы. . .
Перезагрузка не работает. . . Тогда просто переустановите систему
Однако из-за построения множества площадок среда VPS-сервера довольно сложная (возможно, именно поэтому MySQL зависает), а в конфигурационном файле Apache всего сотни строк. Стоимость миграции, связанная с переделкой системы, одна только мысль об этом требует всего моего мужества.
Новое техническое решение - Докер
Резюмируя следующие вопросы:
- Стоимость ручного развертывания слишком высока, а исправлять опечатки очень хлопотно.
- Сервер имеет «грязную» среду из-за накопления времени
- Стоимость переустановки системы слишком высока, а мигрировать сложно
И Докер, это я все решаюSCP-500 Панацея!
Так как же Docker это делает?
Образы и контейнеры
В Docker есть две важные концепции.
Одним из них является контейнер: контейнер особенно похож на виртуальную машину, и в контейнере работает полная операционная система. Nodejs можно установить в контейнер и запуститьnpm install
, может делать все, что может ваша текущая операционная система
Другой — образ: образ — это файл, который используется для создания контейнера. Если вы установили операционную систему Windows, то образ Docker особенно похож на файл «Win7 pure version .rar».
Это все, что вам нужно знать об основах Docker. Это так просто
Кстати, в Docker мы обычно называем реальную ОС, которую вы сейчас используете, «Хост».
Установить Докер
Установить Docker на свой компьютер так же просто, как установить VS Code
Если вы используете компьютер с Windows, вам необходимо приобрести версию, поддерживающую виртуализацию. Например, Win10 Professional Edition, Win10 Home Edition неприемлема.
После установки Docker вы можете открыть красивое окно Docker. На самом деле это окно бесполезно, обычно мы управляем Docker через командную строку CLI, так же как и Git
запустить докер
Затем мы создаем сервер Nginx, на котором могут размещаться статические файлы.
Контейнеры запускают программы, а откуда берутся контейнеры? Контейнеры создаются из образов. Откуда взялось зеркало?
Образ упакован через Dockerfile, который очень похож на наш внешний интерфейс.package.json
документ
Итак, создайте отношения как:
Dockerfile: 类似于“package.json”
|
V
Image: 类似于“Win7纯净版.rar”
|
V
Container: 一个完整操作系统
Создать файл
мы создаем каталогhello-docker
, в каталоге, чтобы создатьindex.html
файл, содержание:
<h1>Hello docker</h1>
Затем создайте новый в каталогеDockerfile
файл, содержание:
FROM nginx
COPY ./index.html /usr/share/nginx/html/index.html
EXPOSE 80
На этом этапе ваша файловая структура должна быть:
hello-docker
|____index.html
|____Dockerfile
образ пакета
Файл создан, теперь мы можемDockerfile
Создайте образ!
В командной строке (Windows предпочитает PowerShell) введите:
cd hello-docker/ # 进入刚刚的目录
docker image build ./ -t hello-docker:1.0.0 # 打包镜像
Уведомление! Docker 中的选项(Options)放的位置非常有讲究,
docker —help image
а такжеdocker image —help
это совсем другая команда
docker image build ./ -t hello-docker:1.0.0
означает: на основе пути./
(текущий путь) упаковать образ, имя образаhello-docker
, номер версии1.0.0
. Команда автоматически найдетDockerfile
упаковать изображение
Советы: вы можете использовать
docker images
для просмотра существующих зеркал на этой машине
Неудивительно, что вы должны получить следующий результат:
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM nginx
---> 5a3221f0137b
Step 2/3 : COPY ./index.html /usr/share/nginx/html/index.html
---> 1c433edd5891
Step 3/3 : EXPOSE 80
---> Running in c2ff9ec2e945
Removing intermediate container c2ff9ec2e945
---> f6a472c1b0a0
Successfully built f6a472c1b0a0
Successfully tagged hello-docker:1.0.0
Вы можете видеть, что он запускает содержимое в Dockerfile, теперь мы просто его дизассемблируем:
-
FROM nginx
: В зависимости от того, какое зеркало -
COPY ./index.html /usr/share/nginx/html/index.html
: поместить хост в./index.html
файл копируется в контейнер/usr/share/nginx/html/index.html
-
EXPOSE 80
: Контейнер открывает порт 80 для внешнего мира.
запустить контейнер
Мы только что создали образ с помощью Dockerfile. Теперь, когда у нас есть образ, нам нужно создать контейнер на его основе:
docker container create -p 2333:80 hello-docker:1.0.0
docker container start xxx # xxx 为上一条命令运行得到的结果
затем откройте в браузере127.0.0.1:2333
, вы должны сами увидеть, что вы только что написалиindex.html
содержание
В первой команде выше мы используемdocker container create
Для создания основыhello-docker:1.0.0
Контейнер изображения, используя-p
указать привязку к порту - поставить80
Порт привязан к хосту2333
порт. После выполнения команды будет возвращен идентификатор контейнера.
Вторая команда — запустить контейнер
После запуска вы можете получить доступ к2333
порт для доступа внутрь контейнера80
Эффект порта
Советы: вы можете использовать
docker container ls
для просмотра запущенных в данный момент контейнеров
Когда контейнер запущен, вы можете войти в контейнер с помощью следующей команды:
docker container exec -it xxx /bin/bash # xxx 为容器ID
Принцип собственно в том, чтобы запустить контейнер/bin/bash
, то можно пройтиbash shell
взаимодействует с контейнером. как будто удаленно подключен к SSH
что случилось
Подытожим, что произошло:
- Написать Dockerfile
- использовать
docker image build
будущееDockerfile
упаковано как изображение - использовать
docker container create
создать контейнер из образа - использовать
docker container start
запустить созданный контейнер
Хотя это очень просто, разве вы не чувствуете, что «мир огромен, есть много дел, и вы можете делать все, что хотите»?
Перенос статического сайта
Далее мы фактически перенесем чистый статический одностраничный сайт SPA, написанный на Vuejs:
- URL-адрес:pea3nut.info
- Исходный код:github/pea3nut-info
что я собираюсь делать
Перед миграцией Docker, если я хочу обновить содержимое онлайн-сайта, мне необходимо:
- местный
npm run build
Упаковка и вывод статических файлов - Вручную загрузить на сервер через FTP
-
git push
Обновите исходный код Github
Немного проблем, поэтому я планирую изменить это:
- воплощать в жизнь
git push
- Автоматически определять наличие обновлений кода на github и автоматически упаковывать образ Docker.
- После компиляции CI подключитесь к VPS по SSH, удалите существующий контейнер и создайте новый контейнер с новым образом.
И польза от этого:
- Больше никаких ручных FTP-загрузок
- Когда я выполняю простые операции, такие как исправление опечаток, я могу быть освобожден от тестирования. сразу после смены
git push
, вместо того, чтобы быть локальнымnpm run build
КИ в Github
Во-первых, заставить Github упаковывать образ каждый раз, когда я обновляю код.
На Github доступны бесплатные ресурсы CI, этоTravis CI
Добавить в корневой каталог проекта.travis.yml
файл со следующим содержимым:
language: node_js
node_js:
- "12"
services:
- docker
before_install:
- npm install
script:
- npm run build
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- docker build -t pea3nut/pea3nut-info:latest .
- docker push pea3nut/pea3nut-info:latest
Содержимое файла очень простое, просто используйтеnpm run build
После компиляции статического вывода упакуйте и передайте зеркало на удаленный сервер. Есть несколько вещей, о которых стоит поговорить подробно:
- Чтобы иметь возможность загрузить изображение на сервер, вам необходимо
hub.docker.com
Зарегистрируйте учетную запись в , затем замените код вpea3nut/pea3nut-info:latest
для用户名/包名:latest
Только что - После использования Github для входа в Travis CI нажмите знак + плюс слева, чтобы добавить собственный репозиторий Github, вам нужно перейти к настройке, чтобы добавить его в проект.
DOCKER_USERNAME
а такжеDOCKER_PASSWORD
переменные окружения. Это гарантирует, что мы можем войти в систему тайно, не будучи другими пользователями Docker Hub, чтобы увидеть ваш пароль. Как показано ниже
Затем вам нужно добавить Dockerfile, чтобы описать, как упаковать образ Docker.
согласно с.travis.yml
Последовательность команд при упаковке образаnpm run build
Он был реализован, и результаты проекта уже доступны. Не нужно запускать в контейнере Dockernpm install
а такжеnpm run build
И так далее, просто скопируйте файл напрямую:
FROM nginx
COPY ./dist/ /usr/share/nginx/html/
EXPOSE 80
Примечание: несмотря на простоту процесса, очереди очень длинные, поэтому рекомендуется выполнять несколько тестов локально.
git push
Если ваш скомпилированный статический сайт также является одностраничным приложением SPA, вам необходимо добавить дополнительную конфигурацию Nginx, чтобы обеспечить доступность запросов.index.html
. Ниже то, что я написалvhost.nginx.conf
Файл конфигурации Nginx, перенаправляйте все запросы, которые не обращаются к файлам, на/index.html
:
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
proxy_set_header Host $host;
if (!-f $request_filename) {
rewrite ^.*$ /index.html break;
}
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Затем добавьте новую строку в Dockerfile, чтобы добавитьvhost.nginx.conf
Документ копирования в контейнер/etc/nginx/conf.d/pea3nut-info.conf
Пусть nginx способна прочитать файл конфигурации:
FROM nginx
COPY ./dist/ /usr/share/nginx/html/
+ COPY ./vhost.nginx.conf /etc/nginx/conf.d/pea3nut-info.conf
EXPOSE 80
затем выполнитьgit push
После этого вы можете увидеть результат компиляции CI в Travis CI. Если с компиляцией все в порядке, на пульте действительно естьpea3nut/pea3nut-info:latest
это зеркало. Вы можете попробовать это локально, чтобы увидеть, нормально ли работает зеркало:
docker image pull pea3nut/pea3nut-info:latest
docker container create -p 8082:80 pea3nut/pea3nut-info:latest
docker container start xxx # xxx 为上一条命令执行的返回值
После завершения операции браузер получает доступ127.0.0.1:8082
Вы должны увидеть эффект!
Затем вы можете войти на удаленный сервер VPS, установить Docker и выполнить ту же команду. Затем получите доступ к общедоступному IP + номеру порта 8082 удаленного сервера VPS, вы должны увидеть тот же эффект, что и локальный.
Советы: Забыли, как установить Docker на VPS? В разделе «Установка Docker» выше вам может понадобиться установка Linux
curl https://get.docker.com/ > install-docker.sh # 下载安装脚本 sh install-docker.sh # 执行安装脚本
Обратный прокси Nginx
Примечание. Следующие операции выполняются на вашем удаленном сервере VPS, а не на локальном компьютере или в контейнере.
В настоящее время мы привязали контейнер к порту 8082, но пользователи не могут вручную ввести порт 8082 для доступа в Интернете. И если контейнер напрямую связан с портом 80, хотя пользователь может получить к нему прямой доступ без добавления порта, что, если есть второй контейнер или несколько контейнеров?
В это время вам нужно запустить Nginx на хост-компьютере, который монополизирует порт 80, а затем распределить запрос на отвечающий контейнер в соответствии с доменным именем. Как показано ниже:
Эта схема называется «обратный прокси».
Войдите на сервер VPS и установите Nginx. Поскольку я Ubuntu, я могу использоватьapt
Установить.其他 Linux 发行版可以百度下安装方法,通常2行内可以搞定:
apt update # 更新软件包
apt-get install nginx # 安装 Nginx
systemctl status nginx # 查看 Nginx 状态
На этом этапе вы можете получить доступ к общедоступному IP-адресу VPS через браузер локально и увидеть страницу приветствия Nginx.
Затем на VPS-сервере/etc/nginx/conf.d/
создатьvhost.conf
файл со следующей конфигурацией:
server {
listen 80;
server_name pea3nut.info;
location / {
proxy_pass http://127.0.0.1:8082;
}
}
Конфигурация означает мониторинг трафика с порта 80, если имя домена доступаpea3nut.info
(замените своим доменным именем), все перенаправляется наhttp://127.0.0.1:8082
середина
После завершения настройки перезапустите сервер Nginx. Если Ubuntu может использоватьsystemctl restart nginx
Команда, немного отличающаяся для разных дистрибутивов Linux
После успешной настройки посетитеpea3nut.info
увидит иVP S公网IP:8082
тот же эффект
обновить сайт
После перехода на Docker процесс, который я хочу изменить, становится следующим:
- Локальная модификация завершена, выполнить
git push
- Дождитесь завершения компиляции CI
- Войдите на сервер VPS и выполните:
docker image pull pea3nut/pea3nut-info:latest
docker container create -p 8082:80 pea3nut/pea3nut-info:latest # 得到 yyy
docker container stop xxx # xxx 为当前运行的容器ID,可用 docker container ls 查看
docker container start yyy # yyy 第二条命令返回值
Команда все еще немного длинная? Мы будем дополнительно оптимизировать его ниже
Миграция сайта Nodejs (Express)
Далее мы фактически перенесем сайт Express SSR, написанный Nodejs.
- URL-адрес:pxer.pea3nut.org
- Исходный код:github/pxer-homepage
что я собираюсь делать
Веб-сайт использует шаблоны Ejs для отображения страниц. Перед миграцией Docker, если я хочу обновить содержимое онлайн-сайта, мне необходимо:
- Измените Ejs или другие файлы локально
- Вручную загрузить на сервер через FTP
- Перезапустите процесс Nodejs на стороне сервера. Если есть какое-либо изменение зависимости пакета npm, его необходимо выполнить вручную на сервере VPS.
npm install
-
git push
Обновите исходный код Github
Это немного громоздко, поэтому я собираюсь изменить его следующим образом:
- воплощать в жизнь
git push
- Автоматически определять наличие обновлений кода на github и автоматически упаковывать образ Docker.
- После компиляции CI подключитесь к VPS по SSH, удалите существующий контейнер и создайте новый контейнер с новым образом.
И польза от этого:
- Больше никаких ручных FTP-загрузок
- Нет необходимости вручную поддерживать среду выполнения Nodejs на сервере.
воплощать в жизнь
Особой разницы между конкретным процессом и обработкой статических сайтов нет, это не более чем:
- Написать Dockerfile
- Автоматически упаковывать изображения во время CI
- Добавьте обратный прокси Nginx к VPS
Я не буду повторяться в этот раз, конкретная конфигурация может ссылаться на соответствующие файлы в проекте.
Советы. Возможно, вы нашли файл Dockerfile в
ENTRYPOINT
Команда должна указывать процесс переднего плана. Если ваше приложение Nodejs поддерживается с помощью PM2, вам необходимо заменитьpm2 start app.js
дляpm2-docker app.js
docker-compose
Когда миграция сайта Nodejs завершена, у нас уже есть 2 контейнера, работающих на нашем VPS-сервере. Каждое обновление изображения должно выполняться вручнуюdocker container create
Переносить кучу параметров может быть обременительно, особенно когда в будущем контейнеров становится все больше и больше. И вот, настала очередьdocker-compose
Появился~
docker-compose — это инструмент управления Docker, официально предоставляемый Docker. Если вы установили Docker через установочный пакет Docker для рабочего стола, он по умолчанию установит для вас docker-compose. Вы можете попробовать следующую команду:
docker-compose --help
Если вы работаете в Linux, вы можете установить docker-compose с помощью следующей команды:
curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
Docker-Compose похож на Docker, и ему нужен только один файл для запуска. Основная функция Docker-Compose - это сэкономить от того, чтобы набрать так много команд Docker
Создайте каталог, затем создайте в каталогеdocker-compose.yml
, содержание следующее:
version: "3.7" # 这个是配置文件的版本,不同的版本号声明方式会有细微的不同
services:
info:
container_name: pea3nut-info
image: pea3nut/pea3nut-info:latest
ports:
- "8082:80"
restart: on-failure
Затем введите следующую команду в каталоге для запуска службы:
docker-compose up info
docker-compose поможет нам автоматически извлекать образы, создавать контейнеры и80
Сопоставление портов с хостом8082
порт.restart
Поле также просит docker-compose перезапустить контейнер, когда обнаруживает, что контейнер неожиданно зависает, подобно pm2, поэтому вам больше не нужно использовать pm2 внутри контейнера.
Если вы хотите обновить образ для создания нового контейнера, просто:
docker-compose pull info
docker-compose stop info
docker-compose rm info
docker-compose up -d info # -d 代表后台运行
Автор открыл исходный код своего метода развертывания веб-сайта, вы можете обратиться кgithub/pea3nut-hub
Миграция сайта WordPress (Apache + PHP + MySQL)
Далее мы фактически перенесем сайт WordPress.
- URL-адрес:pea3nut.blog
- Исходный код: закрытый
Возможно, вы также обнаружили очень большую разницу между этим сайтом и другими сайтами - его исходный код и данные не могут быть обнародованы.
Когда мы упаковывали изображение раньше, мы напрямую вставляли код в изображение. Эта схема здесь явно неприемлема, есть две проблемы:
- Я не хочу раскрывать файлы данных MySQL и содержимое веб-сайта (например, изображения). Если они упакованы в образ, любой может
docker image pull
Загрузите на зеркало, а затем получите файлы в зеркале - При удалении контейнера сохраненные данные MySQL будут потеряны.
Volume
Docker предоставляет что-то под названием Volume, которое может «связать» папку в контейнере и хосте, и любые изменения файлов будут синхронизированы. Итак, я могу смонтировать весь каталог сайта и каталог MySQL как том. Таким образом, при удалении контейнера все файлы данных и исходный код сохраняются.
Создавайте локально./blog/mysql-data
Каталог для хранения данных MySQL, сборка./blog/wordpress
В каталоге хранится исходный код WordPress. затем изменитьdocker-compose.yml
следующим образом:
version: "3.7"
services:
info:
container_name: pea3nut-info
image: pea3nut/pea3nut-info:latest
ports:
- "8082:80"
restart: on-failure
+ blog:
+ container_name: pea3nut-blog
+ image: tutum/lamp:latest
+ ports:
+ - "8081:80"
+ volumes:
+ - ./blog/mysql-data:/var/lib/mysql
+ - ./blog/wordpress:/app
+ restart: on-failure
Видно, что в этот раз образ вообще не запакован, а используется напрямуюtutum/lamp
Отразите предоставленную среду LAMP (Linux + Apache + MySQL + PHP), затем поместите каталог данных MySQL/var/lib/mysql
и исходный каталог/app
Просто загрузите все это
Советы: Через Volume мы как раз решили проблему деплоя, а как разрабатывать локально, а потом синхронизировать исходники на сервер? Использование FTP, конечно, возможно, но немного более громоздко. Фактически, вы можете создать свой собственный сервер Git! Видеть:pea3nut.blog/e127
Поговорки и другие трюки
- Чтобы настроить загрузку:Ubuntu 18.04 позволяет rc.local настроить загрузку
- Китайские файлы искажаются после миграции:Решить проблему, из-за которой имена файлов на китайском языке отображают искаженные символы в Linux.
исходный код
Миграция статического сайта (резюме автора):
- Интернет-адрес: [pea3nut.info][pea3nut.info]
- Исходный код:GitHub.com/pea3nut/pea…
- Файл конфигурации CI:GitHub.com/pea3nut/pea…
- Докерфайл:GitHub.com/pea3nut/pea…
- докер-составить:GitHub.com/pea3nut/pea…
- Конфигурация обратного прокси Nginx:GitHub.com/pea3nut/pea…
Миграция сайта на PHP (блог автора):
- Интернет-адрес: [pea3nut.blog][pea3nut.blog]
- Докерфайл:GitHub.com/pea3nut/pea…
- докер-составить:GitHub.com/pea3nut/pea…
- Конфигурация обратного прокси Nginx:GitHub.com/pea3nut/pea…
Миграция Nodejs (официальный сайт Pxer):
- Интернет-адрес:pxer.pea3nut.org
- Исходный код:Github.com/pea3nut/ Обучение О ...
- Файл конфигурации CI:GitHub.com/pea3nut/training о…
- Докерфайл:GitHub.com/pea3nut/training о…
- докер-составить:GitHub.com/pea3nut/pea…
- Конфигурация обратного прокси Nginx:GitHub.com/pea3nut/pea…
разное:
- Репозиторий, который объединяет конфигурации развертывания:GitHub.com/pea3nut/pea…
постскриптум
привет вотАрахис ПЭА. Спасибо, что прочитали эту статью, большое спасибо!
За два месяца до написания этой статьи я решил перевести весь свой сайт на Docker. Две недели назад я решил организовать этот процесс в виде поста в блоге. Я не ожидала, что так долго буду писать и писать десятки тысяч слов
Честно говоря, я немного волновался в процессе написания. С одной стороны, я действительно просто фронтенд, и мое понимание Docker остается только в использовании, и я беспокоюсь о том, смогу ли я действительно написать учебник по Docker «за границей»; я также очень беспокоюсь о том, будет ли люди действительно хотят провести время за чтением технической статьи с десятками тысяч слов в сегодняшней сетевой среде.
Но Docker действительно работает хорошо. После того, как весь сайт был докеризован, когда я снова мигрировал сервер, я обнаружил, что могу выполнить миграцию всей среды с помощью десяти строк команд, что заняло десять минут! Это «освежающее» чувство также мотивирует меня написать эту статью — я хочу поделиться этим освежением с вами перед экраном. Надеюсь, вам тоже нравится Docker ~ ❤️