Лучшие практики 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