Перейти к основному содержимому

Лучшие практики Docker

Рекомендации по безопасному использованию Docker в продакшене.

Безопасность контейнеров

Dockerfile лучшие практики

# Используем официальные базовые образы
FROM node:18-alpine

# Создаем непривилегированного пользователя
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001

# Устанавливаем рабочую директорию
WORKDIR /app

# Копируем package files
COPY package*.json ./

# Устанавливаем зависимости
RUN npm ci --only=production && npm cache clean --force

# Копируем код приложения
COPY --chown=nextjs:nodejs . .

# Переключаемся на непривилегированного пользователя
USER nextjs

# Открываем порт
EXPOSE 3000

# Используем exec форму для CMD
CMD ["node", "server.js"]

Мультистадийная сборка

# Build stage
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine AS production

RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001

WORKDIR /app

# Копируем только production зависимости
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# Копируем собранное приложение
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

USER nextjs

EXPOSE 3000

CMD ["node", "dist/server.js"]

Docker Compose безопасность

Базовая конфигурация

version: '3.8'

services:
app:
build: .
restart: unless-stopped

# Ограничиваем ресурсы
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 256M

# Безопасность
read_only: true
user: "1001:1001"
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE

# Tmpfs для временных файлов
tmpfs:
- /tmp:noexec,nosuid,size=100m
- /var/run:noexec,nosuid,size=100m

# Переменные окружения из файла
env_file:
- .env

# Сеть
networks:
- app-network

# Volumes
volumes:
- app-data:/app/data:rw
- logs:/app/logs:rw

nginx:
image: nginx:alpine
restart: unless-stopped

ports:
- "80:80"
- "443:443"

# Безопасность
read_only: true
user: "101:101"
cap_drop:
- ALL
cap_add:
- CHOWN
- DAC_OVERRIDE
- SETGID
- SETUID
- NET_BIND_SERVICE

tmpfs:
- /var/cache/nginx:noexec,nosuid,size=100m
- /var/run:noexec,nosuid,size=100m

volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/ssl:ro
- logs:/var/log/nginx:rw

networks:
- app-network

depends_on:
- app

volumes:
app-data:
driver: local
logs:
driver: local

networks:
app-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16

Production конфигурация

version: '3.8'

services:
app:
build:
context: .
dockerfile: Dockerfile.prod

restart: unless-stopped

# Healthcheck
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

# Logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

# Security
security_opt:
- no-new-privileges:true

# Environment
environment:
- NODE_ENV=production
- TZ=UTC

env_file:
- .env.prod

networks:
- app-network

volumes:
- app-data:/app/data:rw
- /etc/localtime:/etc/localtime:ro

redis:
image: redis:7-alpine
restart: unless-stopped

command: redis-server /etc/redis/redis.conf

# Security
user: "999:999"
read_only: true

volumes:
- ./redis.conf:/etc/redis/redis.conf:ro
- redis-data:/data:rw

networks:
- app-network

# Healthcheck
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 3s
retries: 3

postgres:
image: postgres:15-alpine
restart: unless-stopped

# Security
user: "999:999"

environment:
- POSTGRES_DB_FILE=/run/secrets/postgres_db
- POSTGRES_USER_FILE=/run/secrets/postgres_user
- POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password

secrets:
- postgres_db
- postgres_user
- postgres_password

volumes:
- postgres-data:/var/lib/postgresql/data:rw
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro

networks:
- app-network

# Healthcheck
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 5s
retries: 3

nginx:
image: nginx:alpine
restart: unless-stopped

ports:
- "80:80"
- "443:443"

volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./ssl:/etc/ssl:ro
- logs:/var/log/nginx:rw

networks:
- app-network

depends_on:
app:
condition: service_healthy

secrets:
postgres_db:
file: ./secrets/postgres_db.txt
postgres_user:
file: ./secrets/postgres_user.txt
postgres_password:
file: ./secrets/postgres_password.txt

volumes:
app-data:
redis-data:
postgres-data:
logs:

networks:
app-network:
driver: bridge

Безопасность Docker daemon

Конфигурация daemon

Создаем /etc/docker/daemon.json:

{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"seccomp-profile": "/etc/docker/seccomp-default.json",
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 64000,
"Soft": 64000
}
},
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
]
}

Ограничения ресурсов

services:
app:
image: myapp:latest

# CPU ограничения
cpus: 0.5
cpu_shares: 512

# Memory ограничения
mem_limit: 512m
memswap_limit: 512m

# PID ограничения
pids_limit: 100

# Ulimits
ulimits:
nproc: 65535
nofile:
soft: 65535
hard: 65535

Мониторинг и логирование

Docker Compose с мониторингом

version: '3.8'

services:
app:
build: .

# Метки для мониторинга
labels:
- "prometheus.io/scrape=true"
- "prometheus.io/port=3000"
- "prometheus.io/path=/metrics"

# Логирование в syslog
logging:
driver: syslog
options:
syslog-address: "tcp://localhost:514"
tag: "myapp"

networks:
- app-network
- monitoring

prometheus:
image: prom/prometheus:latest

ports:
- "9090:9090"

volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus

command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'

networks:
- monitoring

volumes:
prometheus-data:

networks:
app-network:
monitoring:

Prometheus конфигурация

prometheus.yml:

global:
scrape_interval: 15s

scrape_configs:
- job_name: 'docker-containers'
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 15s

relabel_configs:
- source_labels: [__meta_docker_container_label_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_docker_container_label_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_docker_container_label_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__

Очистка и обслуживание

Скрипт очистки

#!/bin/bash
# docker-cleanup.sh

echo "Docker system cleanup..."

# Остановка неиспользуемых контейнеров
docker container prune -f

# Удаление неиспользуемых образов
docker image prune -a -f

# Удаление неиспользуемых volumes
docker volume prune -f

# Удаление неиспользуемых сетей
docker network prune -f

# Общая очистка
docker system prune -a -f --volumes

echo "Cleanup completed!"

# Показываем освобожденное место
df -h /var/lib/docker

Автоматическая очистка через cron

# Добавляем в crontab
0 2 * * 0 /opt/scripts/docker-cleanup.sh >> /var/log/docker-cleanup.log 2>&1

Мониторинг размера Docker

#!/bin/bash
# docker-size-monitor.sh

echo "=== Docker Disk Usage ==="
docker system df

echo -e "\n=== Container Sizes ==="
docker ps --size --format "table {{.Names}}\t{{.Size}}"

echo -e "\n=== Image Sizes ==="
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

echo -e "\n=== Volume Sizes ==="
docker volume ls -q | xargs docker volume inspect | jq -r '.[] | [.Name, .Mountpoint] | @tsv' | while read name mountpoint; do
size=$(du -sh "$mountpoint" 2>/dev/null | cut -f1)
echo -e "$name\t$size"
done