Докер — универсальный способ доставки приложений на машины (локальный компьютер или удаленные сервера) и их запуска в изолированном окружении.
Вспомните, когда вам приходилось собирать программы из исходников. Этот процесс включает в себя следующие шаги:
- Установить все необходимые зависимости под вашу операционную систему (их список ещё надо найти)
- Скачать архив, распаковать
- Запустить конфигурирование make configure
- Запустить компиляцию make compile
- Установить make install
Как видите, процесс нетривиальный и далеко не всегда быстрый. Иногда даже невыполнимый из-за непонятных ошибок. И это не говоря про загрязнение операционной системы.
Докер позволяет упростить эту процедуру до запуска одной команды причем с почти 100% гарантией успеха.
Чтобы начать пользоваться Докером, необходимо установить движок — Docker Engine. На странице https://docs.docker.com/engine/install/ доступны ссылки для скачивания под все популярные платформы. Выберите вашу и установите Докер.
Посмотрим на вымышленный пример, в котором происходит установка программы Tunnel на локальный компьютер в директорию /usr/local/bin используя образ tunnel:
Запуск этой команды приводит к тому, что в основной системе в директории /usr/local/bin оказывается исполняемый файл программы, находящейся внутри образа tunnel. Команда docker run запускает контейнер из образа tunnel, внутри происходит компиляция программы и, в конечном итоге, она оказывается в директории /usr/local/bin основной файловой системы. Теперь можно стартовать программу, просто набрав tunnel в терминале.
А что если программа, которую мы устанавливаем таким способом, имеет зависимости? Весь фокус в том, что образ, из которого был запущен контейнер, полностью укомплектован. Внутри него установлены все необходимые зависимости, и его запуск практически гарантирует 100% работоспособность, независимо от состояния основной ОС.
Часто даже необязательно копировать программу из контейнера на вашу основную систему. Достаточно запускать сам контейнер, когда в этом возникнет необходимость. Предположим, что мы решили разработать статический сайт на основе Jekyll. Jekyll — популярный генератор статических сайтов, написанный на Ruby. Например, сайт с гайдами Хекслета сгенерирован с его помощью. И при генерации использовался Докер (об этом можно прочитать в гайде: как делать блог на Jekyll).
Старый способ использования Jekyll требовал установки на вашу основную систему как минимум Ruby и самого Jekyll в виде гема (gem — название пакетов в Ruby). Причем, как и всегда в подобных вещах, Jekyll работает только с определенными версиями Ruby, что вносит свои проблемы при настройке.
С Докером запуск Jekyll сводится к одной команде, выполняемой в директории с блогом (подробнее можно посмотреть в репозитории наших гайдов):
Точно таким же образом сейчас запускается огромное количество различного софта. Чем дальше, тем больше подобный способ захватывает мир.
Приложение в контейнере
Теперь поговорим о том, как приложение отображается на контейнеры. Возможны два подхода:
- Всё приложение — один контейнер, внутри которого поднимается дерево процессов: приложение, веб сервер, база данных и всё в этом духе.
- Каждый запущенный контейнер — атомарный сервис. Другими словами каждый контейнер представляет собой ровно одну программу, будь то веб-сервер или приложение.
На практике все преимущества Docker достигаются только со вторым подходом. Во-первых, сервисы, как правило, разнесены по разным машинам и нередко перемещаются по ним (например, в случае выхода из строя сервера), во-вторых, обновление одного сервиса не должно приводить к остановке остальных.
Первый подход крайне редко, но бывает нужен. Например, Хекслет работает в двух режимах. Сам сайт с его сервисами использует вторую модель, когда каждый сервис отдельно, но вот практика, выполняемая в браузере, стартует по принципу "один пользователь — один контейнер". Внутри контейнера может оказаться всё что угодно в зависимости от практики. Как минимум, там всегда стартует сама среда Хекслет IDE, а она в свою очередь порождает терминалы (процессы). В курсе по базам данных в этом же контейнере стартует и база данных, в курсе, связанном с вебом, стартует веб-сервер. Такой подход позволяет создать иллюзию работы на настоящей машине и резко снижает сложность в поддержке упражнений. Повторюсь, что такой вариант использования очень специфичен и вам вряд ли понадобится.
Другой важный аспект при работе с контейнерами касается состояния. Например, если база запускается в контейнере, то её данные ни в коем случае не должны храниться там же, внутри контейнера. Контейнер как процесс операционной системы, может быть легко уничтожен, его наличие всегда временно. Docker содержит механизмы для хранения и использования данных, лежащих в основной файловой системе. О них будет позже.
Работа с образами
Docker — больше, чем просто программа. Это целая экосистема со множеством проектов и сервисов. Главный сервис, с которым вам придется иметь дело — Registry. Хранилище образов.
Концептуально оно работает так же, как и репозиторий пакетов любого пакетного менеджера. Посмотреть его содержимое можно на сайте https://hub.docker.com/.
Когда мы выполняем команду run docker run <image name>, то Docker проверяет наличие указанного образа на локальной машине и скачивает его по необходимости. Список образов, уже скачанных на компьютер, можно посмотреть командой docker images:
Разберёмся с тем, как формируется имя образа, и что оно в себя включает.
Вторая колонка в выводе выше называется TAG. Когда мы выполняли команду docker run nginx, то на самом деле выполнялась команда docker run nginx:latest. То есть мы не просто скачиваем образ nginx, а скачиваем его конкретную версию. Latest — тег по умолчанию. Несложно догадаться, что он означает последнюю версию образа.
Важно понимать, что это всего лишь соглашение, а не правило. Конкретный образ вообще может не иметь тега latest, либо иметь, но он не будет содержать последние изменения, просто потому, что никто их не публикует. Впрочем, популярные образы следуют соглашению. Как понятно из контекста, теги в Докере изменяемы. Другими словами, вам никто не гарантирует, что скачав образ с одним и тем же тегом на разных компьютерах в разное время вы получите одно и то же. Такой подход может показаться странным и ненадежным, ведь нет гарантий, но на практике есть определенные соглашения, которым следуют все популярные образы. Тег latest действительно всегда содержит последнюю версию и постоянно обновляется, но кроме этого тега активно используется семантическое версионирование. Рассмотрим https://hub.docker.com/_/nginx
1.13.8, mainline, 1, 1.13, latest
1.13.8-perl, mainline-perl, 1-perl, 1.13-perl, perl
1.13.8-alpine, mainline-alpine, 1-alpine, 1.13-alpine, alpine
1.13.8-alpine-perl, mainline-alpine-perl, 1-alpine-perl, 1.13-alpine-perl, alpine-perl
1.12.2, stable, 1.12
1.12.2-perl, stable-perl, 1.12-perl
1.12.2-alpine, stable-alpine, 1.12-alpine
1.12.2-alpine-perl, stable-alpine-perl, 1.12-alpine-perl
Теги, в которых присутствует полная семантическая версия (x.x.x) всегда неизменяемы, даже если в них встречается что-то еще, например, 1.12.2-alphine. Такую версию смело нужно брать для продакшен-окружения. Теги, подобные такому 1.12, обновляются при изменении patch версии. То есть внутри образа может оказаться и версия 1.12.2 и в будущем 1.12.8. Точно такая же схема и с версиями, в которых указана только мажорная версия, например, 1. Только в данном случае обновление идет не только по патчу, но и по минорной версии.
Как вы помните, команда docker run скачивает образ, если его нет локально, но эта проверка не связана с обновлением содержимого. Другими словами, если nginx:latest обновился, то docker run его не будет скачивать, он использует тот latest, который прямо сейчас уже загружен. Для гарантированного обновления образа существует другая команда: docker pull. Вот она всегда проверяет, обновился ли образ для определенного тега.
Кроме тегов имя образа может содержать префикс, например, etsy/chef. Этот префикс является именем аккаунта на сайте, через который создаются образы, попадающие в Registry. Большинство образов как раз такие, с префиксом. И есть небольшой набор, буквально сотня образов, которые не имеют префикса. Их особенность в том, что эти образы поддерживает сам Docker. Поэтому если вы видите, что в имени образа нет префикса, значит это официальный образ. Список таких образов можно увидеть здесь: https://github.com/docker-library/official-images/tree/master/library
Удаляются образы командой docker rmi <imagename>.
Если в Докере присутствует хоть один контейнер из удаляемого образа, то Докер не даст его удалить по понятным причинам. Если вы всё же хотите удалить и образ и все контейнеры, связанные с ним, используйте флаг -f.
Управление контейнерами
Картинка описывает жизненный цикл (конечный автомат) контейнера. Кружками на нём изображены состояния, жирным выделены консольные команды, а квадратиками показывается то, что в реальности выполняется.
Проследите путь команды docker run. Несмотря на то, что команда одна, с точки зрения работы Докера выполняется два действия: создание контейнера и запуск. Существуют и более сложные варианты исполнения, но в этом разделе мы рассмотрим только базовые команды.
Запустим nginx так, чтобы он работал в фоне. Для этого после слова run добавляется флаг -d:
После выполнения команды Докер выводит идентификатор контейнера и возвращает управление. Убедитесь в том, что nginx работает, открыв в браузере ссылку localhost:8080. В отличие от предыдущего запуска, наш nginx работает в фоне, а значит не видно его вывода (логов). Посмотреть его можно командой docker logs, которой нужно передать идентификатор контейнера:
Вы также можете подсоединиться к выводу лога в стиле tail -f. Для этого запустите docker logs -f 431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e. Теперь лог будет обновляться каждый раз, когда вы обновляете страницу в браузере. Выйти из этого режима можно набрав Ctrl + C, при этом сам контейнер остановлен не будет.
Теперь выведем информацию о запущенных контейнерах командой docker ps:
Расшифровка столбиков:
- CONTAINER_ID — идентификатор контейнера. Так же, как и в git, используется сокращенная запись хеша.
- IMAGE — имя образа, из которого был поднят контейнер. Если не указан тег, то подразумевается latest.
- COMMAND — команда, которая выполнилась на самом деле при старте контейнера.
- CREATED — время создания контейнера
- STATUS — текущее состояние.
- PORTS — проброс портов.
- NAMES — алиас. Докер позволяет кроме идентификатора иметь имя. Так гораздо проще обращаться с контейнером. Если при создании контейнера имя не указано, то Докер самостоятельно его придумывает. В выводе выше как раз такое имя у nginx.
(Команда docker stats выводит информацию о том, сколько ресурсов потребляют запущенные контейнеры).
Теперь попробуем остановить контейнер. Выполним команду:
Если попробовать набрать docker ps, то там этого контейнера больше нет. Он удален.
Команда docker ps выводит только запущенные контейнеры. Но кроме них могут быть и остановленные. Причём остановка может происходить как по успешному завершению, так и в случае ошибок. Попробуйте набрать docker run ubuntu ls, а затем docker run ubuntu bash -c "unknown". Эти команды не запускают долгоживущий процесс, они завершаются сразу после выполнения, причем вторая с ошибкой, так как такой команды не существует.
Теперь выведем все контейнеры командой docker ps -a. Первыми тремя строчками вывода окажутся:
Здесь как раз два последних наших запуска. Если посмотреть на колонку STATUS, то видно, что оба контейнера находятся в состоянии Exited. То есть запущенная команда внутри них выполнилась, и они остановились. Разница лишь в том, что один завершился успешно (0), а второй с ошибкой (127). После остановки контейнер можно даже перезапустить:
Только в этот раз вы не увидите вывод. Чтобы его посмотреть, воспользуйтесь командой docker logs determined_tereshkova.
Взаимодействие с другими частями системы
Запускать изолированный контейнер, который живёт весь внутри себя, малополезно. Как правило, контейнеру нужно взаимодействовать с внешним миром, принимать входящие запросы на определенный порт, выполнять запросы на другие сервисы, читать общие файлы и писать в них. Все эти возможности настраиваются при создании контейнера.
Interactive mode
Самый простой вариант использования Докера, как мы уже убедились — поднять контейнер и выполнить внутри него какую-либо команду:
После выполнения команды Docker возвращает управление, и мы снова находимся вне контейнера. Если попробовать точно так же запустить баш, то мы получим не то, что хотим:
Дело в том, что bash запускает интерактивную сессию внутри контейнера. Для взаимодействия с ней нужно оставить открытым поток STDIN и запустить TTY (псевдо-терминал). Поэтому для запуска интерактивных сессий нужно не забыть добавить опции -i и -t. Как правило, их добавляют сразу вместе как -it. Поэтому правильный способ запуска баша выглядит так: docker run -it ubuntu bash.
Ports
Если запустить nginx такой командой docker run nginx, то nginx не сможет принять ни один запрос, несмотря на то, что внутри контейнера он слушает 80 порт (напомню, что каждый контейнер по умолчанию живёт в своей собственной сети). Но если запустить его так docker run -p 8080:80 nginx, то nginx начнёт отвечать на порту 8080.
Флаг -p позволяет описывать, как и какой порт выставить наружу. Формат записи 8080:80 расшифровывается так: пробросить порт 8080 снаружи контейнера в контейнер на порт 80. Причём по умолчанию порт 8080 слушается на 0.0.0.0, то есть на всех доступных интерфейсах. Поэтому запущенный таким образом контейнер доступен не только через localhost:8080, но и снаружи машины (если доступ не запрещён как-нибудь ещё). Если нужно выполнить проброс только на loopback, то команда меняется на такую: docker run -p 127.0.0.1:8080:80 nginx.
Docker позволяет пробрасывать столько портов, сколько нужно. Например, в случае nginx часто требуется использовать и 80 порт и 443 для HTTPS. Сделать это можно так: docker run -p 80:80 -p 443:443 nginx Про остальные способы пробрасывать порты можно прочитать в официальной документации.
Volumes
Другая частая задача связана с доступом к основной файловой системе. Например, при старте nginx-контейнера ему можно указать конфигурацию, лежащую на основной фс. Докер прокинет её во внутреннюю фс, и nginx сможет её читать и использовать.
Проброс осуществляется с помощью опции -v. Вот как можно запустить баш сессию из образа Ubuntu, подключив туда историю команд с основной файловой системы: docker run -it -v ~/.bash_history:/root/.bash_history ubuntu bash. Если в открытом баше понажимать стрелку вверх, то отобразится история. Пробрасывать можно как файлы, так и директории. Любые изменения производимые внутри volume меняются как внутри контейнера, так и снаружи, причём по умолчанию доступны любые операции. Как и в случае портов, количество пробрасываемых файлов и директорий может быть любым.
При работе с Volumes есть несколько важных правил, которые надо знать:
- Путь до файла во внешней системе должен быть абсолютным.
- Если внутренний путь (то, что идёт после :) не существует, то Докер создаст все необходимые директории и файлы. Если существует, то заменит старое тем, что было проброшено.
Кроме пробрасывания части фс снаружи Докер предоставляет ещё несколько вариантов создания и использования Volumes. Подробнее — в официальной документации.
Переменные окружения
Конфигурирование приложения внутри контейнера, как правило, осуществляется с помощью переменных окружения в соответствии с 12factors. Существует два способа их установки:
- Флаг -e. Используется он так: docker run -it -e "HOME=/tmp" ubuntu bash
- Специальный файл, содержащий определения переменных окружения, который пробрасывается внутрь контейнера опцией --env-file.
Подготовка собственного образа
Создание и публикация собственного образа не сложнее его использования. Весь процесс делится на три шага:
- Создается файл Dockerfile в корне проекта. Внутри описывается процесс создания образа.
- Выполняется сборка образа командой docker build
- Выполняется публикация образа в Registry командой docker push
Рассмотрим процесс создания образа на примере упаковки линтера eslint (не забудьте повторить его самостоятельно). В результате сборки мы получим образ, который можно использовать так:
То есть достаточно запустить контейнер из этого образа, подключив каталог с файлами js для проверки как Volume во внутреннюю директорию /app.
1. Конечная структура директории, на основе файлов которой соберется образ, выглядит так:
eslint-docker/
Dockerfile
eslintrc.yml
Файл eslintrc.yml содержит конфигурацию линтера. Он автоматически будет прочитан, если лежит в домашней директории под именем .eslintrc.yml. То есть этот файл должен попасть под таким именем в директорию /root внутрь образа.
2. Создание Dockerfile
Dockerfile имеет довольно простой формат. На каждой строчке указывается инструкция (директива) и её описание.
FROM
Инструкция FROM нужна для указания образа, от которого происходит наследование. Здесь необходимо оговориться, что образы строятся на базе друг друга и все вместе образуют большое дерево.
В корне этого дерева находится образ busybox. В прикладных задачах напрямую его не используют, так как Докером предоставляются подготовленные образы под каждую экосистему и стек.
RUN
Основная инструкция в Dockerfile. Фактически здесь указывается sh команда, которая будет выполнена в рамках окружения, указанного во FROM при сборке образа. Так как по умолчанию всё выполняется от пользователя root, то использовать sudo не нужно (и скорее всего его нет в базовом образе). К тому же учтите, что сборка образа — процесс не интерактивный. В тех ситуациях, когда вы используете команду, которая может запросить что-то от пользователя, необходимо подавлять этот вывод. Например, в случае пакетных менеджеров делают так: apt-get install -y curl. Флаг -y как раз говорит о том, что нужно производиться установку без дополнительных вопросов.
Технически образ Докера — это не один файл, а набор так называемых слоев. Каждый вызов RUN формирует новый слой, который можно представить как набор файлов, созданных и измененных (в том числе удаленных) командой, указанной в RUN. Такой подход позволяет значительно улучшить производительность системы, задействовав кеширование слоев, которые не поменялись. С другой стороны, Докер переиспользует слои в разных образах если они идентичны, что сокращает и скорость загрузки и занимаемое пространство на диске. Тема кеширования слоев довольно важная при активном использовании Докера. Для её эффективной работы нужно понимать, как она устроена и как правильно описывать инструкции RUN для максимальной утилизации.
COPY
В соответствии со своим названием команда COPY берёт файл или директорию из основной файловой системы и копирует её внутрь образа. У команды есть ограничение. То, что копируется, должно лежать в той же директории, где и Dockerfile. Именно эту команду используют при разработке когда необходимо упаковать приложение внутрь образа.
WORKDIR
Инструкция, устанавливающая рабочую директорию. Все последующие инструкции будут считать, что они выполняются именно внутри неё. Инструкция WORKDIR действует, как команда cd. Кроме того, когда мы запускаем контейнер, то он также стартует из рабочей директории. Например, запустив bash, вы окажетесь внутри неё.
CMD
Та самая инструкция, определяющая действие по умолчанию при использовании docker run. Она используется только в том случае, если контейнер был запущен без указания команды, иначе она игнорируется.
3. Сборка
Для сборки образа используется команда docker build. С помощью флага -t передается имя образа, включая имя аккаунта и тег. Как обычно, если не указывать тег, то подставляется latest.
После выполнения данной команды вы можете увидеть текущий образ в списке docker images. Вы даже можете начать его использовать без необходимости публикации в Registry. Напомню, что команда docker run не пытается искать обновленную версию образа, если локально есть образ с таким именем и тегом.
4. Публикация
Для успешного выполнения публикации нужно соблюсти два условия:
- Зарегистрироваться на Docker Cloud и создать там репозиторий для образа.
- Залогиниться в cli интерфейсе используя команду docker login.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 20:41:06 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="A3AraBMpHs5vUXOqPh1Rgr3etkNVIN5MOv_3HfSyB3XsoeBf4VezrtkSVzIyEqH1fdeb6V0XIO6HH21JprXgGw";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Docker | DevOps: Автоматизация локального окружения</title>
<meta name="description" content="Docker / DevOps: Автоматизация локального окружения: Узнать что такое docker, как он упрощает жизнь разработчику, познакомиться с базовыми понятиями и командами.">
<link rel="canonical" href="https://ru.hexlet.io/courses/devops-local-setup/lessons/docker/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Docker">
<meta property="og:title" content="DevOps: Автоматизация локального окружения">
<meta property="og:description" content="Docker / DevOps: Автоматизация локального окружения: Узнать что такое docker, как он упрощает жизнь разработчику, познакомиться с базовыми понятиями и командами.">
<meta property="og:url" content="https://ru.hexlet.io/courses/devops-local-setup/lessons/docker/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="_xrEsFAv8-2QNGvTgGevL1PyOtXmyc62Ehcg1HlqScgQyw-HolFejSZ3T0uMaF9Yk_sXf-7-MBSv97qAK22upg" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T20:41:05.949Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"aXdXvAsrWnKdx8T3zdVfwUtmboewVbtkQ6wo3apJufWGppyL-VX3EiuE4G_B2q-2i29DLbhiRcb-TLKJ-E5emw","topics":[{"id":56489,"title":"Добрый день!\n\nА в этот курс будет добавлено что-то о **kubernetes**?","plain_title":"Добрый день! А в этот курс будет добавлено что-то о kubernetes? ","creator":{"public_name":"Roman","id":366856,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman","id":366856,"is_tutor":false},"id":119763,"body":"Данке!","topic_id":56489},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":119753,"body":"Приветствую, Роман!\n\nТема о kubernetes есть в другом курсе - \"[DevOps: Деплой и эксплуатация](https://ru.hexlet.io/courses/devops-deploy)\".","topic_id":56489}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Docker","entity_url":null,"active":true}},{"id":91237,"title":"Я не очень понимаю. Я по каким то причинам решил вести разработку с помощью докера. У меня в приложении есть менеджер пакетов. На локальной машине все просто, я могу добавлять и удалять пакеты.\nНо как быть если я использую докер? \nЕсли я правильно понял, то когда контейнер стартует, в нем должно запускаться приложение. Но что делать, если я не хочу запускать приложение? Я хочу запустить менеджер пакетов или что-то такое? Те же тесты например. Как мне быть? ","plain_title":"Я не очень понимаю. Я по каким то причинам решил вести разработку с помощью докера. У меня в приложении есть менеджер пакетов. На локальной машине все просто, я могу добавлять и удалять пакеты. Но как быть если я использую докер? Если я правильно понял, то когда контейнер стартует, в нем должно запускаться приложение. Но что делать, если я не хочу запускать приложение? Я хочу запустить менеджер пакетов или что-то такое? Те же тесты например. Как мне быть? ","creator":{"public_name":"","id":168,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":180605,"body":"**dosart**, докер — это инструмент для запуска процессов в изолированной среде (контейнеры). Поэтому, если у вас нет процесса, то и докер не нужен. Запуск тестов — это такой же процесс, как запуск приложения. Вместо приложения запускайте тесты.","topic_id":91237}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Docker","entity_url":null,"active":true}}],"lesson":{"exercise":null,"units":[{"id":4497,"name":"theory","url":"/courses/devops-local-setup/lessons/docker/theory_unit"}],"links":[],"ordered_units":[{"id":4497,"name":"theory","url":"/courses/devops-local-setup/lessons/docker/theory_unit"}],"id":1993,"slug":"docker","state":"approved","name":"Docker","course_order":500,"goal":"Узнать что такое docker, как он упрощает жизнь разработчику, познакомиться с базовыми понятиями и командами.","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"**Докер — универсальный способ доставки приложений на машины (локальный компьютер или удаленные сервера) и их запуска в изолированном окружении.**\n\nВспомните, когда вам приходилось собирать программы из исходников. Этот процесс включает в себя следующие шаги:\n\n* Установить все необходимые зависимости под вашу операционную систему (их список ещё надо найти)\n* Скачать архив, распаковать\n* Запустить конфигурирование `make configure`\n* Запустить компиляцию `make compile`\n* Установить `make install`\n\nКак видите, процесс нетривиальный и далеко не всегда быстрый. Иногда даже невыполнимый из-за непонятных ошибок. И это не говоря про загрязнение операционной системы.\n\nДокер позволяет упростить эту процедуру до запуска одной команды причем с почти 100% гарантией успеха.\n\nЧтобы начать пользоваться Докером, необходимо установить движок — Docker Engine. На странице https://docs.docker.com/engine/install/ доступны ссылки для скачивания под все популярные платформы. Выберите вашу и установите Докер.\n\nПосмотрим на вымышленный пример, в котором происходит установка программы Tunnel на локальный компьютер в директорию */usr/local/bin* используя образ _tunnel_:\n\n```bash\ndocker run -v /usr/local/bin:/out tunnel\n```\n\nЗапуск этой команды приводит к тому, что в основной системе в директории */usr/local/bin* оказывается исполняемый файл программы, находящейся внутри образа _tunnel_. Команда `docker run` запускает контейнер из образа _tunnel_, внутри происходит компиляция программы и, в конечном итоге, она оказывается в директории */usr/local/bin* основной файловой системы. Теперь можно стартовать программу, просто набрав `tunnel` в терминале.\n\nА что если программа, которую мы устанавливаем таким способом, имеет зависимости? Весь фокус в том, что образ, из которого был запущен контейнер, полностью укомплектован. Внутри него установлены все необходимые зависимости, и его запуск практически гарантирует 100% работоспособность, независимо от состояния основной ОС.\n\nЧасто даже необязательно копировать программу из контейнера на вашу основную систему. Достаточно запускать сам контейнер, когда в этом возникнет необходимость. Предположим, что мы решили разработать статический сайт на основе Jekyll. Jekyll — популярный генератор статических сайтов, написанный на Ruby. Например, сайт с [гайдами Хекслета](https://guides.hexlet.io/ru/) сгенерирован с его помощью. И при генерации использовался Докер (об этом можно прочитать в гайде: [как делать блог на Jekyll](https://guides.hexlet.io/ru/jekyll/)).\n\nСтарый способ использования Jekyll требовал установки на вашу основную систему как минимум Ruby и самого Jekyll в виде гема (gem — название пакетов в Ruby). Причем, как и всегда в подобных вещах, Jekyll работает только с определенными версиями Ruby, что вносит свои проблемы при настройке.\n\nС Докером запуск Jekyll сводится к одной команде, выполняемой в директории с блогом (подробнее можно посмотреть в [репозитории](https://github.com/hexletguides/hexletguides.github.io) наших гайдов):\n\n```bash\ndocker run --rm --volume=\"$PWD:/srv/jekyll\" -it jekyll/jekyll jekyll server\n```\n\nТочно таким же образом сейчас запускается огромное количество различного софта. Чем дальше, тем больше подобный способ захватывает мир.\n\n## Приложение в контейнере\n\nТеперь поговорим о том, как приложение отображается на контейнеры. Возможны два подхода:\n\n1. Всё приложение — один контейнер, внутри которого поднимается дерево процессов: приложение, веб сервер, база данных и всё в этом духе.\n1. Каждый запущенный контейнер — атомарный сервис. Другими словами каждый контейнер представляет собой ровно одну программу, будь то веб-сервер или приложение.\n\nНа практике все преимущества Docker достигаются только со вторым подходом. Во-первых, сервисы, как правило, разнесены по разным машинам и нередко перемещаются по ним (например, в случае выхода из строя сервера), во-вторых, обновление одного сервиса не должно приводить к остановке остальных.\n\nПервый подход крайне редко, но бывает нужен. Например, Хекслет работает в двух режимах. Сам сайт с его сервисами использует вторую модель, когда каждый сервис отдельно, но вот практика, выполняемая в браузере, стартует по принципу \"один пользователь — один контейнер\". Внутри контейнера может оказаться всё что угодно в зависимости от практики. Как минимум, там всегда стартует сама среда Хекслет IDE, а она в свою очередь порождает терминалы (процессы). В курсе по базам данных в этом же контейнере стартует и база данных, в курсе, связанном с вебом, стартует веб-сервер. Такой подход позволяет создать иллюзию работы на настоящей машине и резко снижает сложность в поддержке упражнений. Повторюсь, что такой вариант использования очень специфичен и вам вряд ли понадобится.\n\nДругой важный аспект при работе с контейнерами касается состояния. Например, если база запускается в контейнере, то её данные ни в коем случае не должны храниться там же, внутри контейнера. Контейнер как процесс операционной системы, может быть легко уничтожен, его наличие всегда временно. Docker содержит механизмы для хранения и использования данных, лежащих в основной файловой системе. О них будет позже.\n\n## Работа с образами\n\nDocker — больше, чем просто программа. Это целая экосистема со множеством проектов и сервисов. Главный сервис, с которым вам придется иметь дело — Registry. Хранилище образов.\n\nКонцептуально оно работает так же, как и репозиторий пакетов любого пакетного менеджера. Посмотреть его содержимое можно на сайте [https://hub.docker.com/](https://hub.docker.com/).\n\nКогда мы выполняем команду _run_ `docker run <image name>`, то Docker проверяет наличие указанного образа на локальной машине и скачивает его по необходимости. Список образов, уже скачанных на компьютер, можно посмотреть командой `docker images`:\n\n```bash\ndocker images\n\nREPOSITORY TAG IMAGE ID CREATED SIZE\nworkshopdevops_web latest cfd7771b4b3a 2 days ago 817MB\nhexletbasics_app latest 8e34a5f631ea 2 days ago 1.3GB\nmokevnin/rails latest 96487c602a9b 2 days ago 743MB\nubuntu latest 2a4cca5ac898 3 days ago 111MB\nRuby 2.4 713da53688a6 3 weeks ago 687MB\nRuby 2.5 4c7885e3f2bb 3 weeks ago 881MB\nnginx latest 3f8a4339aadd 3 weeks ago 108MB\nelixir latest 93617745963c 4 weeks ago 889MB\npostgres latest ec61d13c8566 5 weeks ago 287MB\n```\n\nРазберёмся с тем, как формируется имя образа, и что оно в себя включает.\n\nВторая колонка в выводе выше называется TAG. Когда мы выполняли команду `docker run nginx`, то на самом деле выполнялась команда `docker run nginx:latest`. То есть мы не просто скачиваем образ _nginx_, а скачиваем его конкретную версию. Latest — тег по умолчанию. Несложно догадаться, что он означает последнюю версию образа.\n\nВажно понимать, что это всего лишь соглашение, а не правило. Конкретный образ вообще может не иметь тега _latest_, либо иметь, но он не будет содержать последние изменения, просто потому, что никто их не публикует. Впрочем, популярные образы следуют соглашению. Как понятно из контекста, теги в Докере изменяемы. Другими словами, вам никто не гарантирует, что скачав образ с одним и тем же тегом на разных компьютерах в разное время вы получите одно и то же. Такой подход может показаться странным и ненадежным, ведь нет гарантий, но на практике есть определенные соглашения, которым следуют все популярные образы. Тег latest действительно всегда содержит последнюю версию и постоянно обновляется, но кроме этого тега активно используется [семантическое версионирование](https://semver.org/). Рассмотрим https://hub.docker.com/_/nginx\n\n 1.13.8, mainline, 1, 1.13, latest\n 1.13.8-perl, mainline-perl, 1-perl, 1.13-perl, perl\n 1.13.8-alpine, mainline-alpine, 1-alpine, 1.13-alpine, alpine\n 1.13.8-alpine-perl, mainline-alpine-perl, 1-alpine-perl, 1.13-alpine-perl, alpine-perl\n 1.12.2, stable, 1.12\n 1.12.2-perl, stable-perl, 1.12-perl\n 1.12.2-alpine, stable-alpine, 1.12-alpine\n 1.12.2-alpine-perl, stable-alpine-perl, 1.12-alpine-perl\n\nТеги, в которых присутствует полная семантическая версия (x.x.x) всегда неизменяемы, даже если в них встречается что-то еще, например, _1.12.2-alphine_. Такую версию смело нужно брать для продакшен-окружения. Теги, подобные такому _1.12_, обновляются при изменении patch версии. То есть внутри образа может оказаться и версия _1.12.2_ и в будущем _1.12.8_. Точно такая же схема и с версиями, в которых указана только мажорная версия, например, _1_. Только в данном случае обновление идет не только по патчу, но и по минорной версии.\n\nКак вы помните, команда `docker run` скачивает образ, если его нет локально, но эта проверка не связана с обновлением содержимого. Другими словами, если _nginx:latest_ обновился, то `docker run` его не будет скачивать, он использует тот _latest_, который прямо сейчас уже загружен. Для гарантированного обновления образа существует другая команда: `docker pull`. Вот она всегда проверяет, обновился ли образ для определенного тега.\n\nКроме тегов имя образа может содержать префикс, например, `etsy/chef`. Этот префикс является именем аккаунта на сайте, через который создаются образы, попадающие в Registry. Большинство образов как раз такие, с префиксом. И есть небольшой набор, буквально сотня образов, которые не имеют префикса. Их особенность в том, что эти образы поддерживает сам Docker. Поэтому если вы видите, что в имени образа нет префикса, значит это официальный образ. Список таких образов можно увидеть здесь: https://github.com/docker-library/official-images/tree/master/library\n\nУдаляются образы командой `docker rmi <imagename>`.\n\n```bash\ndocker rmi Ruby:2.4\n\nUntagged: Ruby:2.4\nUntagged: Ruby@sha256:d973c59b89f3c5c9bb330e3350ef8c529753ba9004dcd1bfbcaa4e9c0acb0c82\n```\n\nЕсли в Докере присутствует хоть один контейнер из удаляемого образа, то Докер не даст его удалить по понятным причинам. Если вы всё же хотите удалить и образ и все контейнеры, связанные с ним, используйте флаг `-f`.\n\n## Управление контейнерами\n\n\n\nКартинка описывает жизненный цикл (конечный автомат) контейнера. Кружками на нём изображены состояния, жирным выделены консольные команды, а квадратиками показывается то, что в реальности выполняется.\n\nПроследите путь команды `docker run`. Несмотря на то, что команда одна, с точки зрения работы Докера выполняется два действия: создание контейнера и запуск. Существуют и более сложные варианты исполнения, но в этом разделе мы рассмотрим только базовые команды.\n\nЗапустим nginx так, чтобы он работал в фоне. Для этого после слова _run_ добавляется флаг `-d`:\n\n```bash\ndocker run -d -p 8080:80 nginx\n\n431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e\n```\n\nПосле выполнения команды Докер выводит идентификатор контейнера и возвращает управление. Убедитесь в том, что nginx работает, открыв в браузере ссылку `localhost:8080`. В отличие от предыдущего запуска, наш nginx работает в фоне, а значит не видно его вывода (логов). Посмотреть его можно командой `docker logs`, которой нужно передать идентификатор контейнера:\n\n```bash\ndocker logs 431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e\n\n172.17.0.1 - - [19/Jan/2018:07:38:55 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\" \"-\"\n```\n\nВы также можете подсоединиться к выводу лога в стиле `tail -f`. Для этого запустите `docker logs -f 431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e`. Теперь лог будет обновляться каждый раз, когда вы обновляете страницу в браузере. Выйти из этого режима можно набрав <kbd>Ctrl + C</kbd>, при этом сам контейнер остановлен не будет.\n\nТеперь выведем информацию о запущенных контейнерах командой `docker ps`:\n\n```bash\nCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n431a3b3fc24b nginx \"nginx -g 'daemon of…\" 2 minutes ago Up 2 minutes 80/tcp wizardly_rosalind\n```\n\nРасшифровка столбиков:\n\n* CONTAINER_ID — идентификатор контейнера. Так же, как и в git, используется сокращенная запись хеша.\n* IMAGE — имя образа, из которого был поднят контейнер. Если не указан тег, то подразумевается _latest_.\n* COMMAND — команда, которая выполнилась на самом деле при старте контейнера.\n* CREATED — время создания контейнера\n* STATUS — текущее состояние.\n* PORTS — проброс портов.\n* NAMES — алиас. Докер позволяет кроме идентификатора иметь имя. Так гораздо проще обращаться с контейнером. Если при создании контейнера имя не указано, то Докер самостоятельно его придумывает. В выводе выше как раз такое имя у nginx.\n\n(*Команда `docker stats` выводит информацию о том, сколько ресурсов потребляют запущенные контейнеры).*\n\nТеперь попробуем остановить контейнер. Выполним команду:\n\n```bash\n# Вместо CONTAINER_ID можно указывать имя\ndocker kill 431a3b3fc24b # docker kill wizardly_rosalind\n\n431a3b3fc24b\n```\n\nЕсли попробовать набрать `docker ps`, то там этого контейнера больше нет. Он удален.\n\nКоманда `docker ps` выводит только запущенные контейнеры. Но кроме них могут быть и остановленные. Причём остановка может происходить как по успешному завершению, так и в случае ошибок. Попробуйте набрать `docker run ubuntu ls`, а затем `docker run ubuntu bash -c \"unknown\"`. Эти команды не запускают долгоживущий процесс, они завершаются сразу после выполнения, причем вторая с ошибкой, так как такой команды не существует.\n\nТеперь выведем все контейнеры командой `docker ps -a`. Первыми тремя строчками вывода окажутся:\n\n```bash\nCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n85fb81250406 ubuntu \"bash -c unkown\" Less than a second ago Exited (127) 3 seconds ago loving_bose\nc379040bce42 ubuntu \"ls\" Less than a second ago Exited (0) 9 seconds ago determined_tereshkova\n```\n\nЗдесь как раз два последних наших запуска. Если посмотреть на колонку STATUS, то видно, что оба контейнера находятся в состоянии Exited. То есть запущенная команда внутри них выполнилась, и они остановились. Разница лишь в том, что один завершился успешно (0), а второй с ошибкой (127). После остановки контейнер можно даже перезапустить:\n\n```bash\ndocker start determined_tereshkova # В вашем случае будет другое имя\n```\n\nТолько в этот раз вы не увидите вывод. Чтобы его посмотреть, воспользуйтесь командой `docker logs determined_tereshkova`.\n\n## Взаимодействие с другими частями системы\n\nЗапускать изолированный контейнер, который живёт весь внутри себя, малополезно. Как правило, контейнеру нужно взаимодействовать с внешним миром, принимать входящие запросы на определенный порт, выполнять запросы на другие сервисы, читать общие файлы и писать в них. Все эти возможности настраиваются при создании контейнера.\n\n### Interactive mode\n\nСамый простой вариант использования Докера, как мы уже убедились — поднять контейнер и выполнить внутри него какую-либо команду:\n\n```bash\ndocker run ubuntu ls /usr\n\nbin\ngames\ninclude\nlib\nlocal\nsbin\nshare\nsrc\n$\n```\n\nПосле выполнения команды Docker возвращает управление, и мы снова находимся вне контейнера. Если попробовать точно так же запустить баш, то мы получим не то, что хотим:\n\n```bash\ndocker run ubuntu bash\n```\n\nДело в том, что `bash` запускает интерактивную сессию внутри контейнера. Для взаимодействия с ней нужно оставить открытым поток STDIN и запустить TTY (псевдо-терминал). Поэтому для запуска интерактивных сессий нужно не забыть добавить опции `-i` и `-t`. Как правило, их добавляют сразу вместе как `-it`. Поэтому правильный способ запуска баша выглядит так: `docker run -it ubuntu bash`.\n\n### Ports\n\nЕсли запустить nginx такой командой `docker run nginx`, то nginx не сможет принять ни один запрос, несмотря на то, что внутри контейнера он слушает _80_ порт (напомню, что каждый контейнер по умолчанию живёт в своей собственной сети). Но если запустить его так `docker run -p 8080:80 nginx`, то nginx начнёт отвечать на порту _8080_.\n\nФлаг `-p` позволяет описывать, как и какой порт выставить наружу. Формат записи `8080:80` расшифровывается так: пробросить порт _8080_ снаружи контейнера в контейнер на порт _80_. Причём по умолчанию порт _8080_ слушается на `0.0.0.0`, то есть на всех доступных интерфейсах. Поэтому запущенный таким образом контейнер доступен не только через `localhost:8080`, но и снаружи машины (если доступ не запрещён как-нибудь ещё). Если нужно выполнить проброс только на _loopback_, то команда меняется на такую: `docker run -p 127.0.0.1:8080:80 nginx`.\n\nDocker позволяет пробрасывать столько портов, сколько нужно. Например, в случае nginx часто требуется использовать и `80` порт и `443` для HTTPS. Сделать это можно так: `docker run -p 80:80 -p 443:443 nginx` Про остальные способы пробрасывать порты можно прочитать в официальной документации.\n\n### Volumes\n\nДругая частая задача связана с доступом к основной файловой системе. Например, при старте nginx-контейнера ему можно указать конфигурацию, лежащую на основной фс. Докер прокинет её во внутреннюю фс, и nginx сможет её читать и использовать.\n\nПроброс осуществляется с помощью опции `-v`. Вот как можно запустить баш сессию из образа Ubuntu, подключив туда историю команд с основной файловой системы: `docker run -it -v ~/.bash_history:/root/.bash_history ubuntu bash`. Если в открытом баше понажимать стрелку вверх, то отобразится история. Пробрасывать можно как файлы, так и директории. Любые изменения производимые внутри volume меняются как внутри контейнера, так и снаружи, причём по умолчанию доступны любые операции. Как и в случае портов, количество пробрасываемых файлов и директорий может быть любым.\n\nПри работе с Volumes есть несколько важных правил, которые надо знать:\n\n* Путь до файла во внешней системе должен быть абсолютным.\n* Если внутренний путь (то, что идёт после `:`) не существует, то Докер создаст все необходимые директории и файлы. Если существует, то заменит старое тем, что было проброшено.\n\nКроме пробрасывания части фс снаружи Докер предоставляет ещё несколько вариантов создания и использования Volumes. Подробнее — в официальной документации.\n\n### Переменные окружения\n\nКонфигурирование приложения внутри контейнера, как правило, осуществляется с помощью переменных окружения в соответствии с [12factors](https://12factor.net/ru/). Существует два способа их установки:\n\n* Флаг `-e`. Используется он так: `docker run -it -e \"HOME=/tmp\" ubuntu bash`\n* Специальный файл, содержащий определения переменных окружения, который пробрасывается внутрь контейнера опцией `--env-file`.\n\n## Подготовка собственного образа\n\nСоздание и публикация собственного образа не сложнее его использования. Весь процесс делится на три шага:\n\n* Создается файл `Dockerfile` в корне проекта. Внутри описывается процесс создания образа.\n* Выполняется сборка образа командой `docker build`\n* Выполняется публикация образа в Registry командой `docker push`\n\nРассмотрим процесс создания образа на примере упаковки линтера `eslint` (не забудьте повторить его самостоятельно). В результате сборки мы получим образ, который можно использовать так:\n\n```bash\ndocker run -it -v /path/to/js/files:/app my_account_name/eslint\n\n/app/index.js\n 3:6 error Parsing error: Unexpected token\n\n 1 | import path from 'path';\n 2 |\n> 3 | path(;)\n | ^\n 4 |\n\n✖ 1 problem (1 error, 0 warnings)\n```\n\nТо есть достаточно запустить контейнер из этого образа, подключив каталог с файлами js для проверки как Volume во внутреннюю директорию */app*.\n\n### 1. Конечная структура директории, на основе файлов которой соберется образ, выглядит так:\n\n```\neslint-docker/\n Dockerfile\n eslintrc.yml\n```\n\nФайл *eslintrc.yml* содержит конфигурацию линтера. Он автоматически будет прочитан, если лежит в домашней директории под именем *.eslintrc.yml*. То есть этот файл должен попасть под таким именем в директорию */root* внутрь образа.\n\n### 2. Создание Dockerfile\n\n```dockerfile\n# Dockerfile\nFROM node:9.3\n\nWORKDIR /usr/src\n\nRUN npm install -g eslint babel-eslint\nRUN npm install -g eslint-config-airbnb-base eslint-plugin-import\n\nCOPY eslintrc.yml /root/.eslintrc.yml\n\nCMD [\"eslint\", \"/app\"]\n```\n\nDockerfile имеет довольно простой формат. На каждой строчке указывается инструкция (директива) и её описание.\n\n#### FROM\n\nИнструкция FROM нужна для указания образа, от которого происходит наследование. Здесь необходимо оговориться, что образы строятся на базе друг друга и все вместе образуют большое дерево.\n\nВ корне этого дерева находится образ _busybox_. В прикладных задачах напрямую его не используют, так как Докером предоставляются подготовленные образы под каждую экосистему и стек.\n\n#### RUN\n\nОсновная инструкция в Dockerfile. Фактически здесь указывается _sh_ команда, которая будет выполнена в рамках окружения, указанного во FROM при сборке образа. Так как по умолчанию всё выполняется от пользователя _root_, то использовать _sudo_ не нужно (и скорее всего его нет в базовом образе). К тому же учтите, что сборка образа — процесс не интерактивный. В тех ситуациях, когда вы используете команду, которая может запросить что-то от пользователя, необходимо подавлять этот вывод. Например, в случае пакетных менеджеров делают так: `apt-get install -y curl`. Флаг `-y` как раз говорит о том, что нужно производиться установку без дополнительных вопросов.\n\nТехнически образ Докера — это не один файл, а набор так называемых *слоев*. Каждый вызов RUN формирует новый слой, который можно представить как набор файлов, созданных и измененных (в том числе удаленных) командой, указанной в RUN. Такой подход позволяет значительно улучшить производительность системы, задействовав кеширование слоев, которые не поменялись. С другой стороны, Докер переиспользует слои в разных образах если они идентичны, что сокращает и скорость загрузки и занимаемое пространство на диске. Тема кеширования слоев довольно важная при активном использовании Докера. Для её эффективной работы нужно понимать, как она устроена и как правильно описывать инструкции `RUN` для максимальной утилизации.\n\n#### COPY\n\nВ соответствии со своим названием команда COPY берёт файл или директорию из основной файловой системы и копирует её внутрь образа. У команды есть ограничение. То, что копируется, должно лежать в той же директории, где и Dockerfile. Именно эту команду используют при разработке когда необходимо упаковать приложение внутрь образа.\n\n#### WORKDIR\n\nИнструкция, устанавливающая рабочую директорию. Все последующие инструкции будут считать, что они выполняются именно внутри неё. Инструкция `WORKDIR` действует, как команда `cd`. Кроме того, когда мы запускаем контейнер, то он также стартует из рабочей директории. Например, запустив bash, вы окажетесь внутри неё.\n\n#### CMD\n\nТа самая инструкция, определяющая действие по умолчанию при использовании `docker run`. Она используется только в том случае, если контейнер был запущен без указания команды, иначе она игнорируется.\n\n\n\n### 3. Сборка\n\nДля сборки образа используется команда `docker build`. С помощью флага `-t` передается имя образа, включая имя аккаунта и тег. Как обычно, если не указывать тег, то подставляется _latest_.\n\n```bash\ndocker build -t my_account_name/eslint .\n```\n\nПосле выполнения данной команды вы можете увидеть текущий образ в списке `docker images`. Вы даже можете начать его использовать без необходимости публикации в Registry. Напомню, что команда `docker run` не пытается искать обновленную версию образа, если локально есть образ с таким именем и тегом.\n\n### 4. Публикация\n\n```bash\ndocker push my_account_name/eslint\n```\n\nДля успешного выполнения публикации нужно соблюсти два условия:\n\n* Зарегистрироваться на Docker Cloud и создать там репозиторий для образа.\n* Залогиниться в cli интерфейсе используя команду `docker login`.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":4495,"name":"theory","url":"/courses/devops-local-setup/lessons/vagrant/theory_unit"},{"id":4738,"name":"quiz","url":"/courses/devops-local-setup/lessons/vagrant/quiz_unit"}],"links":[{"id":425606,"name":"Официальная документация","url":"https://www.vagrantup.com/docs\n"}],"ordered_units":[{"id":4495,"name":"theory","url":"/courses/devops-local-setup/lessons/vagrant/theory_unit"},{"id":4738,"name":"quiz","url":"/courses/devops-local-setup/lessons/vagrant/quiz_unit"}],"id":1991,"slug":"vagrant","state":"approved","name":"Vagrant","course_order":200,"goal":"Научиться запускать приложение в изолированной среде с помощью Vagrant","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"569878603","theory":"*Vagrant — продукт компании HashiCorp, специализирующейся на инструментах для автоматизации разработки и эксплуатации. Он позволяет создавать и конфигурировать легковесные, повторяемые и переносимые окружения для разработки.*\n\nПервая задача любого разработчика на новом проекте - развернуть окружение. Как правило, она сводится к следующим шагам:\n\n1. Клонировать репозиторий с проектом.\n1. Поставить необходимые пакеты для работы (например, библиотеку для xml).\n1. Установить дополнительные программы, такие как базу данных.\n1. Правильно настроить конфигурационные параметры.\n\nБез автоматизации подобный процесс может занимать как часы, так и целые дни, в зависимости от сложности проекта. Причем, для каждого человека в команде. Для каждой смены рабочей машины или после переустановки системы. Проблема усугубляется тем, что чем больше разработчиков на проекте, тем более разнообразные машины они используют, включая разные операционные системы. В подобном случае процесс настройки гарантированно будет разным, что является еще одним источником проблем. Весь процесс настройки должен быть где-то описан, но, как правило, эти описания быстро устаревают и их мало кто хочет поддерживать, а любые попытки поддерживать актуальность в конце концов угасают.\n\nПосле добавления пары таких проектов операционная система становится сильно захламленной. Даже если вы не работаете над проектом, после включения компьютера начнут стартовать сервисы, которые требуются только для разработки.\n\nЕще одна проблема - изменения. Практически любая перенастройка установленной конфигурации потребует ручного вмешательства, доустановки пакетов, программ или изменения конфигов.\n\nИ последнее: нередко требуется возможность развернуть проект и для не программистов, например верстальщиков или тестировщиков. Наличие даже хорошо описанного процесса установки мало им поможет.\n\nТеперь можно попробовать сформулировать требования к идеальному окружению:\n\n1. Изолированность. Таким образом избегаются возможные конфликты с другими окружениями (например, основной системой) и основная система остается чистой.\n1. Повторяемость. Пересоздать рабочую среду можно за считанные минуты набрав буквально одну команду. Любое изменение распространяется сразу для всех.\n1. Переносимость. Окружение разворачивается под любой системой одним универсальным способом.\n\nVagrant создан для решения именно этих задач. Во многом его работа опирается на виртуализацию, для которой, по умолчанию, используется продукт [VirtualBox](https://www.virtualbox.org/).\n\n\n\nНо в отличие от обычной работы с виртуальной машиной, когда внутри нее стоит система с графической оболочкой, Vagrant создает виртуальную машину доступную только в терминальном режиме (через командную строку), при этом сама разработка продолжается на хост-машине, а вот запуск кода на выполнение происходит внутри машины. Другими словами, редактор ставится на вашу основную систему и код лежит также в ней. Vagrant прозрачно прокидывает код внутрь машины и позволяет его запускать.\n\n## Подготовка к работе\n\nИспользование Vagrant подразумевает некоторую настройку операционной системы и наличие определенных знаний:\n\n* Нужно знать, что такое виртуализация и как работать с виртуальными машинами\n* Командная строка - единственный способ взаимодействия с вагрантом. Если ваша операционная система Windows, то вам необходимо [настроить](https://docs.microsoft.com/en-us/windows/wsl/install-win10) её.\n* Базовое знание сетей, понятие порта.\n* На вашем компьютере должно быть хотя бы 4 гигабайта оперативной памяти, хотя даже с таким объемом, работать проблематично. Минимально комфортный уровень - 8 гигабайт.\n\n## Установка\n\nПервым делом необходимо поставить систему виртуализации, например VirtualBox. Скачать его под вашу операционную систему можно [здесь](https://www.virtualbox.org/wiki/Downloads).\n\nЗатем скачайте установщик Vagrant под вашу операционную систему на странице [Download](https://www.vagrantup.com/downloads.html).\n\nОткройте терминал и убедитесь что Vagrant работает:\n\n```bash\nvagrant -v\n```\n\n## Инициализация\n\nДизайн Vagrant предполагает, что мы создаем отдельную конфигурацию на каждый проект (а соответственно и виртуальную машину), а не одну на все. Инициализация выполняется в папке соответствующего проекта:\n\n```bash\nvagrant init ubuntu/xenial64\n```\n\nВ результате выполнения этой команды Vagrant создаст файл Vagrantfile. Внутри файла описана конфигурация виртуальной машины на языке Ruby. Не страшно, если вы не знакомы с ним, содержимое файла интуитивно понятно, а документация Vagrant достаточно подробна.\n\nКроме создания файла, вагрант скачает на вашу машину образ (image) операционной системы, хранящийся под именем `ubuntu/xenial64`. В данном случае речь идет про Ubuntu 16.04. Vagrant поддерживает каталог образов, созданных специально под работу с ним. При необходимости можно выбрать другой, посмотрев его имя в [каталоге](https://app.vagrantup.com/boxes/search). Если открыть файл Vagrantfile, то можно увидеть что имя образа прописано внутри конфигурации:\n\n```ruby\nVagrant.configure('2') do |config|\n config.vm.box = 'ubuntu/xenial64'\nend\n```\n\n## Запуск\n\nСтарт выполняется командой `vagrant up`. Она создает виртуальную машину и запускает ее. Если машина уже была создана (предыдущими запусками), то произойдет только старт.\n\nВ этот момент уже можно начинать работать с кодом, лежащим в той же папке, что и Vagrantfile. Все изменения автоматически оказываются внутри запущенной виртуальной машины.\n\n## Подключение\n\nДля входа внутрь используется команда `vagrant ssh`. После выполнения терминал подключается к машине в домашнюю директорию пользователя по умолчанию. Традиционно в вагранте это пользователь с именем `vagrant`. Теперь необходимо выполнить переход в папку `/vagrant`. Именно в ней окажется код проекта.\n\nДальше можно действовать по старинке. Поставить все необходимые пакеты и зависимости, а затем запустить проект. Если это веб-сайт, то он запустится на определенном порту. По умолчанию все, что стартует внутри виртуальной машины, доступно только внутри. Для возможности обращаться к сайту с хост машины (через ваш любимый браузер), необходимо прокинуть соответствующий порт наружу. Об этом достаточно подробно рассказано в [документации](https://www.vagrantup.com/docs/networking/basic_usage.html). Предположим, что внутри Vagrant сайт стартует на порту 8080, и вы хотите обращаться к нему снаружи. Для этого достаточно добавить в конфигурацию:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n # ...\n config.vm.network \"forwarded_port\", guest: 8080, host: 8080\nend\n```\n\nИзменения применяются после перезагрузки машины. Для этого достаточно выполнить команду `vagrant reload`.\n\n## Остановка\n\nВ конце работы не забудьте выполнить `vagrant halt`, иначе машина останется запущенной и будет потреблять ресурсы. Если вы решили, что пора полностью все удалить и поставить заново, воспользуйтесь командой `vagrant destroy`.\n"},"id":231,"slug":"devops-local-setup","challenges_count":0,"name":"DevOps: Автоматизация локального окружения","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"free","description":"Курс учит тому, как автоматизировать локальное окружение с помощью Ansible, и перейти на разработку в Docker Compose.","kind":"sandbox","updated_at":"2026-01-20T11:54:46.577Z","language":"shell","duration_cache":6840,"skills":["Упаковывать приложение в Docker","Разрабатывать с помощью Docker Compose","Настраивать локальное окружение с помощью Ansible"],"keywords":["vagrant","docker","CI","ansible"],"lessons_count":7,"cover":"/vite/assets/course-DLoqF1jj.png"},"recommendedLandings":[],"lessonMemberUnit":null,"accessToLearnUnitExists":true,"accessToCourseExists":true},"url":"/courses/devops-local-setup/lessons/docker/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">DevOps: Автоматизация локального окружения</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Docker</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Docker","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"DevOps: Автоматизация локального окружения"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p><strong>Докер — универсальный способ доставки приложений на машины (локальный компьютер или удаленные сервера) и их запуска в изолированном окружении.</strong></p>
<p>Вспомните, когда вам приходилось собирать программы из исходников. Этот процесс включает в себя следующие шаги:</p>
<ul>
<li>Установить все необходимые зависимости под вашу операционную систему (их список ещё надо найти)</li>
<li>Скачать архив, распаковать</li>
<li>Запустить конфигурирование <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">make configure</code></li>
<li>Запустить компиляцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">make compile</code></li>
<li>Установить <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">make install</code></li>
</ul>
<p>Как видите, процесс нетривиальный и далеко не всегда быстрый. Иногда даже невыполнимый из-за непонятных ошибок. И это не говоря про загрязнение операционной системы.</p>
<p>Докер позволяет упростить эту процедуру до запуска одной команды причем с почти 100% гарантией успеха.</p>
<p>Чтобы начать пользоваться Докером, необходимо установить движок — Docker Engine. На странице <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://docs.docker.com/engine/install/" rel="noopener noreferrer" target="_blank">https://docs.docker.com/engine/install/</a> доступны ссылки для скачивания под все популярные платформы. Выберите вашу и установите Докер.</p>
<p>Посмотрим на вымышленный пример, в котором происходит установка программы Tunnel на локальный компьютер в директорию <em>/usr/local/bin</em> используя образ <em>tunnel</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker run -v /usr/local/bin:/out tunnel</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Запуск этой команды приводит к тому, что в основной системе в директории <em>/usr/local/bin</em> оказывается исполняемый файл программы, находящейся внутри образа <em>tunnel</em>. Команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run</code> запускает контейнер из образа <em>tunnel</em>, внутри происходит компиляция программы и, в конечном итоге, она оказывается в директории <em>/usr/local/bin</em> основной файловой системы. Теперь можно стартовать программу, просто набрав <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tunnel</code> в терминале.</p>
<p>А что если программа, которую мы устанавливаем таким способом, имеет зависимости? Весь фокус в том, что образ, из которого был запущен контейнер, полностью укомплектован. Внутри него установлены все необходимые зависимости, и его запуск практически гарантирует 100% работоспособность, независимо от состояния основной ОС.</p>
<p>Часто даже необязательно копировать программу из контейнера на вашу основную систему. Достаточно запускать сам контейнер, когда в этом возникнет необходимость. Предположим, что мы решили разработать статический сайт на основе Jekyll. Jekyll — популярный генератор статических сайтов, написанный на Ruby. Например, сайт с <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://guides.hexlet.io/ru/" rel="noopener noreferrer" target="_blank">гайдами Хекслета</a> сгенерирован с его помощью. И при генерации использовался Докер (об этом можно прочитать в гайде: <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://guides.hexlet.io/ru/jekyll/" rel="noopener noreferrer" target="_blank">как делать блог на Jekyll</a>).</p>
<p>Старый способ использования Jekyll требовал установки на вашу основную систему как минимум Ruby и самого Jekyll в виде гема (gem — название пакетов в Ruby). Причем, как и всегда в подобных вещах, Jekyll работает только с определенными версиями Ruby, что вносит свои проблемы при настройке.</p>
<p>С Докером запуск Jekyll сводится к одной команде, выполняемой в директории с блогом (подробнее можно посмотреть в <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://github.com/hexletguides/hexletguides.github.io" rel="noopener noreferrer" target="_blank">репозитории</a> наших гайдов):</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker run --rm --volume="$PWD:/srv/jekyll" -it jekyll/jekyll jekyll server</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Точно таким же образом сейчас запускается огромное количество различного софта. Чем дальше, тем больше подобный способ захватывает мир.</p>
<h2 id="heading-2-1">Приложение в контейнере</h2>
<p>Теперь поговорим о том, как приложение отображается на контейнеры. Возможны два подхода:</p>
<ol>
<li>Всё приложение — один контейнер, внутри которого поднимается дерево процессов: приложение, веб сервер, база данных и всё в этом духе.</li>
<li>Каждый запущенный контейнер — атомарный сервис. Другими словами каждый контейнер представляет собой ровно одну программу, будь то веб-сервер или приложение.</li>
</ol>
<p>На практике все преимущества Docker достигаются только со вторым подходом. Во-первых, сервисы, как правило, разнесены по разным машинам и нередко перемещаются по ним (например, в случае выхода из строя сервера), во-вторых, обновление одного сервиса не должно приводить к остановке остальных.</p>
<p>Первый подход крайне редко, но бывает нужен. Например, Хекслет работает в двух режимах. Сам сайт с его сервисами использует вторую модель, когда каждый сервис отдельно, но вот практика, выполняемая в браузере, стартует по принципу "один пользователь — один контейнер". Внутри контейнера может оказаться всё что угодно в зависимости от практики. Как минимум, там всегда стартует сама среда Хекслет IDE, а она в свою очередь порождает терминалы (процессы). В курсе по базам данных в этом же контейнере стартует и база данных, в курсе, связанном с вебом, стартует веб-сервер. Такой подход позволяет создать иллюзию работы на настоящей машине и резко снижает сложность в поддержке упражнений. Повторюсь, что такой вариант использования очень специфичен и вам вряд ли понадобится.</p>
<p>Другой важный аспект при работе с контейнерами касается состояния. Например, если база запускается в контейнере, то её данные ни в коем случае не должны храниться там же, внутри контейнера. Контейнер как процесс операционной системы, может быть легко уничтожен, его наличие всегда временно. Docker содержит механизмы для хранения и использования данных, лежащих в основной файловой системе. О них будет позже.</p>
<h2 id="heading-2-2">Работа с образами</h2>
<p>Docker — больше, чем просто программа. Это целая экосистема со множеством проектов и сервисов. Главный сервис, с которым вам придется иметь дело — Registry. Хранилище образов.</p>
<p>Концептуально оно работает так же, как и репозиторий пакетов любого пакетного менеджера. Посмотреть его содержимое можно на сайте <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://hub.docker.com/" rel="noopener noreferrer" target="_blank">https://hub.docker.com/</a>.</p>
<p>Когда мы выполняем команду <em>run</em> <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run <image name></code>, то Docker проверяет наличие указанного образа на локальной машине и скачивает его по необходимости. Список образов, уже скачанных на компьютер, можно посмотреть командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker images</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
workshopdevops_web latest cfd7771b4b3a 2 days ago 817MB
hexletbasics_app latest 8e34a5f631ea 2 days ago 1.3GB
mokevnin/rails latest 96487c602a9b 2 days ago 743MB
ubuntu latest 2a4cca5ac898 3 days ago 111MB
Ruby 2.4 713da53688a6 3 weeks ago 687MB
Ruby 2.5 4c7885e3f2bb 3 weeks ago 881MB
nginx latest 3f8a4339aadd 3 weeks ago 108MB
elixir latest 93617745963c 4 weeks ago 889MB
postgres latest ec61d13c8566 5 weeks ago 287MB</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Разберёмся с тем, как формируется имя образа, и что оно в себя включает.</p>
<p>Вторая колонка в выводе выше называется TAG. Когда мы выполняли команду <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run nginx</code>, то на самом деле выполнялась команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run nginx:latest</code>. То есть мы не просто скачиваем образ <em>nginx</em>, а скачиваем его конкретную версию. Latest — тег по умолчанию. Несложно догадаться, что он означает последнюю версию образа.</p>
<p>Важно понимать, что это всего лишь соглашение, а не правило. Конкретный образ вообще может не иметь тега <em>latest</em>, либо иметь, но он не будет содержать последние изменения, просто потому, что никто их не публикует. Впрочем, популярные образы следуют соглашению. Как понятно из контекста, теги в Докере изменяемы. Другими словами, вам никто не гарантирует, что скачав образ с одним и тем же тегом на разных компьютерах в разное время вы получите одно и то же. Такой подход может показаться странным и ненадежным, ведь нет гарантий, но на практике есть определенные соглашения, которым следуют все популярные образы. Тег latest действительно всегда содержит последнюю версию и постоянно обновляется, но кроме этого тега активно используется <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://semver.org/" rel="noopener noreferrer" target="_blank">семантическое версионирование</a>. Рассмотрим <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://hub.docker.com/_/nginx" rel="noopener noreferrer" target="_blank">https://hub.docker.com/_/nginx</a></p>
<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">1.13.8, mainline, 1, 1.13, latest
1.13.8-perl, mainline-perl, 1-perl, 1.13-perl, perl
1.13.8-alpine, mainline-alpine, 1-alpine, 1.13-alpine, alpine
1.13.8-alpine-perl, mainline-alpine-perl, 1-alpine-perl, 1.13-alpine-perl, alpine-perl
1.12.2, stable, 1.12
1.12.2-perl, stable-perl, 1.12-perl
1.12.2-alpine, stable-alpine, 1.12-alpine
1.12.2-alpine-perl, stable-alpine-perl, 1.12-alpine-perl</code>
<p>Теги, в которых присутствует полная семантическая версия (x.x.x) всегда неизменяемы, даже если в них встречается что-то еще, например, <em>1.12.2-alphine</em>. Такую версию смело нужно брать для продакшен-окружения. Теги, подобные такому <em>1.12</em>, обновляются при изменении patch версии. То есть внутри образа может оказаться и версия <em>1.12.2</em> и в будущем <em>1.12.8</em>. Точно такая же схема и с версиями, в которых указана только мажорная версия, например, <em>1</em>. Только в данном случае обновление идет не только по патчу, но и по минорной версии.</p>
<p>Как вы помните, команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run</code> скачивает образ, если его нет локально, но эта проверка не связана с обновлением содержимого. Другими словами, если <em>nginx:latest</em> обновился, то <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run</code> его не будет скачивать, он использует тот <em>latest</em>, который прямо сейчас уже загружен. Для гарантированного обновления образа существует другая команда: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker pull</code>. Вот она всегда проверяет, обновился ли образ для определенного тега.</p>
<p>Кроме тегов имя образа может содержать префикс, например, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">etsy/chef</code>. Этот префикс является именем аккаунта на сайте, через который создаются образы, попадающие в Registry. Большинство образов как раз такие, с префиксом. И есть небольшой набор, буквально сотня образов, которые не имеют префикса. Их особенность в том, что эти образы поддерживает сам Docker. Поэтому если вы видите, что в имени образа нет префикса, значит это официальный образ. Список таких образов можно увидеть здесь: <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://github.com/docker-library/official-images/tree/master/library" rel="noopener noreferrer" target="_blank">https://github.com/docker-library/official-images/tree/master/library</a></p>
<p>Удаляются образы командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker rmi <imagename></code>.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker rmi Ruby:2.4
Untagged: Ruby:2.4
Untagged: Ruby@sha256:d973c59b89f3c5c9bb330e3350ef8c529753ba9004dcd1bfbcaa4e9c0acb0c82</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если в Докере присутствует хоть один контейнер из удаляемого образа, то Докер не даст его удалить по понятным причинам. Если вы всё же хотите удалить и образ и все контейнеры, связанные с ним, используйте флаг <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-f</code>.</p>
<h2 id="heading-2-3">Управление контейнерами</h2>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE1MSwicHVyIjoiYmxvYl9pZCJ9fQ==--36ccf1c4b7ca4ec3f0ecf4ec1e7f3e7f5d75208a/docker-container-lifecycle.png" alt="Docker Container LifeCycle" loading="lazy"/></p>
<p>Картинка описывает жизненный цикл (конечный автомат) контейнера. Кружками на нём изображены состояния, жирным выделены консольные команды, а квадратиками показывается то, что в реальности выполняется.</p>
<p>Проследите путь команды <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run</code>. Несмотря на то, что команда одна, с точки зрения работы Докера выполняется два действия: создание контейнера и запуск. Существуют и более сложные варианты исполнения, но в этом разделе мы рассмотрим только базовые команды.</p>
<p>Запустим nginx так, чтобы он работал в фоне. Для этого после слова <em>run</em> добавляется флаг <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-d</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker run -d -p 8080:80 nginx
431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>После выполнения команды Докер выводит идентификатор контейнера и возвращает управление. Убедитесь в том, что nginx работает, открыв в браузере ссылку <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">localhost:8080</code>. В отличие от предыдущего запуска, наш nginx работает в фоне, а значит не видно его вывода (логов). Посмотреть его можно командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker logs</code>, которой нужно передать идентификатор контейнера:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker logs 431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e
172.17.0.1 - - [19/Jan/2018:07:38:55 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" "-"</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Вы также можете подсоединиться к выводу лога в стиле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tail -f</code>. Для этого запустите <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker logs -f 431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e</code>. Теперь лог будет обновляться каждый раз, когда вы обновляете страницу в браузере. Выйти из этого режима можно набрав <kbd>Ctrl + C</kbd>, при этом сам контейнер остановлен не будет.</p>
<p>Теперь выведем информацию о запущенных контейнерах командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker ps</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
431a3b3fc24b nginx "nginx -g 'daemon of…" 2 minutes ago Up 2 minutes 80/tcp wizardly_rosalind</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Расшифровка столбиков:</p>
<ul>
<li>CONTAINER_ID — идентификатор контейнера. Так же, как и в git, используется сокращенная запись хеша.</li>
<li>IMAGE — имя образа, из которого был поднят контейнер. Если не указан тег, то подразумевается <em>latest</em>.</li>
<li>COMMAND — команда, которая выполнилась на самом деле при старте контейнера.</li>
<li>CREATED — время создания контейнера</li>
<li>STATUS — текущее состояние.</li>
<li>PORTS — проброс портов.</li>
<li>NAMES — алиас. Докер позволяет кроме идентификатора иметь имя. Так гораздо проще обращаться с контейнером. Если при создании контейнера имя не указано, то Докер самостоятельно его придумывает. В выводе выше как раз такое имя у nginx.</li>
</ul>
<p>(<em>Команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker stats</code> выводит информацию о том, сколько ресурсов потребляют запущенные контейнеры).</em></p>
<p>Теперь попробуем остановить контейнер. Выполним команду:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># Вместо CONTAINER_ID можно указывать имя
docker kill 431a3b3fc24b # docker kill wizardly_rosalind
431a3b3fc24b</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если попробовать набрать <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker ps</code>, то там этого контейнера больше нет. Он удален.</p>
<p>Команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker ps</code> выводит только запущенные контейнеры. Но кроме них могут быть и остановленные. Причём остановка может происходить как по успешному завершению, так и в случае ошибок. Попробуйте набрать <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run ubuntu ls</code>, а затем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run ubuntu bash -c "unknown"</code>. Эти команды не запускают долгоживущий процесс, они завершаются сразу после выполнения, причем вторая с ошибкой, так как такой команды не существует.</p>
<p>Теперь выведем все контейнеры командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker ps -a</code>. Первыми тремя строчками вывода окажутся:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
85fb81250406 ubuntu "bash -c unkown" Less than a second ago Exited (127) 3 seconds ago loving_bose
c379040bce42 ubuntu "ls" Less than a second ago Exited (0) 9 seconds ago determined_tereshkova</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Здесь как раз два последних наших запуска. Если посмотреть на колонку STATUS, то видно, что оба контейнера находятся в состоянии Exited. То есть запущенная команда внутри них выполнилась, и они остановились. Разница лишь в том, что один завершился успешно (0), а второй с ошибкой (127). После остановки контейнер можно даже перезапустить:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker start determined_tereshkova # В вашем случае будет другое имя</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Только в этот раз вы не увидите вывод. Чтобы его посмотреть, воспользуйтесь командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker logs determined_tereshkova</code>.</p>
<h2 id="heading-2-4">Взаимодействие с другими частями системы</h2>
<p>Запускать изолированный контейнер, который живёт весь внутри себя, малополезно. Как правило, контейнеру нужно взаимодействовать с внешним миром, принимать входящие запросы на определенный порт, выполнять запросы на другие сервисы, читать общие файлы и писать в них. Все эти возможности настраиваются при создании контейнера.</p>
<h3 id="heading-3-5">Interactive mode</h3>
<p>Самый простой вариант использования Докера, как мы уже убедились — поднять контейнер и выполнить внутри него какую-либо команду:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker run ubuntu ls /usr
bin
games
include
lib
local
sbin
share
src
$</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>После выполнения команды Docker возвращает управление, и мы снова находимся вне контейнера. Если попробовать точно так же запустить баш, то мы получим не то, что хотим:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker run ubuntu bash</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Дело в том, что <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">bash</code> запускает интерактивную сессию внутри контейнера. Для взаимодействия с ней нужно оставить открытым поток STDIN и запустить TTY (псевдо-терминал). Поэтому для запуска интерактивных сессий нужно не забыть добавить опции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-i</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-t</code>. Как правило, их добавляют сразу вместе как <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-it</code>. Поэтому правильный способ запуска баша выглядит так: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run -it ubuntu bash</code>.</p>
<h3 id="heading-3-6">Ports</h3>
<p>Если запустить nginx такой командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run nginx</code>, то nginx не сможет принять ни один запрос, несмотря на то, что внутри контейнера он слушает <em>80</em> порт (напомню, что каждый контейнер по умолчанию живёт в своей собственной сети). Но если запустить его так <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run -p 8080:80 nginx</code>, то nginx начнёт отвечать на порту <em>8080</em>.</p>
<p>Флаг <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-p</code> позволяет описывать, как и какой порт выставить наружу. Формат записи <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">8080:80</code> расшифровывается так: пробросить порт <em>8080</em> снаружи контейнера в контейнер на порт <em>80</em>. Причём по умолчанию порт <em>8080</em> слушается на <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">0.0.0.0</code>, то есть на всех доступных интерфейсах. Поэтому запущенный таким образом контейнер доступен не только через <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">localhost:8080</code>, но и снаружи машины (если доступ не запрещён как-нибудь ещё). Если нужно выполнить проброс только на <em>loopback</em>, то команда меняется на такую: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run -p 127.0.0.1:8080:80 nginx</code>.</p>
<p>Docker позволяет пробрасывать столько портов, сколько нужно. Например, в случае nginx часто требуется использовать и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">80</code> порт и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">443</code> для HTTPS. Сделать это можно так: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run -p 80:80 -p 443:443 nginx</code> Про остальные способы пробрасывать порты можно прочитать в официальной документации.</p>
<h3 id="heading-3-7">Volumes</h3>
<p>Другая частая задача связана с доступом к основной файловой системе. Например, при старте nginx-контейнера ему можно указать конфигурацию, лежащую на основной фс. Докер прокинет её во внутреннюю фс, и nginx сможет её читать и использовать.</p>
<p>Проброс осуществляется с помощью опции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-v</code>. Вот как можно запустить баш сессию из образа Ubuntu, подключив туда историю команд с основной файловой системы: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run -it -v ~/.bash_history:/root/.bash_history ubuntu bash</code>. Если в открытом баше понажимать стрелку вверх, то отобразится история. Пробрасывать можно как файлы, так и директории. Любые изменения производимые внутри volume меняются как внутри контейнера, так и снаружи, причём по умолчанию доступны любые операции. Как и в случае портов, количество пробрасываемых файлов и директорий может быть любым.</p>
<p>При работе с Volumes есть несколько важных правил, которые надо знать:</p>
<ul>
<li>Путь до файла во внешней системе должен быть абсолютным.</li>
<li>Если внутренний путь (то, что идёт после <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">:</code>) не существует, то Докер создаст все необходимые директории и файлы. Если существует, то заменит старое тем, что было проброшено.</li>
</ul>
<p>Кроме пробрасывания части фс снаружи Докер предоставляет ещё несколько вариантов создания и использования Volumes. Подробнее — в официальной документации.</p>
<h3 id="heading-3-8">Переменные окружения</h3>
<p>Конфигурирование приложения внутри контейнера, как правило, осуществляется с помощью переменных окружения в соответствии с <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://12factor.net/ru/" rel="noopener noreferrer" target="_blank">12factors</a>. Существует два способа их установки:</p>
<ul>
<li>Флаг <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-e</code>. Используется он так: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run -it -e "HOME=/tmp" ubuntu bash</code></li>
<li>Специальный файл, содержащий определения переменных окружения, который пробрасывается внутрь контейнера опцией <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">--env-file</code>.</li>
</ul>
<h2 id="heading-2-9">Подготовка собственного образа</h2>
<p>Создание и публикация собственного образа не сложнее его использования. Весь процесс делится на три шага:</p>
<ul>
<li>Создается файл <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Dockerfile</code> в корне проекта. Внутри описывается процесс создания образа.</li>
<li>Выполняется сборка образа командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker build</code></li>
<li>Выполняется публикация образа в Registry командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker push</code></li>
</ul>
<p>Рассмотрим процесс создания образа на примере упаковки линтера <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">eslint</code> (не забудьте повторить его самостоятельно). В результате сборки мы получим образ, который можно использовать так:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker run -it -v /path/to/js/files:/app my_account_name/eslint
/app/index.js
3:6 error Parsing error: Unexpected token
1 | import path from 'path';
2 |
> 3 | path(;)
| ^
4 |
✖ 1 problem (1 error, 0 warnings)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>То есть достаточно запустить контейнер из этого образа, подключив каталог с файлами js для проверки как Volume во внутреннюю директорию <em>/app</em>.</p>
<h3 id="heading-3-10">1. Конечная структура директории, на основе файлов которой соберется образ, выглядит так:</h3>
<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">eslint-docker/
Dockerfile
eslintrc.yml</code>
<p>Файл <em>eslintrc.yml</em> содержит конфигурацию линтера. Он автоматически будет прочитан, если лежит в домашней директории под именем <em>.eslintrc.yml</em>. То есть этот файл должен попасть под таким именем в директорию <em>/root</em> внутрь образа.</p>
<h3 id="heading-3-11">2. Создание Dockerfile</h3>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># Dockerfile
FROM node:9.3
WORKDIR /usr/src
RUN npm install -g eslint babel-eslint
RUN npm install -g eslint-config-airbnb-base eslint-plugin-import
COPY eslintrc.yml /root/.eslintrc.yml
CMD ["eslint", "/app"]</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Dockerfile имеет довольно простой формат. На каждой строчке указывается инструкция (директива) и её описание.</p>
<h4 id="heading-4-12">FROM</h4>
<p>Инструкция FROM нужна для указания образа, от которого происходит наследование. Здесь необходимо оговориться, что образы строятся на базе друг друга и все вместе образуют большое дерево.</p>
<p>В корне этого дерева находится образ <em>busybox</em>. В прикладных задачах напрямую его не используют, так как Докером предоставляются подготовленные образы под каждую экосистему и стек.</p>
<h4 id="heading-4-13">RUN</h4>
<p>Основная инструкция в Dockerfile. Фактически здесь указывается <em>sh</em> команда, которая будет выполнена в рамках окружения, указанного во FROM при сборке образа. Так как по умолчанию всё выполняется от пользователя <em>root</em>, то использовать <em>sudo</em> не нужно (и скорее всего его нет в базовом образе). К тому же учтите, что сборка образа — процесс не интерактивный. В тех ситуациях, когда вы используете команду, которая может запросить что-то от пользователя, необходимо подавлять этот вывод. Например, в случае пакетных менеджеров делают так: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">apt-get install -y curl</code>. Флаг <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-y</code> как раз говорит о том, что нужно производиться установку без дополнительных вопросов.</p>
<p>Технически образ Докера — это не один файл, а набор так называемых <em>слоев</em>. Каждый вызов RUN формирует новый слой, который можно представить как набор файлов, созданных и измененных (в том числе удаленных) командой, указанной в RUN. Такой подход позволяет значительно улучшить производительность системы, задействовав кеширование слоев, которые не поменялись. С другой стороны, Докер переиспользует слои в разных образах если они идентичны, что сокращает и скорость загрузки и занимаемое пространство на диске. Тема кеширования слоев довольно важная при активном использовании Докера. Для её эффективной работы нужно понимать, как она устроена и как правильно описывать инструкции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">RUN</code> для максимальной утилизации.</p>
<h4 id="heading-4-14">COPY</h4>
<p>В соответствии со своим названием команда COPY берёт файл или директорию из основной файловой системы и копирует её внутрь образа. У команды есть ограничение. То, что копируется, должно лежать в той же директории, где и Dockerfile. Именно эту команду используют при разработке когда необходимо упаковать приложение внутрь образа.</p>
<h4 id="heading-4-15">WORKDIR</h4>
<p>Инструкция, устанавливающая рабочую директорию. Все последующие инструкции будут считать, что они выполняются именно внутри неё. Инструкция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">WORKDIR</code> действует, как команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cd</code>. Кроме того, когда мы запускаем контейнер, то он также стартует из рабочей директории. Например, запустив bash, вы окажетесь внутри неё.</p>
<h4 id="heading-4-16">CMD</h4>
<p>Та самая инструкция, определяющая действие по умолчанию при использовании <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run</code>. Она используется только в том случае, если контейнер был запущен без указания команды, иначе она игнорируется.</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE1MiwicHVyIjoiYmxvYl9pZCJ9fQ==--404a417eed4745d485348a3dc45795a84adff1e5/docker.png" alt="Docker" loading="lazy"/></p>
<h3 id="heading-3-17">3. Сборка</h3>
<p>Для сборки образа используется команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker build</code>. С помощью флага <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-t</code> передается имя образа, включая имя аккаунта и тег. Как обычно, если не указывать тег, то подставляется <em>latest</em>.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker build -t my_account_name/eslint .</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>После выполнения данной команды вы можете увидеть текущий образ в списке <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker images</code>. Вы даже можете начать его использовать без необходимости публикации в Registry. Напомню, что команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker run</code> не пытается искать обновленную версию образа, если локально есть образ с таким именем и тегом.</p>
<h3 id="heading-3-18">4. Публикация</h3>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">docker push my_account_name/eslint</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Для успешного выполнения публикации нужно соблюсти два условия:</p>
<ul>
<li>Зарегистрироваться на Docker Cloud и создать там репозиторий для образа.</li>
<li>Залогиниться в cli интерфейсе используя команду <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">docker login</code>.</li>
</ul></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/devops-local-setup/lessons/docker/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 7</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/devops-local-setup/lessons/docker/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>