Jump to content
View in the app

A better way to browse. Learn more.

T.M.I IThub

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Зачем Docker: "работает на моей машине"

История, знакомая каждому разработчику. Приложение работает на вашем ноутбуке. Выкладываете на сервер — падает. Отличия: версия Python 3.9 vs 3.11, разные системные библиотеки, другая переменная PATH, конфликт зависимостей с другим приложением.

Docker решает эту проблему радикально: упаковывает приложение вместе со всем окружением — операционной системой, библиотеками, конфигурацией. Контейнер запускается одинаково везде: на ноутбуке разработчика, в CI/CD, на production-сервере, в облаке.

Дополнительные бонусы:

  • Изоляция: одно приложение не мешает другому

  • Воспроизводимость: одинаковая среда у всей команды

  • Быстрое развёртывание: docker pull + docker run вместо часа установки

  • Масштабирование: запустить 10 экземпляров так же легко, как 1

  • Чистота: удалить контейнер = никаких следов на хосте


Основные концепции

Image (образ): Слоёная файловая система со всем необходимым. Неизменяемый шаблон. Хранится в Registry (Docker Hub, GitHub Container Registry, ваш собственный).

Container (контейнер): Запущенный экземпляр образа. Изолированный процесс с собственной файловой системой, сетью, PID-пространством. Контейнеры ephemeral — данные исчезают при удалении (если нет Volume).

Volume (том): Постоянное хранилище данных, переживает удаление контейнера.

Network (сеть): Изолированная виртуальная сеть. Контейнеры в одной сети видят друг друга по имени.

Dockerfile: Инструкции для сборки образа. Каждая инструкция — новый слой.

Registry: Хранилище образов. Docker Hub — публичный. Можно развернуть свой (Harbor, Nexus).


Dockerfile: пишем правильно

Базовый пример (Python приложение):

# Начинаем с официального образа Python
# ВАЖНО: всегда указывайте точную версию, не :latest!
FROM python:3.11-slim

# Метаданные
LABEL maintainer="your@email.com"
LABEL version="1.0"
LABEL description="Industrial IoT Gateway"

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

# Копируем ТОЛЬКО файлы зависимостей первыми!
# Слои кешируются — если requirements.txt не изменился,
# pip install не запустится при следующей сборке
COPY requirements.txt .

# Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt

# Копируем исходный код (изменяется чаще — поэтому последним)
COPY src/ ./src/
COPY config/ ./config/

# Создаём непривилегированного пользователя (безопасность!)
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser

# Переменные окружения
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    LOG_LEVEL=INFO

# Открываем порт (документация, не публикует сам по себе)
EXPOSE 8080

# Healthcheck: Docker проверяет живость контейнера
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# Команда запуска
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080"]

Многоэтапная сборка (Multi-stage build):

Критически важна для production. Финальный образ не содержит инструментов сборки (gcc, make, pip), что уменьшает размер и поверхность атаки:

# ===== ЭТАП 1: Сборка =====
FROM python:3.11 AS builder

WORKDIR /build

# Устанавливаем зависимости для сборки
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    libffi-dev \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .

# Устанавливаем в отдельную директорию
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# ===== ЭТАП 2: Production образ =====
FROM python:3.11-slim AS production

# Копируем только установленные пакеты из builder
COPY --from=builder /install /usr/local

WORKDIR /app

# Только runtime зависимости ОС
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# Безопасность
RUN groupadd -r app && useradd -r -g app -d /app -s /sbin/nologin app
COPY --chown=app:app src/ ./src/
USER app

ENV PYTHONUNBUFFERED=1
HEALTHCHECK --interval=30s --timeout=5s CMD curl -f http://localhost:8080/health
CMD ["python", "src/main.py"]

# Результат: образ ~180MB вместо ~900MB!

.dockerignore — что НЕ копировать в образ:

# .dockerignore
**/__pycache__
**/*.pyc
**/*.pyo
.git
.gitignore
.venv
venv
*.env
.env.*
tests/
docs/
*.md
.github/
node_modules/
dist/
*.log
.DS_Store

# Секреты — никогда в образ!
*.key
*.pem
*secret*
config.local.*

Docker Compose: многосервисные приложения

Docker Compose — инструмент для запуска нескольких связанных контейнеров.

