Dockerfile — текстовый рецепт сборки образа: какую основу взять, какие файлы скопировать и какую команду запускать. Образ хранит подготовленное окружение; контейнер запускает программу из этого образа. Ниже соберём небольшое HTTP-приложение, получим JSON через curl и проверим, какие шаги повторяются после изменения кода.
Пример рассчитан на локальное обучение. Нужен работающий Docker; команды выполняются в терминале Linux, macOS или WSL. Python на компьютере устанавливать не нужно: он будет внутри образа.
Dockerfile собирает образ, docker run запускает процесс
docker run.Подготовить приложение и Dockerfile
Создай пустую папку dockerfile-demo. В ней будут четыре файла:
dockerfile-demo/
├── app.py
├── requirements.txt
├── Dockerfile
└── .dockerignore
В app.py сохрани приложение с одним адресом /:
from flask import Flask
app = Flask(__name__)
@app.get("/")
def hello():
return {"message": "Hello, Koddo!"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Содержимое requirements.txt:
Flask==3.1.2
Создай Dockerfile без расширения:
FROM python:3.14-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
USER 10001
EXPOSE 8000
CMD ["python", "app.py"]
Инструкции читаются сверху вниз:
| Инструкция | Что происходит в примере |
|---|---|
FROM | Берёт базовый образ с Python |
WORKDIR | Делает /app рабочей папкой следующих команд |
COPY | Копирует файл с компьютера в образ |
RUN | Устанавливает Flask во время сборки |
USER | Задаёт числовой UID 10001 для запуска приложения без root |
EXPOSE | Описывает порт приложения; сам порт не публикует |
CMD | Задаёт команду по умолчанию для запуска контейнера |
Различия между инструкциями закреплены в справочнике Dockerfile. Сервер слушает 0.0.0.0 внутри контейнера, чтобы принимать запросы через его сетевой интерфейс. Доступ с компьютера настроим отдельно через -p.
Контекст сборки и .dockerignore
В .dockerignore запиши:
.git
.venv
__pycache__
*.pyc
.env
.env.*
Контекст — набор файлов, доступных сборщику. В команде docker build ... . последняя точка означает текущую папку. COPY app.py . берёт исходник относительно этого контекста, а помещает в /app, заданную через WORKDIR.
.dockerignore исключает лишние файлы из контекста. Это особенно полезно, если позже появится COPY . .: локальное окружение и файлы .env не попадут в образ. Пароль, уже записанный в app.py, исключение .env не защитит. Правила контекста и исключений описаны в документации Docker.
Собрать образ и запустить контейнер
Перейди в dockerfile-demo и выполни:
docker build -t koddo-hello:1 .
docker run -d --name koddo-hello -p 127.0.0.1:8000:8000 koddo-hello:1
curl --fail http://127.0.0.1:8000/
Когда сервер запустится, ответ будет таким:
{"message":"Hello, Koddo!"}
-t назначает образу имя и тег. -d запускает контейнер в фоне, --name задаёт его имя. Адрес 127.0.0.1:8000:8000 публикует порт 8000 контейнера на порту 8000 только локального интерфейса компьютера. Если этот порт занят, замени левый номер на 8001 и обращайся к http://127.0.0.1:8001/.
Если запрос пришёл раньше запуска сервера, посмотри логи и повтори curl:
docker logs koddo-hello
Flask сообщит, что используется development server. Для учебного запуска это ожидаемо; для сайта с пользователями нужен сервер WSGI, например Gunicorn или Waitress. Контейнеризация сама по себе не снимает это ограничение Flask.
RUN, CMD и ENTRYPOINT: когда выполняется команда
RUN выполняется при сборке и меняет содержимое образа. Если написать RUN python app.py, сборка застрянет на работающем сервере. CMD сохраняет команду для будущего запуска; сборщик её не выполняет.
Команду CMD можно заменить аргументами после имени образа:
docker run --rm koddo-hello:1 python --version
Этот контейнер напечатает версию Python и завершится, не запуская app.py. Флаг --rm удалит его после выполнения команды.
Если образ предназначен для одной программы, можно заменить последнюю строку Dockerfile на две:
ENTRYPOINT ["python"]
CMD ["app.py"]
Тогда docker run IMAGE --version запустит python --version: аргументы заменят CMD, а ENTRYPOINT останется. Это альтернативная конфигурация; для основного примера оставь исходный CMD. Для принудительной замены ENTRYPOINT существует флаг --entrypoint.
Форма со списком в двойных кавычках запускает программу напрямую, без дополнительной оболочки. Правила совместной работы CMD и ENTRYPOINT приведены в документации Docker.
Что пересобирается после изменения кода
Замени Hello, Koddo! в app.py на Hello again!, затем собери образ с новым тегом:
docker build --progress=plain -t koddo-hello:2 .
В проверочной сборке шаг RUN pip install --no-cache-dir -r requirements.txt получил отметку CACHED, а COPY app.py . выполнился заново. Причина в порядке инструкций: файл зависимостей скопирован и обработан до исходника приложения. Если сначала выполнить COPY . ., изменение app.py затронет и следующую установку зависимостей.
Теперь добавь в конец requirements.txt комментарий # cache check и повтори сборку. Даже без смены версии Flask содержимое файла изменилось: шаг копирования и следующий RUN pip install выполнятся заново. Правила проверки файлов и повторного использования слоёв описаны в документации кеша Docker.
--no-cache-dir отключает файловый кеш pip, а не кеш слоёв Docker. Для полной пересборки шагов Docker есть docker build --no-cache; скачивание новой версии базового образа отдельно запрашивается флагом --pull.
Работающий контейнер продолжает использовать старый образ. Чтобы увидеть новый ответ, пересоздай его:
docker stop koddo-hello
docker rm koddo-hello
docker run -d --name koddo-hello -p 127.0.0.1:8000:8000 koddo-hello:2
curl --fail http://127.0.0.1:8000/
После запуска ответ изменится на {"message":"Hello again!"}. В этом примере нет bind mount: редактирование файла на компьютере не меняет уже созданный контейнер.
Почему один тег не гарантирует одинаковый образ
python:3.14-slim может со временем указывать на обновлённый образ. При проверке использовался Python 3.14.6. Точная версия Flask закреплена, но его транзитивные зависимости в этом учебном requirements.txt не зафиксированы.
Для воспроизводимой сборки приложения закрепляют базовый образ по digest и полное дерево зависимостей с контрольными суммами. Обновления затем делают явно, чтобы получать исправления безопасности. Docker разбирает компромисс между обновлением образов и закреплением digest в рекомендациях по сборке.
По окончании упражнения останови и удали контейнер:
docker stop koddo-hello
docker rm koddo-hello
Дальше добавь к приложению PostgreSQL по руководству Docker Compose. Для автоматической сборки образа после изменения кода пригодится разбор CI/CD.