Урок 6. Свой образ: Dockerfile и docker build
Готовых образов много, но свою программу придётся упаковать самому. Рецепт образа записывают в файл Dockerfile: с какого образа начать, какие файлы положить, что установить и какую команду запустить. По рецепту команда docker build собирает образ.
Самый простой Dockerfile
Даже тестовый образ hello-world собран по Dockerfile — вот он целиком:
FROM scratch
COPY hello /
CMD ["/hello"]FROM scratch — начать с пустого образа, COPY — положить в корень готовую программу hello, CMD — запускать её. Обычно образы начинают не с пустоты, а с готовой основы.
Программа для примера
Создайте папку проекта и в ней файл app.py — маленький веб-сервер на Python без сторонних библиотек:
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = "Привет из контейнера!\n".encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
print("Сервер запущен на порту 8000")
HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()Рядом — файл Dockerfile (именно так, с большой буквы и без расширения):
FROM python:3.13-slim
WORKDIR /app
COPY app.py .
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["python", "app.py"]Инструкции по строкам
| Инструкция | Что делает |
|---|---|
FROM python:3.13-slim | основа — официальный образ с Python 3.13; slim — урезанный вариант без лишних программ |
WORKDIR /app | рабочая папка внутри образа; создаётся, если её нет. Дальнейшие команды и относительные пути — от неё |
COPY app.py . | скопировать файл из папки проекта в рабочую папку образа (точка — это /app) |
ENV PYTHONUNBUFFERED=1 | переменная окружения; эта заставляет Python сразу выводить текст, иначе docker logs покажет его с задержкой |
EXPOSE 8000 | отметка, что программа слушает порт 8000. Порт она не публикует — это делает -p при запуске |
CMD ["python", "app.py"] | команда по умолчанию при запуске контейнера |
Ещё две инструкции нужны почти в каждом Dockerfile:
RUN команда— выполнить команду при сборке и сохранить результат в образе: поставить пакеты, собрать программу, создать папку;COPY . .— скопировать всю папку проекта. Что копировать не нужно, перечисляют в файле.dockerignore(справка Файл .dockerignore).
Не путайте RUN и CMD. RUN выполняется один раз, когда собирается образ. CMD ничего не делает при сборке — он лишь записывает, что запускать, когда из образа создают контейнер. Чем CMD отличается от ENTRYPOINT — в справке Чем отличаются CMD и ENTRYPOINT.
Сборка
В папке проекта выполните:
docker build -t hello-web:1.0 .-t hello-web:1.0— имя и тег будущего образа;.в конце — контекст сборки: папка, из которойCOPYберёт файлы. Точку часто забывают — без неё сборка не начнётся.
Docker по очереди выполнит инструкции и в конце напишет, что образ готов. Проверьте docker images — там появится hello-web с тегом 1.0. Запуск — как у любого образа:
docker run -d --name hello -p 8000:8000 hello-web:1.0$ curl http://localhost:8000
Привет из контейнера!docker logs hello покажет строку Сервер запущен на порту 8000 и запросы к серверу.
Почему 0.0.0.0, а не 127.0.0.1
Самая частая ошибка своих образов: программа слушает 127.0.0.1 (или localhost). Внутри контейнера это адрес самого контейнера, а подключения через -p приходят на его сетевой адрес. В итоге контейнер работает, порт опубликован, а браузер получает обрыв соединения. Программа в контейнере должна слушать 0.0.0.0 — все адреса. У веб-фреймворков (готовых основ для сайтов и веб-приложений) это обычно отдельный параметр, например --host 0.0.0.0.
RUN и установка пакетов
Пример с программой из пакетов системы и библиотеками Python:
FROM python:3.13-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]apt-get update, установка и очистка — в одной инструкцииRUN. Иначе Docker может взять из кэша старый список пакетов, а удаление списка в отдельной строке не уменьшит образ;-y— не спрашивать подтверждения: при сборке ответить некому;- в
requirements.txt— библиотеки проекта, по одной в строке.
Слои и кэш
Каждая инструкция создаёт слой. При повторной сборке Docker берёт из кэша все слои до первой изменившейся инструкции, а всё после неё собирает заново. Поэтому порядок строк важен:
- сначала то, что меняется редко: основа, системные пакеты;
- потом список библиотек (
COPY requirements.txt .) и их установка; - в конце — код программы (
COPY . .), который меняется постоянно.
Тогда после правки кода сборка занимает секунды: библиотеки берутся из кэша. Если копировать весь код до pip install, библиотеки будут ставиться заново при каждой правке.
Образ не обновляется сам. Поменяли код — пересоберите образ (docker build) и пересоздайте контейнер (docker rm -f hello, затем снова docker run). Старый контейнер так и работает со старым образом.
Теги
Тег отличает версии одного образа. Один образ может носить несколько тегов:
docker build -t hello-web:1.1 -t hello-web:latest .
docker tag hello-web:1.1 hello-web:stableЧтобы отправить образ в реестр, его имя должно начинаться с имени учётной записи или адреса реестра. Для Docker Hub:
docker login
docker tag hello-web:1.1 ivanov/hello-web:1.1
docker push ivanov/hello-web:1.1Здесь ivanov — имя учётной записи на Docker Hub. Публичный образ видят все, поэтому пароли и ключи в образ не кладут никогда: из образа их достаёт любой.
Частые ошибки
- Забыта точка в
docker build— Docker напишет, что ожидает один аргумент. - Сборка падает на
COPYс ошибкойnot found— файла нет в контексте сборки или он исключён в.dockerignore. Пути вCOPY— относительно папки контекста, выше неё выйти нельзя. - Контейнер работает, а страница не открывается — программа слушает
127.0.0.1вместо0.0.0.0или порт в-pуказан не тот. - В логах пусто — Python держит вывод в буфере: нужна строка
ENV PYTHONUNBUFFERED=1или запускpython -u. - Правка кода не видна — образ не пересобран или контейнер не пересоздан.
Коротко
- Dockerfile — рецепт образа;
docker build -t имя:тег .собирает образ из папки-контекста. FROM— основа,WORKDIR— рабочая папка,COPY— файлы,RUN— команды при сборке,CMD— команда при запуске.EXPOSEтолько отмечает порт; публикует его-pпри запуске.- Программа в контейнере слушает
0.0.0.0. - Редко меняющееся — выше, код — ниже: так работает кэш слоёв.
- После правки кода — пересборка образа и пересоздание контейнера.