Полный пример: IoT-платформа

# docker-compose.yml
version: '3.9'

# Общие настройки через YAML anchors (DRY)
x-common-env: &common-env
  TZ: Europe/Moscow
  LOG_LEVEL: ${LOG_LEVEL:-INFO}

x-restart-policy: &restart-policy
  restart: unless-stopped

services:

  # ===== MQTT БРОКЕР =====
  mosquitto:
    image: eclipse-mosquitto:2.0.18
    <<: *restart-policy
    volumes:
      - ./mosquitto/config:/mosquitto/config:ro
      - mosquitto_data:/mosquitto/data
      - mosquitto_logs:/mosquitto/log
    ports:
      - "1883:1883"     # MQTT
      - "9001:9001"     # WebSocket
    networks:
      - iot_network
    healthcheck:
      test: ["CMD", "mosquitto_sub", "-t", "$$SYS/#", "-C", "1", "-i", "healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 3

  # ===== БАЗА ДАННЫХ ВРЕМЕННЫХ РЯДОВ =====
  influxdb:
    image: influxdb:2.7
    <<: *restart-policy
    environment:
      <<: *common-env
      DOCKER_INFLUXDB_INIT_MODE: setup
      DOCKER_INFLUXDB_INIT_USERNAME: ${INFLUX_USER:-admin}
      DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUX_PASSWORD:?INFLUX_PASSWORD required}
      DOCKER_INFLUXDB_INIT_ORG: factory
      DOCKER_INFLUXDB_INIT_BUCKET: telemetry
      DOCKER_INFLUXDB_INIT_RETENTION: 30d
      DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${INFLUX_TOKEN:?INFLUX_TOKEN required}
    volumes:
      - influxdb_data:/var/lib/influxdb2
      - influxdb_config:/etc/influxdb2
    ports:
      - "8086:8086"
    networks:
      - iot_network
      - monitoring_network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8086/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s

  # ===== GRAFANA =====
  grafana:
    image: grafana/grafana:10.3.1
    <<: *restart-policy
    depends_on:
      influxdb:
        condition: service_healthy
    environment:
      <<: *common-env
      GF_SECURITY_ADMIN_USER: ${GF_ADMIN_USER:-admin}
      GF_SECURITY_ADMIN_PASSWORD: ${GF_ADMIN_PASSWORD:?required}
      GF_SERVER_ROOT_URL: http://localhost:3000
      GF_SMTP_ENABLED: "true"
      GF_SMTP_HOST: ${SMTP_HOST:-localhost:25}
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
    ports:
      - "3000:3000"
    networks:
      - monitoring_network
    user: "472"  # grafana user

  # ===== IOT ШЛЮЗ (наш сервис) =====
  gateway:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
      args:
        BUILD_DATE: ${BUILD_DATE:-unknown}
        GIT_COMMIT: ${GIT_COMMIT:-unknown}
    <<: *restart-policy
    depends_on:
      mosquitto:
        condition: service_healthy
      influxdb:
        condition: service_healthy
    environment:
      <<: *common-env
      MQTT_HOST: mosquitto        # Имя сервиса = DNS-имя внутри сети!
      MQTT_PORT: 1883
      INFLUX_URL: http://influxdb:8086
      INFLUX_TOKEN: ${INFLUX_TOKEN}
      INFLUX_ORG: factory
      INFLUX_BUCKET: telemetry
      MODBUS_PORT: /dev/ttyUSB0   # Реальный порт с хоста
    volumes:
      - ./config:/app/config:ro
      - gateway_logs:/app/logs
    devices:
      - "/dev/ttyUSB0:/dev/ttyUSB0"  # Проброс USB-устройства
    ports:
      - "8080:8080"
    networks:
      - iot_network
      - monitoring_network
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M

  # ===== NGINX: реверс-прокси =====
  nginx:
    image: nginx:1.25-alpine
    <<: *restart-policy
    depends_on:
      - grafana
      - gateway
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro  # TLS сертификаты
    ports:
      - "80:80"
      - "443:443"
    networks:
      - monitoring_network

# ===== ТОМА =====
volumes:
  mosquitto_data:
  mosquitto_logs:
  influxdb_data:
  influxdb_config:
  grafana_data:
  gateway_logs:

