Инструкции Dockerfile, лучшие практики написания
Dockerfile — это рецепт сборки вашего образа. Изучите все инструкции и лучшие практики написания.
Dockerfile — это текстовый файл с инструкциями для автоматической сборки Docker-образа. Каждая инструкция создаёт новый слой образа.
Dockerfile позволяет:
Пример Dockerfile для Python-приложения:
# Базовый образ
FROM python:3.11-slim
# Метаданные
LABEL maintainer="John Doe <john@example.com>"
LABEL version="1.0"
# Рабочая директория
WORKDIR /app
# Копирование зависимостей
COPY requirements.txt .
# Установка зависимостей
RUN pip install --no-cache-dir -r requirements.txt
# Копирование кода приложения
COPY . .
# Переменные окружения
ENV PYTHONUNBUFFERED=1
ENV APP_ENV=production
# Проброс порта (документация)
EXPOSE 8000
# Пользователь (безопасность)
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
# Команда запуска
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]FROM задаёт базовый образ. Должна быть первой инструкцией (кроме ARG).
# Базовый синтаксис
FROM <образ>[:<тег>]
# Примеры
FROM python:3.11-slim
FROM node:18-alpine
FROM ubuntu:22.04Рекомендации:
python:3.11.4-slim, не python:latest)LABEL добавляет метаданные к образу.
LABEL maintainer="John Doe <john@example.com>"
LABEL version="1.0"
LABEL description="My Python Application"
# Несколько меток в одной инструкции
LABEL com.example.vendor="ACME" \
com.example.version="1.0" \
com.example.release-id="2024.01"Просмотр метаданных:
docker inspect --format='{{json .Config.Labels}}' myappWORKDIR устанавливает рабочую директорию для последующих инструкций.
WORKDIR /app
RUN pwd # /app
WORKDIR /src
RUN pwd # /src
# Создаёт директорию, если не существует
WORKDIR /nonexistent # Будет созданаВажно: Используйте абсолютные пути. Избегайте RUN cd /app && ... — используйте WORKDIR.
COPY копирует файлы из контекста сборки в образ.
# Копирование одного файла
COPY requirements.txt .
# Копирование нескольких файлов
COPY src/ /app/src/
COPY README.md /app/
# Копирование с переименованием
COPY app.py /app/main.py
# Копирование нескольких файлов с wildcard
COPY *.py /app/src/Контекст сборки:
docker build -t myapp ..) означает текущую директорию.dockerignore исключает файлы из контекстаADD похож на COPY, но с дополнительными возможностями:
# Распаковка архива
ADD archive.tar.gz /app/
# Загрузка по URL (не рекомендуется)
ADD https://example.com/file.txt /app/Рекомендация: Используйте COPY по умолчанию. ADD — только для распаковки архивов.
RUN выполняет команду в новом слое образа.
# Shell форма (запускается через /bin/sh -c)
RUN apt update && apt install -y nginx
# Exec форма (предпочтительна)
RUN ["apt", "update", "&&", "apt", "install", "-y", "nginx"]
# Многострочные команды для читаемости
RUN apt update && \
apt install -y \
git \
curl \
vim && \
rm -rf /var/lib/apt/lists/*Важно: Объединяйте команды в один RUN для уменьшения количества слоёв.
CMD задаёт команду по умолчанию для запуска контейнера.
# Exec форма (предпочтительна)
CMD ["nginx", "-g", "daemon off;"]
# Shell форма
CMD nginx -g "daemon off;"
# С переменными окружения
CMD ["python", "manage.py", "runserver"]Важно:
docker run myapp bashENTRYPOINT задаёт главную команду контейнера.
# Exec форма
ENTRYPOINT ["python", "app.py"]
# Shell форма
ENTRYPOINT python app.pyРазница между CMD и ENTRYPOINT:
| Характеристика | CMD | ENTRYPOINT |
|---|---|---|
| Переопределение | Легко (docker run image command) | Требует --entrypoint |
| Назначение | Аргументы по умолчанию | Основная команда |
| Комбинирование | Можно использовать с ENTRYPOINT | Может использовать CMD как аргументы |
Паттерн: ENTRYPOINT + CMD
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "8000"]При запуске:
# Использует CMD по умолчанию
docker run myapp
# python app.py --host 0.0.0.0 --port 8000
# Переопределяет CMD
docker run myapp --help
# python app.py --helpEXPOSE документирует порты, которые слушает приложение.
EXPOSE 80
EXPOSE 443
EXPOSE 8000 8080Важно: EXPOSE не публикует порты на хосте. Для доступа используйте -p при запуске:
docker run -p 8080:80 myappENV устанавливает переменные окружения.
# Одна переменная
ENV APP_ENV=production
# Несколько переменных
ENV DB_HOST=localhost \
DB_PORT=5432 \
DB_NAME=myapp
# Использование в Dockerfile
ENV APP_HOME=/app
WORKDIR $APP_HOMEПеременные доступны:
docker run -e VAR=valueARG определяет переменные для времени сборки.
# Переменная сборки
ARG VERSION=1.0
LABEL version=${VERSION}
# Использование в RUN
ARG PYTHONDONTWRITEBYTECODE=1
ENV PYTHONDONTWRITEBYTECODE=${PYTHONDONTWRITEBYTECODE}
# Переменная без значения по умолчанию
ARG SECRET_KEY
RUN echo ${SECRET_KEY} > /tmp/secretРазница ARG vs ENV:
Передача при сборке:
docker build --build-arg VERSION=2.0 -t myapp .VOLUME создаёт точку монтирования для внешнего хранилища.
VOLUME ["/data"]
VOLUME /data /logsПри запуске Docker создаёт anonymous volume:
docker run myapp
# Volume создан в /var/lib/docker/volumes/Или монтируйте свой volume:
docker run -v mydata:/data myappUSER задаёт пользователя для запуска процессов.
# Создание пользователя
RUN useradd --create-home --shell /bin/bash appuser
# Переключение пользователя
USER appuser
# С указанием группы
USER appuser:appgroupБезопасность: Не запускайте контейнеры от root в production.
HEALTHCHECK определяет команду для проверки работоспособности.
HEALTHCHECK \
CMD curl -f http://localhost:8000/health || exit 1Параметры:
--interval — период между проверками (по умолчанию 30s)--timeout — таймаут команды (по умолчанию 30s)--start-period — время на старт приложения (по умолчанию 0s)--retries — количество попыток до статуса unhealthy (по умолчанию 3)Проверка статуса:
docker ps # Покажет статус (healthy/unhealthy)
docker inspect --format='{{.State.Health.Status}}' container_idSTOPSIGNAL задаёт сигнал для остановки контейнера.
STOPSIGNAL SIGTERM
STOPSIGNAL SIGINTПо умолчанию Docker отправляет SIGTERM, затем SIGKILL через 10 секунд.
SHELL меняет оболочку для shell-формы инструкций.
# Использование bash вместо sh
SHELL ["/bin/bash", "-c"]
RUN source /etc/profile && echo $HOMEMulti-stage builds используют несколько инструкций FROM для разделения этапов.
# Этап 1: Сборка
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp
# Этап 2: Запуск
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY /app/myapp .
COPY /etc/ssl/certs /etc/ssl/certs
CMD ["./myapp"]Преимущества:
# Этап 1: Сборка
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Этап 2: Production
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]Создайте .dockerignore в корне проекта:
.git
.gitignore
*.md
.env
.env.local
__pycache__
*.pyc
node_modules
venv
.Dockerfile
docker-compose*.yml
# ✅ Хорошо — кэширование работает
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# ❌ Плохо — кэш сбрасывается при любом изменении кода
COPY . .
RUN pip install -r requirements.txt# ✅ Хорошо — один слой
RUN apt update && \
apt install -y git curl && \
rm -rf /var/lib/apt/lists/*
# ❌ Плохо — три слоя
RUN apt update
RUN apt install -y git curl
RUN rm -rf /var/lib/apt/lists/*# ✅ Хорошо
RUN useradd appuser
USER appuser
# ❌ Плохо
USER root
RUN sudo -u appuser ...# ✅ Хорошо
FROM python:3.11.4-slim
FROM node:18.16.0-alpine
# ❌ Плохо
FROM python:latest
FROM node:latest
FROM ubuntu# ✅ Хорошо
COPY requirements.txt setup.py ./
RUN pip install -r requirements.txt
# ❌ Плохо
COPY requirements.txt .
COPY setup.py .
RUN pip install -r requirements.txtДля корректной обработки сигналов используйте:
# С tini
FROM python:3.11-slim
RUN apt update && apt install -y tini
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["python", "app.py"]FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
DJANGO_SETTINGS_MODULE=config.settings.production
WORKDIR /app
# Зависимости
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Код
COPY . .
# Сборка статики
RUN python manage.py collectstatic --noinput
# Пользователь
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
EXPOSE 8000
HEALTHCHECK \
CMD curl -f http://localhost:8000/health/ || exit 1
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "config.wsgi"]FROM node:18-alpine
ENV NODE_ENV=production
WORKDIR /app
# Зависимости
COPY package*.json ./
RUN npm ci --only=production
# Код
COPY . .
# Пользователь
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
HEALTHCHECK \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "server.js"]# Этап 1: Сборка
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# Этап 2: Запуск
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY /app/target/*.jar app.jar
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
EXPOSE 8080
HEALTHCHECK \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]# Hadolint — линтер для Dockerfile
hadolint Dockerfile
# Установка
brew install hadolint # macOS
pip install hadolint # Python# История слоёв
docker history myapp:latest
# Детальная информация
docker inspect myapp:latest
# Размер каждого слоя
docker image inspect --format='{{range .RootFS.Layers}}{{println .}}{{end}}' myapp# Подробный вывод
docker build --progress=plain -t myapp .
# Без кэша
docker build --no-cache -t myapp .
# С аргументами
docker build --build-arg DEBUG=1 -t myapp .Цель: Научиться писать базовый Dockerfile для Python-приложения.
Задание:
app.py с Flaskrequirements.txtDockerfile# Создание структуры проекта
mkdir myflaskapp && cd myflaskapp
# app.py
cat > app.py << 'EOF'
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello from Docker!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
# requirements.txt
cat > requirements.txt << 'EOF'
flask==3.0.0
EOF
# Dockerfile
cat > Dockerfile << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
EOF
# Сборка образа
docker build -t myflaskapp .
# Запуск контейнера
docker run -d --name flask-app -p 5000:5000 myflaskapp
# Проверка
curl http://localhost:5000
docker logs flask-appОжидаемый результат: Вы видите "Hello from Docker!" в браузере.
Цель: Понять, как работает кэширование слоёв Docker.
Задание:
# ❌ Плохой Dockerfile (кэш не работает)
cat > Dockerfile.bad << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY . .
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "app.py"]
EOF
# ✅ Хороший Dockerfile (кэш работает)
cat > Dockerfile.good << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
EOF
# Сборка с выводом для наблюдения за кэшем
docker build --progress=plain -t test-bad -f Dockerfile.bad .
docker build --progress=plain -t test-good -f Dockerfile.good .
# Измените app.py (добавьте комментарий)
# Соберите снова и сравните выводОжидаемый результат: В хорошем Dockerfile слой с pip install используется из кэша, в плохом — пересобирается каждый раз.
Цель: Научиться использовать многоэтапную сборку для уменьшения размера образа.
Задание:
# main.go
cat > main.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("Hello from Go in Docker!")
}
EOF
# go.mod
cat > go.mod << 'EOF'
module example.com/myapp
go 1.21
EOF
# Dockerfile с multi-stage
cat > Dockerfile << 'EOF'
# Этап 1: Сборка
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod main.go ./
RUN go build -o myapp main.go
# Этап 2: Запуск
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]
EOF
# Сборка и проверка размера
docker build -t myapp-multi .
docker images | grep myapp
# Сравните с одноэтапной сборкой
cat > Dockerfile.single << 'EOF'
FROM golang:1.21
WORKDIR /app
COPY go.mod main.go ./
RUN go build -o myapp main.go
CMD ["./myapp"]
EOF
docker build -t myapp-single -f Dockerfile.single .
docker images | grep myappОжидаемый результат: Multi-stage образ в 10-20 раз меньше одноэтапного.
Цель: Научиться создавать безопасные образы с непривилегированным пользователем.
Задание:
# ❌ Небезопасный Dockerfile
cat > Dockerfile.insecure << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]
EOF
# ✅ Безопасный Dockerfile
cat > Dockerfile.secure << 'EOF'
FROM python:3.11-slim
# Создание пользователя
RUN useradd --create-home --shell /bin/bash appuser
WORKDIR /app
COPY --chown=appuser:appuser app.py .
# Переключение на непривилегированного пользователя
USER appuser
CMD ["python", "app.py"]
EOF
# Сборка
docker build -t insecure -f Dockerfile.insecure .
docker build -t secure -f Dockerfile.secure .
# Проверка пользователя
docker run --rm insecure whoami
# root
docker run --rm secure whoami
# appuserОжидаемый результат: Безопасный образ запускается от пользователя appuser, а не от root.
Цель: Научиться добавлять проверку работоспособности в Dockerfile.
Задание:
# Dockerfile с HEALTHCHECK
cat > Dockerfile << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
# Добавление health check
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000')" || exit 1
CMD ["python", "app.py"]
EOF
# Сборка и запуск
docker build -t myflaskapp-health .
docker run -d --name flask-health myflaskapp-health
# Проверка статуса
docker ps # Ищите (healthy) в статусе
docker inspect --format='{{.State.Health.Status}}' flask-health
# Логи health check
docker inspect --format='{{json .State.Health.Log}}' flask-health | jqОжидаемый результат: Статус контейнера меняется с starting на healthy через несколько секунд.
appuser, а не roothealthyВ следующей теме вы научитесь управлять запущенными контейнерами: запускать, останавливать, мониторить и анализировать логи.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.