- Регистрация
- 7 Авг 2025
- Сообщения
- 19
- Реакции
- 0
- Баллы
- 1
- Возраст
- 31
Если ты пишешь Dockerfile, скорее всего, он работает. Но вопрос не в том, работает ли. Вопрос во втором: будет ли он работать через неделю, на втором сервере, в CI/CD, на чужом железе — и будет ли это безопасно. Или все сломается, потому что ты не зафиксировал зависимости, положился на последний, и забыл о том, что ENTRYPOINT — это тоже код.
В этой статье я роскажу как собрать нормальный Docker-образ, который предсказуем, устойчив и готов к продакшену.
1. Первая ошибка: ты начинаешь с плохой базы
Многие берут базовый образ, не задумываясь. Например, python:3.12. Это "толстый" образ с кучей ненужных пакетов. Он может весить 1+ ГБ. Там куча системных библиотек, что увеличивает потенциальную поверхность для атак и делает билды медленнее.
Плохой Dockerfile:
FROM python:3.12
WORKDIR /app
COPY. .
RUN pip install -r requirements.txt
CMD ["python", "main.py"]
Что здесь не так:
Образ велик.
Все слои кэша сбиваются при любом изменении.
Ты клонируешь весь проект внутрь, включая мусор.
Как лучше:
Брать python:3.12-slim или python:3.12-alpine, но только если понимаешь, как с ним работать и зачем ты его выбираешь.
Использовать многоступенчатую сборку: сначала билд, потом перенос нужного в "чистую" фазу.
Указывать чёткую структуру слоев: сначала зависимости, потом код.
2. Не используй latest
latest - это ловушка. Сегодня образ один, завтра он обновится, и все сломается. Причем сломается неожиданно – у тебя в проде или в CI.
Пример:
FROM node:latest
В понедельник это Node 18. В среду уже 20. И твой build падает, потому что какой-то пакет несовместим.
Лучше указать конкретную версию:
FROM node:18.16.1
Или даже с sha256:
FROM node@sha256:<digest>
Это не просто паранойя. Это способ зафиксировать окружение. Больше контроля – меньше сюрпризов.
3. Пример хорошего Dockerfile для Python‑приложения
FROM python:3.12.1-slim as builder
WORKDIR /install
RUN apt-get update && apt-get install -y build-essential
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip wheel --no-deps --wheel-dir /wheels -r requirements.txt
FROM python:3.12.1-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY --from=Builder /wheels /wheels
COPY requirements.txt .
RUN pip install --no-deps --no-index --find-links=/wheels -r requirements.txt
COPY. .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Этот Dockerfile делает две вещи: сначала собирает в зависимости, потом создает чистый образ с приложением. Так получается меньше мусора и быстрее сборка.
Первая часть – сборка зависимостей:
Берем легкий образ Python 3.12.1
Ставим build-essential – он нужен для компиляции некоторых пакетов
Копируем файл requirements.txt
Собираем зависимости в wheel-файлы (это как zip-архивы для Python-пакетов)
Фишка в том, что build-essential останется только в этой временной части и не попадет в итоговый образ.
Вторая часть – финальный образ:
Снова берём чистый Python 3.12.1 без лишнего
Настраиваем две важные переменные:
PYTHONDONTWRITEBYTECODE — чтобы не создавались .pyc-файлы
PYTHONUNBUFFERED — чтобы логи выводились сразу
Копируем wheel-файлы из первой части
Ставим зависимости из этих файлов (уже без интернета и компиляции)
Копируем само приложение
Запускаем uvicorn на порту 8000
Почему так лучше:
Итоговый образ меньше весит
Не нужно компилировать пакеты при каждом запуске
Сборка идет быстрее счёт кеширования wheel-файлов
Нет лишних пакетов типа build-essential в финальном образе
Если нужно добавить что-нибудь в образ (например, curl для healthcheck), делай это перед последним COPY. Но помни – каждый RUN добавляет слой к образу.
4. .dockerignore - must-have
Без .dockerignore ты случайно кладешь в образ лишнее:
.git
pycache
.env
.vscode/
node_modules/
и т.д. и т.п.
Это увеличивает размер, сбивает кэш и вообще плохо.
Пример .dockerignore:
.git
__pycache__/
*.pyc
.env
.vscode/
node_modules/
*.log
Сделай это один раз – и забудешь о проблемах.
5. Volume: будь осторожен
Проблема: когда ты монтируешь volume, он затирает всё внутри контейнера.
docker run -v $(pwd):/app myapp
В результате:
Все, что ты собрал – перезаписано.
Права доступа могут сломать работу.
Что делать:
Используй именованные volume:
docker volume create mydata
docker run -v mydata:/data myapp
Пропиши правильные права заранее.
6. CMD и ENTRYPOINT
Нюанс: CMD подменяется, ENTRYPOINT – остается.
Пример:
CMD ["python", "app.py"]
Ты запустил:
docker run myapp bash
А получил: python app.py bash
Правильно да:
ENTRYPOINT ["python", "app.py"]
CMD ["--debug"]
Теперь все работает предсказуемо.
7. USER: не запускай все от root
По умолчанию Docker запускается от root. Это небезопасно. Особенно если есть volume или доступ к сокету.
Добавь:
RUN useradd -m myuser
USER myuser
Теперь все работает от безопасного пользователя. И если кто-нибудь сломает контейнер, он не получит root.
8. Healthcheck
Docker считает, что контейнер жил, если он просто не умер. Но твой сервис может зависнуть или вернут 500.
Добавить healthcheck:
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
Теперь Docker будет знать, когда сервис реально жил.
9. Кэш Docker
Слои в Docker кэшируются. Если ты пишешь слои неправильно, кэш не работает.
Плохо:
COPY. .
RUN pip install -r requirements.txt
Лучше:
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY. .
Теперь, когда ты меняешь только код, зависимости не ставятся заново.
10. Docker в CI
Частые ошибки:
Пуш latest без тега.
Кладут частные ключи в образ.
Не используют .dockerignore.
Как надо:
Указывать метки явно: myapp:1.2.3
Не храните секреты в образе. использовать secrets или переменные окружения.
Убедится, что .dockerignore есть и рабочий.
11. Уменьшение размера вида
Каждый MB важен, особенно в CI/CD. Что можно сделать:
Убирать временные пакеты после использования.
RUN apt-get install -y gcc && pip install some-lib && apt-get remove -y gcc
Использовать --no-cache и чистить /tmp.
Применят slim, а не full-образы.
12. Alpine - не серебряная пуля
Да, вон маленький. Но:
Использует musl, а не glibc.
Проблемы с совместимостью C-библиотек.
Долгий билд Python-пакетов.
Используй Alpine только если ты понимаешь, зачем он тебе нужен. В остальных случаях – slim.
Итоги:
Сборка Docker-образа — это не только про "работает ли у меня". Это про устойчивость, безопасность и предсказуемость. Маленькие ошибки приводят к большим проблемам.
В этой статье я роскажу как собрать нормальный Docker-образ, который предсказуем, устойчив и готов к продакшену.
1. Первая ошибка: ты начинаешь с плохой базы
Многие берут базовый образ, не задумываясь. Например, python:3.12. Это "толстый" образ с кучей ненужных пакетов. Он может весить 1+ ГБ. Там куча системных библиотек, что увеличивает потенциальную поверхность для атак и делает билды медленнее.
Плохой Dockerfile:
FROM python:3.12
WORKDIR /app
COPY. .
RUN pip install -r requirements.txt
CMD ["python", "main.py"]
Что здесь не так:
Образ велик.
Все слои кэша сбиваются при любом изменении.
Ты клонируешь весь проект внутрь, включая мусор.
Как лучше:
Брать python:3.12-slim или python:3.12-alpine, но только если понимаешь, как с ним работать и зачем ты его выбираешь.
Использовать многоступенчатую сборку: сначала билд, потом перенос нужного в "чистую" фазу.
Указывать чёткую структуру слоев: сначала зависимости, потом код.
2. Не используй latest
latest - это ловушка. Сегодня образ один, завтра он обновится, и все сломается. Причем сломается неожиданно – у тебя в проде или в CI.
Пример:
FROM node:latest
В понедельник это Node 18. В среду уже 20. И твой build падает, потому что какой-то пакет несовместим.
Лучше указать конкретную версию:
FROM node:18.16.1
Или даже с sha256:
FROM node@sha256:<digest>
Это не просто паранойя. Это способ зафиксировать окружение. Больше контроля – меньше сюрпризов.
3. Пример хорошего Dockerfile для Python‑приложения
FROM python:3.12.1-slim as builder
WORKDIR /install
RUN apt-get update && apt-get install -y build-essential
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip wheel --no-deps --wheel-dir /wheels -r requirements.txt
FROM python:3.12.1-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY --from=Builder /wheels /wheels
COPY requirements.txt .
RUN pip install --no-deps --no-index --find-links=/wheels -r requirements.txt
COPY. .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Этот Dockerfile делает две вещи: сначала собирает в зависимости, потом создает чистый образ с приложением. Так получается меньше мусора и быстрее сборка.
Первая часть – сборка зависимостей:
Берем легкий образ Python 3.12.1
Ставим build-essential – он нужен для компиляции некоторых пакетов
Копируем файл requirements.txt
Собираем зависимости в wheel-файлы (это как zip-архивы для Python-пакетов)
Фишка в том, что build-essential останется только в этой временной части и не попадет в итоговый образ.
Вторая часть – финальный образ:
Снова берём чистый Python 3.12.1 без лишнего
Настраиваем две важные переменные:
PYTHONDONTWRITEBYTECODE — чтобы не создавались .pyc-файлы
PYTHONUNBUFFERED — чтобы логи выводились сразу
Копируем wheel-файлы из первой части
Ставим зависимости из этих файлов (уже без интернета и компиляции)
Копируем само приложение
Запускаем uvicorn на порту 8000
Почему так лучше:
Итоговый образ меньше весит
Не нужно компилировать пакеты при каждом запуске
Сборка идет быстрее счёт кеширования wheel-файлов
Нет лишних пакетов типа build-essential в финальном образе
Если нужно добавить что-нибудь в образ (например, curl для healthcheck), делай это перед последним COPY. Но помни – каждый RUN добавляет слой к образу.
4. .dockerignore - must-have
Без .dockerignore ты случайно кладешь в образ лишнее:
.git
pycache
.env
.vscode/
node_modules/
и т.д. и т.п.
Это увеличивает размер, сбивает кэш и вообще плохо.
Пример .dockerignore:
.git
__pycache__/
*.pyc
.env
.vscode/
node_modules/
*.log
Сделай это один раз – и забудешь о проблемах.
5. Volume: будь осторожен
Проблема: когда ты монтируешь volume, он затирает всё внутри контейнера.
docker run -v $(pwd):/app myapp
В результате:
Все, что ты собрал – перезаписано.
Права доступа могут сломать работу.
Что делать:
Используй именованные volume:
docker volume create mydata
docker run -v mydata:/data myapp
Пропиши правильные права заранее.
6. CMD и ENTRYPOINT
Нюанс: CMD подменяется, ENTRYPOINT – остается.
Пример:
CMD ["python", "app.py"]
Ты запустил:
docker run myapp bash
А получил: python app.py bash
Правильно да:
ENTRYPOINT ["python", "app.py"]
CMD ["--debug"]
Теперь все работает предсказуемо.
7. USER: не запускай все от root
По умолчанию Docker запускается от root. Это небезопасно. Особенно если есть volume или доступ к сокету.
Добавь:
RUN useradd -m myuser
USER myuser
Теперь все работает от безопасного пользователя. И если кто-нибудь сломает контейнер, он не получит root.
8. Healthcheck
Docker считает, что контейнер жил, если он просто не умер. Но твой сервис может зависнуть или вернут 500.
Добавить healthcheck:
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
Теперь Docker будет знать, когда сервис реально жил.
9. Кэш Docker
Слои в Docker кэшируются. Если ты пишешь слои неправильно, кэш не работает.
Плохо:
COPY. .
RUN pip install -r requirements.txt
Лучше:
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY. .
Теперь, когда ты меняешь только код, зависимости не ставятся заново.
10. Docker в CI
Частые ошибки:
Пуш latest без тега.
Кладут частные ключи в образ.
Не используют .dockerignore.
Как надо:
Указывать метки явно: myapp:1.2.3
Не храните секреты в образе. использовать secrets или переменные окружения.
Убедится, что .dockerignore есть и рабочий.
11. Уменьшение размера вида
Каждый MB важен, особенно в CI/CD. Что можно сделать:
Убирать временные пакеты после использования.
RUN apt-get install -y gcc && pip install some-lib && apt-get remove -y gcc
Использовать --no-cache и чистить /tmp.
Применят slim, а не full-образы.
12. Alpine - не серебряная пуля
Да, вон маленький. Но:
Использует musl, а не glibc.
Проблемы с совместимостью C-библиотек.
Долгий билд Python-пакетов.
Используй Alpine только если ты понимаешь, зачем он тебе нужен. В остальных случаях – slim.
Итоги:
Сборка Docker-образа — это не только про "работает ли у меня". Это про устойчивость, безопасность и предсказуемость. Маленькие ошибки приводят к большим проблемам.