# ===== СЕТИ =====
networks:
  iot_network:        # Для IoT-компонентов
    driver: bridge
  monitoring_network: # Для мониторинга и UI
    driver: bridge

.env файл для Compose:

# .env — НЕ коммитить в git!
INFLUX_USER=admin
INFLUX_PASSWORD=super_secret_influx_pass
INFLUX_TOKEN=my-super-secret-token-change-this
GF_ADMIN_USER=admin
GF_ADMIN_PASSWORD=super_secret_grafana_pass
LOG_LEVEL=INFO
BUILD_DATE=2024-03-15
GIT_COMMIT=abc123

Docker: ключевые команды

# ===== ОБРАЗЫ =====
docker build -t myapp:1.0 .           # Собрать образ
docker build --no-cache -t myapp:1.0 . # Без кеша
docker images                          # Список образов
docker rmi myapp:1.0                   # Удалить образ
docker image prune                     # Удалить неиспользуемые

# История слоёв (анализ размера):
docker history myapp:1.0

# ===== КОНТЕЙНЕРЫ =====
docker run -d \
  --name gateway \
  -p 8080:8080 \
  -e MQTT_HOST=192.168.1.100 \
  -v $(pwd)/config:/app/config:ro \
  --restart unless-stopped \
  myapp:1.0

docker ps                              # Запущенные контейнеры
docker ps -a                           # Все (включая остановленные)
docker logs gateway -f                 # Логи в реальном времени
docker logs gateway --tail 100         # Последние 100 строк
docker exec -it gateway bash           # Войти внутрь контейнера
docker stop gateway                    # Остановить
docker start gateway                   # Запустить
docker restart gateway                 # Перезапустить
docker rm gateway                      # Удалить (сначала stop)
docker stats                           # Потребление ресурсов

# ===== COMPOSE =====
docker compose up -d                   # Запустить все сервисы
docker compose up -d gateway           # Запустить только gateway
docker compose down                    # Остановить и удалить контейнеры
docker compose down -v                 # + удалить тома (ОСТОРОЖНО!)
docker compose logs -f                 # Логи всех сервисов
docker compose logs -f gateway         # Логи конкретного сервиса
docker compose ps                      # Статус сервисов
docker compose pull                    # Обновить образы
docker compose build --no-cache        # Пересобрать
docker compose restart gateway         # Перезапустить сервис
docker compose exec gateway bash       # Войти в контейнер

# ===== REGISTRY =====
docker tag myapp:1.0 ghcr.io/myorg/myapp:1.0
docker push ghcr.io/myorg/myapp:1.0
docker pull ghcr.io/myorg/myapp:1.0

# ===== ОЧИСТКА =====
docker system prune -a                 # Удалить ВСЁ неиспользуемое
docker volume prune                    # Удалить неиспользуемые тома

Volumes и persistence: не теряем данные

# Типы монтирования:

services:
  app:
    volumes:
      # 1. Named Volume (рекомендуется для данных БД)
      - db_data:/var/lib/postgresql/data
      
      # 2. Bind Mount (для конфигов и разработки)
      - ./config:/app/config:ro    # :ro = read-only
      - ./src:/app/src             # Для hot-reload при разработке
      
      # 3. tmpfs (только в RAM, для временных данных)
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 100M

volumes:
  db_data:
    driver: local
    # Для production: внешние тома
    # external: true
    # name: prod_db_data

Backup томов:

# Резервная копия тома influxdb_data
docker run --rm \
  -v influxdb_data:/source:ro \
  -v $(pwd)/backups:/backup \
  alpine:3 \
  tar czf /backup/influxdb_$(date +%Y%m%d).tar.gz -C /source .

# Восстановление
docker run --rm \
  -v influxdb_data:/target \
  -v $(pwd)/backups:/backup:ro \
  alpine:3 \
  tar xzf /backup/influxdb_20240315.tar.gz -C /target

Оптимизация размера образа

# 

ПЛОХО: большой образ FROM ubuntu:22.04 RUN apt-get update RUN apt-get install -y python3 RUN apt-get install -y python3-pip RUN pip3 install flask COPY app.py . CMD ["python3", "app.py"] # Размер: ~480 МБ, 5 лишних слоёв #

