dicker dockerfile

Как собирается образ

Образ собирается слоями, сверху вниз. Docker читает Dockerfile строку за строкой и выполняет каждую инструкцию, а каждая инструкция добавляет новый слой поверх предыдущих. Так, шаг за шагом, и набирается готовый образ.

Зачем вообще знать про слои, станет ясно чуть ниже, когда дойдем до кеширования: именно от слоев зависит скорость сборки.

Главные инструкции

Этих пяти инструкций достаточно для большинства приложений.

Упаковываем Python-приложение

Допустим, у нас простое веб-приложение на Python:

FROM python:3.12
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "app.py"]

Разберем по строкам.

  1. FROM python.12 берет за основу готовый образ с уже установленным Python 3.12. Систему с нуля мы не собираем, внутри этого образа уже есть Linux и Python, нам остается добавить свое приложение. FROM всегда стоит первой инструкцией в Dockerfile.

  2. WORKDIR /app задает рабочую папку внутри образа, и все следующие команды выполняются относительно нее. По сути это cd /app на всю оставшуюся сборку. Если папки нет, Docker ее создаст.

  3. COPY requirements.txt . копирует файл со списком зависимостей с вашего компьютера внутрь образа, в текущую папку /app, на которую указывает точка. Сам requirements.txt – это перечень библиотек, нужных приложению.

  4. RUN pip install -r requirements.txt выполняет установку зависимостей прямо во время сборки образа, и после этого все нужные библиотеки зашиты внутрь. RUN срабатывает один раз при сборке, а не при запуске.

  5. COPY . . копирует весь остальной код приложения: первая точка означает «откуда», из текущего каталога на вашем компьютере, вторая – «куда», в /app внутри образа.

  6. EXPOSE 8080 объявляет, что приложение внутри слушает порт 8080. Это подсказка о том, на каком порту работает приложение. Сам порт она не открывает, но помогает понять, что пробрасывать наружу.

  7. CMD [“python”, “app.py”] задает, что запустить, когда контейнер стартует. В отличие от RUN, который выполняется при сборке, CMD срабатывает при запуске контейнера.

Почему COPY в два захода

Обратите внимание: requirements.txt копируется отдельной строкой, а весь остальной код – позже, отдельным COPY. Это сделано не для красоты, а ради кеша.

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

Дальше работает простое наблюдение: зависимости меняются редко, а код правят постоянно. Поэтому зависимости копируются и ставятся первыми, и этот тяжелый шаг оседает в кеше, а код кладется уже после него. Когда вы в очередной раз поправили код, Docker не переустанавливает библиотеки заново, ведь их слой не тронут. На больших проектах это экономит минуты на каждой сборке.

Собираем образ

Когда Dockerfile готов, собираем образ:

docker build -t myapp .
Часть командыЧто делает
docker buildсобирает образ из Dockerfile
-t myappназывает (tag) его myapp
.указывает, что Dockerfile искать в текущей папке

Docker прочитает Dockerfile, выполнит все инструкции по порядку, и на выходе получится образ с именем myapp. Проверить, что он появился:

docker images

Запускаем

Свой образ запускается так же, как раньше запускался nginx:

docker run -d -p 8080:8080 myapp

Приложение работает в контейнере. Этот контейнер можно запустить на любой машине с Docker, на сервере, на ноутбуке коллеги, в облаке, и везде он заведется одинаково, потому что все окружение зашито внутрь образа.

Что еще бывает в Dockerfile

Кроме основных пяти, есть и другие инструкции, которые встретятся позже: ENV для переменных окружения, ARG для аргументов сборки, ENTRYPOINT (похоже на CMD, но со своими нюансами), USER для выбора пользователя, под которым идет запуск, VOLUME для данных.