ХОРОШО: оптимизированный образ FROM python:3.11-slim RUN pip install --no-cache-dir flask COPY app.py . CMD ["python", "app.py"] # Размер: ~85 МБ, чистая сборка #

ЕЩЁ ЛУЧШЕ: alpine (минимальный дистрибутив) FROM python:3.11-alpine # Некоторые C-расширения нужно собирать RUN apk add --no-cache gcc musl-dev && \ pip install --no-cache-dir flask && \ apk del gcc musl-dev COPY app.py . CMD ["python", "app.py"] # Размер: ~45 МБ

Правило минимума слоёв для apt/apk:

# Объединяйте RUN команды в одну для минимизации слоёв
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        libpq5 \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean
# ОДИН слой вместо нескольких, и сразу очистка кеша

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

1. Никогда не запускать от root:

# Создаём непривилегированного пользователя
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

2. Read-only файловая система:

services:
  app:
    read_only: true  # Файловая система только для чтения
    tmpfs:
      - /tmp         # Разрешаем запись только в tmpfs
      - /var/run

3. Ограничение capabilities:

services:
  app:
    cap_drop:
      - ALL          # Убираем ВСЕ capabilities
    cap_add:
      - NET_BIND_SERVICE  # Добавляем только необходимые

4. Сканирование образов на уязвимости:

# Trivy — бесплатный сканер (Aqua Security)
docker run --rm aquasec/trivy image myapp:1.0

# Или встроенный Docker Scout
docker scout cves myapp:1.0

5. Secrets — не в переменных окружения production:

# docker-compose.yml с Docker secrets
services:
  app:
    secrets:
      - db_password
      - api_key
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password  # Читаем из файла!

secrets:
  db_password:
    file: ./secrets/db_password.txt  # или external: true для Swarm/K8s

Healthcheck и зависимости между сервисами

services:
  postgres:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s  # Даём время на инициализацию

  app:
    depends_on:
      postgres:
        condition: service_healthy  # Ждём пока postgres healthy!
      redis:
        condition: service_healthy

Entrypoint-скрипт для ожидания зависимостей:

#!/bin/sh
# entrypoint.sh

set -e

echo "Ожидание готовности базы данных..."

until nc -z -w5 ${DB_HOST} ${DB_PORT}; do
    echo "База данных недоступна, ждём..."
    sleep 2
done

echo "База данных готова!"

# Запуск миграций
python manage.py migrate --no-input

# Запуск приложения
exec "$@"
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["gunicorn", "app:application"]

Docker в CI/CD (GitHub Actions)

# .github/workflows/docker.yml
name: Build and Deploy

on:
  push:
    tags: ['v*.*.*']

jobs:
  build-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64  # Multi-arch!
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:${{ github.ref_name }}
            ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            GIT_COMMIT=${{ github.sha }}
            BUILD_DATE=${{ github.event.head_commit.timestamp }}

  deploy:
    needs: build-push
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: deploy
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /opt/iot-platform
            
            # Обновляем образ
            docker compose pull gateway
            
            # Обновляем с нулевым downtime
            docker compose up -d --no-deps gateway
            
            # Ждём healthcheck
            sleep 15
            docker compose ps gateway | grep -q "healthy" || exit 1
            
            echo "Деплой успешен!"

Заключение: когда Docker, когда нет

Используйте Docker:

  • Приложения с множеством зависимостей

  • Многосервисные приложения (backend + БД + брокер + мониторинг)

  • CI/CD с гарантированной воспроизводимостью

  • Несколько приложений на одном сервере

  • Нужна возможность быстрого масштабирования

Не обязательно Docker:

  • Простые скрипты без зависимостей

  • Приложения с прямым доступом к оборудованию (хотя --device помогает)

  • Жёсткое реальное время (latency контейнера ~1 мкс, но это не ноль)

  • Команда незнакома с Docker — сначала обучение, потом внедрение

Docker — это не серебряная пуля, но инвестиция в него окупается быстро. Начните с docker-compose.yml для локальной разработки — уже это даст ощутимый результат: одна команда docker compose up поднимает весь стек вместо часа настройки.

User Feedback

Create an account or sign in to leave a review

There are no reviews to display.

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.