Автоматическое обновление Docker контейнеров: полное руководство и примеры

Золотые правила автообновления Docker
За 20 лет в системном администрировании я понял одну простую истину: всё, что можно автоматизировать, должно быть автоматизировано. Обновление Docker контейнеров — не исключение. Сегодня разберём все способы автоматического обновления контейнеров: от простых решений для домашней лаборатории до enterprise-подходов для продакшена.

Почему автоматическое обновление контейнеров — это важно

Представьте: у вас 50 контейнеров на 10 серверах. Каждую неделю выходят обновления безопасности. Обновлять вручную? Это 3-4 часа работы каждую неделю. Автоматизация? 10 минут на настройку один раз, и дальше всё работает само.

Основные причины автоматизировать обновления:

  • Безопасность — закрытие уязвимостей в базовых образах
  • Экономия времени — никаких ручных действий по расписанию
  • Стабильность — обновления по расписанию, а не «когда вспомнили»
  • Консистентность — все контейнеры обновляются единообразно

Способ 1: Watchtower — самое простое решение

Watchtower — это контейнер, который мониторит ваши запущенные контейнеры и автоматически обновляет их при появлении новых образов. Установил, настроил, забыл. Мой любимый инструмент для домашних проектов и небольших инфраструктур.

Базовая установка Watchtower

# Запуск Watchtower с базовыми настройками
<a class="wpil_keyword_link" href="https://it-apteka.com/tag/docker/"   title="Docker" data-wpil-keyword-link="linked"  data-wpil-monitor-id="94">docker</a> run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower

# Watchtower будет проверять обновления каждые 24 часа
# и автоматически обновлять все контейнеры

Продвинутая настройка Watchtower

# Запуск с кастомными параметрами
<a class="wpil_keyword_link" href="https://it-apteka.com/tag/docker/"   title="Docker" data-wpil-keyword-link="linked"  data-wpil-monitor-id="250">docker</a> run -d \
  --name watchtower \
  --restart unless-stopped \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e WATCHTOWER_CLEANUP=true \
  -e WATCHTOWER_INCLUDE_RESTARTING=true \
  -e WATCHTOWER_INCLUDE_STOPPED=false \
  -e WATCHTOWER_POLL_INTERVAL=3600 \
  -e WATCHTOWER_NOTIFICATIONS=email \
  -e WATCHTOWER_NOTIFICATION_EMAIL_FROM=watchtower@company.com \
  -e WATCHTOWER_NOTIFICATION_EMAIL_TO=admin@company.com \
  -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com \
  -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587 \
  -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=your-email@gmail.com \
  -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=your-app-password \
  containrrr/watchtower

Расшифровка параметров:

  • WATCHTOWER_CLEANUP=true — удаляет старые образы после обновления
  • WATCHTOWER_POLL_INTERVAL=3600 — проверка каждые 3600 секунд (1 час)
  • WATCHTOWER_INCLUDE_STOPPED=false — не обновлять остановленные контейнеры
  • WATCHTOWER_NOTIFICATIONS — уведомления по email, Slack, Telegram и т.д.

Обновление только определённых контейнеров

# Обновлять только контейнеры с меткой &amp;amp;amp;amp;quot;com.centurylinklabs.watchtower.enable=true&amp;amp;amp;amp;quot;
docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e WATCHTOWER_LABEL_ENABLE=true \
  containrrr/watchtower

# Пример контейнера с меткой для обновления
docker run -d \
  --label com.centurylinklabs.watchtower.enable=true \
  --name nginx \
  nginx:latest

# Или обновлять конкретные контейнеры по именам
docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower nginx portainer traefik

Способ 2: Docker Compose с pull политикой

Если вы используете Docker Compose (а вы должны его использовать!), можно настроить автоматическое обновление через cron и docker-compose pull. Это более контролируемый подход, чем Watchtower.

Пример docker-compose.yml

version: &amp;amp;amp;amp;#039;3.8&amp;amp;amp;amp;#039;

services:
  web:
    image: nginx:latest
    container_name: web-server
    restart: unless-stopped
    ports:
      - &amp;amp;amp;amp;quot;80:80&amp;amp;amp;amp;quot;
    
  database:
    image: postgres:15-alpine
    container_name: postgres-db
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: secure_password
    volumes:
      - db-data:/var/lib/postgresql/data

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

volumes:
  db-data:

Скрипт автоматического обновления

#!/bin/bash
# Файл: /opt/scripts/docker-update.sh

# Переходим в директорию с docker-compose.yml
cd /opt/docker-apps || exit 1

# Логирование
LOG_FILE=&amp;amp;amp;amp;quot;/var/log/docker-update.log&amp;amp;amp;amp;quot;
echo &amp;amp;amp;amp;quot;$(date &amp;amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;amp;#039;) - Начало обновления контейнеров&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot;

# Скачиваем последние образы
docker-compose pull &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot; 2&amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;1

# Проверяем, есть ли обновления
if docker-compose pull | grep -q &amp;amp;amp;amp;quot;Downloaded newer image&amp;amp;amp;amp;quot;; then
    echo &amp;amp;amp;amp;quot;$(date &amp;amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;amp;#039;) - Найдены обновления, перезапуск...&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot;
    
    # Останавливаем контейнеры
    docker-compose down &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot; 2&amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;1
    
    # Запускаем с новыми образами
    docker-compose up -d &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot; 2&amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;1
    
    # Удаляем старые образы
    docker image prune -f &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot; 2&amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;1
    
    echo &amp;amp;amp;amp;quot;$(date &amp;amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;amp;#039;) - Обновление завершено успешно&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot;
else
    echo &amp;amp;amp;amp;quot;$(date &amp;amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;amp;#039;) - Обновлений не найдено&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot;
fi

Настройка cron для автоматического запуска

# Делаем &amp;amp;lt;a class=&amp;amp;quot;wpil_keyword_link&amp;amp;quot; href=&amp;amp;quot;https://it-apteka.com/category/scripts/&amp;amp;quot;   title=&amp;amp;quot;Скрипты&amp;amp;quot; data-wpil-keyword-link=&amp;amp;quot;linked&amp;amp;quot;  data-wpil-monitor-id=&amp;amp;quot;67&amp;amp;quot;&amp;amp;gt;скрипт&amp;amp;lt;/a&amp;amp;gt; исполняемым
chmod +x /opt/scripts/docker-update.sh

# Открываем crontab
crontab -e

# Добавляем задачу: каждое воскресенье в 03:00
0 3 * * 0 /opt/scripts/docker-update.sh

# Или каждый день в 02:00 (для критичных систем)
0 2 * * * /opt/scripts/docker-update.sh

# Или каждую неделю в среду в 04:00
0 4 * * 3 /opt/scripts/docker-update.sh

Способ 3: Portainer с автообновлением

Portainer — это web-интерфейс для управления Docker. С версии 2.0 он поддерживает автоматическое обновление контейнеров через встроенный функционал.

Установка Portainer

# Создаём volume для данных Portainer
docker volume create portainer_data

# Запускаем Portainer
docker run -d \
  -p 8000:8000 \
  -p 9443:9443 \
  --name portainer \
  --restart=unless-stopped \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:latest

# Открываем в браузере https://your-server-ip:9443
# Создаём админа и подключаемся к локальному Docker

В веб-интерфейсе Portainer переходите в Stacks → Add stack → Web editor, вставляете ваш docker-compose.yml и включаете опцию «Pull latest image version». Portainer будет автоматически проверять обновления.

Настройка автообновления через Portainer API

# Получение токена авторизации
TOKEN=$(curl -s -X POST https://portainer.local:9443/api/auth \
  -H &amp;amp;amp;amp;quot;Content-Type: application/json&amp;amp;amp;amp;quot; \
  -d &amp;amp;amp;amp;#039;{&amp;amp;amp;amp;quot;username&amp;amp;amp;amp;quot;:&amp;amp;amp;amp;quot;admin&amp;amp;amp;amp;quot;,&amp;amp;amp;amp;quot;password&amp;amp;amp;amp;quot;:&amp;amp;amp;amp;quot;your-password&amp;amp;amp;amp;quot;}&amp;amp;amp;amp;#039; \
  -k | jq -r &amp;amp;amp;amp;#039;.jwt&amp;amp;amp;amp;#039;)

# Обновление всех контейнеров в стеке
curl -X POST &amp;amp;amp;amp;quot;https://portainer.local:9443/api/stacks/1/git/redeploy?pullImage=true&amp;amp;amp;amp;quot; \
  -H &amp;amp;amp;amp;quot;Authorization: Bearer $TOKEN&amp;amp;amp;amp;quot; \
  -k

Способ 4: Kubernetes-подход с Keel

Если вы уже используете Kubernetes или хотите enterprise-решение для Docker Swarm, обратите внимание на Keel. Это как Watchtower на стероидах с поддержкой политик обновления.

Установка Keel для Docker

# docker-compose.yml для Keel
version: &amp;amp;amp;amp;#039;3.8&amp;amp;amp;amp;#039;

services:
  keel:
    image: keelhq/keel:latest
    container_name: keel
    restart: unless-stopped
    ports:
      - &amp;amp;amp;amp;quot;9300:9300&amp;amp;amp;amp;quot;
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - POLL=1
      - POLL_INTERVAL=300  # проверка каждые 5 минут
      - WEBHOOK_ENDPOINT=http://your-server:9300/v1/webhooks/dockerhub
    labels:
      - &amp;amp;amp;amp;quot;keel.policy=major&amp;amp;amp;amp;quot;  # обновлять на major версии
      - &amp;amp;amp;amp;quot;keel.trigger=poll&amp;amp;amp;amp;quot;

Применение политик обновления через labels

# Пример контейнера с политикой обновления
docker run -d \
  --name my-app \
  --label keel.policy=minor \
  --label keel.trigger=poll \
  --label keel.pollSchedule=&amp;amp;amp;amp;quot;@every 10m&amp;amp;amp;amp;quot; \
  myapp:1.0.0

# Политики обновления:
# all - обновлять на любую версию
# major - только major версии (1.x.x -&amp;amp;amp;amp;gt; 2.x.x)
# minor - только minor версии (1.1.x -&amp;amp;amp;amp;gt; 1.2.x)
# patch - только patch версии (1.1.1 -&amp;amp;amp;amp;gt; 1.1.2)

Способ 5: GitHub Actions + Webhook для CI/CD обновлений

Самый продвинутый подход — автоматическое обновление при пуше в репозиторий. Идеально для continuous deployment.

GitHub Actions workflow

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

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Build and Push
        run: |
          docker build -t myuser/myapp:latest .
          docker push myuser/myapp:latest
      
      - name: Deploy to Server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/myapp
            docker-compose pull
            docker-compose up -d
            docker image prune -f

Webhook для мгновенного обновления

# Установка webhook listener на сервере
docker run -d \
  --name webhook \
  -p 9000:9000 \
  -v /opt/scripts:/scripts \
  -v /var/run/docker.sock:/var/run/docker.sock \
  almir/webhook -verbose -hooks=/scripts/hooks.json

# Файл hooks.json
cat &amp;amp;amp;amp;gt; /opt/scripts/hooks.json &amp;amp;amp;amp;lt;&amp;amp;amp;amp;lt; &amp;amp;amp;amp;#039;EOF&amp;amp;amp;amp;#039;
[
  {
    &amp;amp;amp;amp;quot;id&amp;amp;amp;amp;quot;: &amp;amp;amp;amp;quot;update-myapp&amp;amp;amp;amp;quot;,
    &amp;amp;amp;amp;quot;execute-command&amp;amp;amp;amp;quot;: &amp;amp;amp;amp;quot;/scripts/update-myapp.sh&amp;amp;amp;amp;quot;,
    &amp;amp;amp;amp;quot;command-working-directory&amp;amp;amp;amp;quot;: &amp;amp;amp;amp;quot;/opt/myapp&amp;amp;amp;amp;quot;,
    &amp;amp;amp;amp;quot;pass-arguments-to-command&amp;amp;amp;amp;quot;: [],
    &amp;amp;amp;amp;quot;trigger-rule&amp;amp;amp;amp;quot;: {
      &amp;amp;amp;amp;quot;match&amp;amp;amp;amp;quot;: {
        &amp;amp;amp;amp;quot;type&amp;amp;amp;amp;quot;: &amp;amp;amp;amp;quot;payload-hash-sha1&amp;amp;amp;amp;quot;,
        &amp;amp;amp;amp;quot;secret&amp;amp;amp;amp;quot;: &amp;amp;amp;amp;quot;your-secret-key&amp;amp;amp;amp;quot;,
        &amp;amp;amp;amp;quot;parameter&amp;amp;amp;amp;quot;: {
          &amp;amp;amp;amp;quot;source&amp;amp;amp;amp;quot;: &amp;amp;amp;amp;quot;header&amp;amp;amp;amp;quot;,
          &amp;amp;amp;amp;quot;name&amp;amp;amp;amp;quot;: &amp;amp;amp;amp;quot;X-Hub-Signature&amp;amp;amp;amp;quot;
        }
      }
    }
  }
]
EOF

# Скрипт обновления
cat &amp;amp;amp;amp;gt; /opt/scripts/update-myapp.sh &amp;amp;amp;amp;lt;&amp;amp;amp;amp;lt; &amp;amp;amp;amp;#039;EOF&amp;amp;amp;amp;#039;
#!/bin/bash
cd /opt/myapp
docker-compose pull
docker-compose up -d
docker image prune -f
echo &amp;amp;amp;amp;quot;$(date) - Обновление выполнено&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; /var/log/webhook.log
EOF

chmod +x /opt/scripts/update-myapp.sh

Сравнительная таблица методов обновления

Метод Сложность Контроль Подходит для
Watchtower Очень низкая Средний Домашние проекты, тестовые среды
Docker Compose + Cron Низкая Высокий Малый и средний бизнес
Portainer Низкая Высокий Визуальное управление, команды
Keel Средняя Очень высокий Enterprise, Kubernetes
CI/CD + Webhook Высокая Максимальный Продакшен, DevOps команды

Best practices: что нужно знать перед автоматизацией

1. Всегда используйте теги версий, а не latest

# ПЛОХО - непредсказуемое поведение
image: nginx:latest

# ХОРОШО - контролируемые обновления
image: nginx:1.25.3-alpine

# ЕЩЁ ЛУЧШЕ - семантическое версионирование с ограничениями
image: nginx:1.25  # обновится до 1.25.x, но не до 1.26

2. Настройте health checks

# docker-compose.yml с health check
services:
  web:
    image: nginx:latest
    healthcheck:
      test: [&amp;amp;amp;amp;quot;CMD&amp;amp;amp;amp;quot;, &amp;amp;amp;amp;quot;curl&amp;amp;amp;amp;quot;, &amp;amp;amp;amp;quot;-f&amp;amp;amp;amp;quot;, &amp;amp;amp;amp;quot;http://localhost/health&amp;amp;amp;amp;quot;]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

3. Делайте бэкапы перед обновлением

#!/bin/bash
# Улучшенный <a class="wpil_keyword_link" href="https://it-apteka.com/category/scripts/"   title="Скрипты" data-wpil-keyword-link="linked"  data-wpil-monitor-id="251">скрипт</a> обновления с бэкапом

BACKUP_DIR=&amp;amp;amp;amp;quot;/backups/docker/$(date +%Y%m%d_%H%M%S)&amp;amp;amp;amp;quot;
mkdir -p &amp;amp;amp;amp;quot;$BACKUP_DIR&amp;amp;amp;amp;quot;

# Бэкап volumes
docker run --rm \
  -v myapp_data:/data \
  -v &amp;amp;amp;amp;quot;$BACKUP_DIR&amp;amp;amp;amp;quot;:/backup \
  alpine tar czf /backup/myapp_data.tar.gz -C /data .

# Сохранение конфигурации
docker-compose config &amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$BACKUP_DIR/docker-compose.yml&amp;amp;amp;amp;quot;

# Теперь можно безопасно обновлять
docker-compose pull &amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp; docker-compose up -d

4. Используйте staged rollout для критичных систем

# Обновление с проверкой работоспособности
#!/bin/bash

# Скачиваем новый образ
docker-compose pull web

# Запускаем тестовый контейнер
docker-compose up -d web-test

# Ждём 30 секунд
sleep 30

# Проверяем health check
if docker inspect --format=&amp;amp;amp;amp;#039;{{.State.Health.Status}}&amp;amp;amp;amp;#039; web-test | grep -q &amp;amp;amp;amp;quot;healthy&amp;amp;amp;amp;quot;; then
    echo &amp;amp;amp;amp;quot;Тест пройден, обновляем продакшен&amp;amp;amp;amp;quot;
    docker-compose up -d web
    docker-compose stop web-test
else
    echo &amp;amp;amp;amp;quot;Тест провален, откатываемся&amp;amp;amp;amp;quot;
    docker-compose stop web-test
    exit 1
fi

Типичные ошибки и как их избежать

Ошибка 1: Автообновление production без тестирования

Никогда, слышите, НИКОГДА не настраивайте автообновление production контейнеров без staging окружения. Я видел, как одно невинное обновление PostgreSQL уронило весь продакшен на 4 часа из-за breaking changes в конфигурации.

Решение: Создайте staging среду, обновляйте сначала её, тестируйте 24-48 часов, потом обновляйте production.

Ошибка 2: Отсутствие мониторинга после обновлений

# Добавьте уведомления в Telegram после обновления
#!/bin/bash

TELEGRAM_BOT_TOKEN=&amp;amp;amp;amp;quot;your-bot-token&amp;amp;amp;amp;quot;
TELEGRAM_CHAT_ID=&amp;amp;amp;amp;quot;your-chat-id&amp;amp;amp;amp;quot;

send_telegram() {
    curl -s -X POST &amp;amp;amp;amp;quot;https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage&amp;amp;amp;amp;quot; \
        -d chat_id=&amp;amp;amp;amp;quot;${TELEGRAM_CHAT_ID}&amp;amp;amp;amp;quot; \
        -d text=&amp;amp;amp;amp;quot;$1&amp;amp;amp;amp;quot;
}

# После обновления
if docker-compose up -d; then
    send_telegram &amp;amp;amp;amp;quot;✅ Контейнеры успешно обновлены на $(hostname)&amp;amp;amp;amp;quot;
else
    send_telegram &amp;amp;amp;amp;quot;❌ ОШИБКА обновления контейнеров на $(hostname)!&amp;amp;amp;amp;quot;
fi

Ошибка 3: Игнорирование логов обновлений

# Настройте ротацию логов и &amp;amp;amp;lt;a class=&amp;amp;amp;quot;wpil_keyword_link&amp;amp;amp;quot; href=&amp;amp;amp;quot;https://it-apteka.com/category/monitoring/&amp;amp;amp;quot;   title=&amp;amp;amp;quot;Мониторинг&amp;amp;amp;quot; data-wpil-keyword-link=&amp;amp;amp;quot;linked&amp;amp;amp;quot;  data-wpil-monitor-id=&amp;amp;amp;quot;35&amp;amp;amp;quot;&amp;amp;amp;gt;мониторинг&amp;amp;amp;lt;/a&amp;amp;amp;gt;
cat &amp;amp;amp;amp;gt; /etc/logrotate.d/docker-update &amp;amp;amp;amp;lt;&amp;amp;amp;amp;lt; &amp;amp;amp;amp;#039;EOF&amp;amp;amp;amp;#039;
/var/log/docker-update.log {
    daily
    rotate 30
    compress
    missingok
    notifempty
    create 0644 root root
}
EOF

# Проверка логов на ошибки каждый час
cat &amp;amp;amp;amp;gt; /opt/scripts/check-docker-logs.sh &amp;amp;amp;amp;lt;&amp;amp;amp;amp;lt; &amp;amp;amp;amp;#039;EOF&amp;amp;amp;amp;#039;
#!/bin/bash
if grep -i &amp;amp;amp;amp;quot;error\|failed&amp;amp;amp;amp;quot; /var/log/docker-update.log | tail -20; then
    echo &amp;amp;amp;amp;quot;Обнаружены ошибки в логах обновлений!&amp;amp;amp;amp;quot; | &lt;a class=&quot;wpil_keyword_link&quot; href=&quot;https://it-apteka.com/tag/mail/&quot;   title=&quot;mail&quot; data-wpil-keyword-link=&quot;linked&quot;  data-wpil-monitor-id=&quot;105&quot;&gt;mail&lt;/a&gt; -s &amp;amp;amp;amp;quot;Docker Update Errors&amp;amp;amp;amp;quot; admin@company.com
fi
EOF

chmod +x /opt/scripts/check-docker-logs.sh
echo &amp;amp;amp;amp;quot;0 * * * * /opt/scripts/check-docker-logs.sh&amp;amp;amp;amp;quot; | crontab -

Продвинутый пример: полный production-ready скрипт

#!/bin/bash
# Полнофункциональный скрипт автообновления с уведомлениями и откатом

set -euo pipefail

# Конфигурация
COMPOSE_FILE=&amp;amp;amp;amp;quot;/opt/myapp/docker-compose.yml&amp;amp;amp;amp;quot;
BACKUP_DIR=&amp;amp;amp;amp;quot;/backups/docker&amp;amp;amp;amp;quot;
LOG_FILE=&amp;amp;amp;amp;quot;/var/log/docker-update.log&amp;amp;amp;amp;quot;
TELEGRAM_BOT_TOKEN=&amp;amp;amp;amp;quot;your-token&amp;amp;amp;amp;quot;
TELEGRAM_CHAT_ID=&amp;amp;amp;amp;quot;your-chat-id&amp;amp;amp;amp;quot;
MAX_RETRIES=3
HEALTH_CHECK_TIMEOUT=60

# Функция логирования
log() {
    echo &amp;amp;amp;amp;quot;$(date &amp;amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;amp;#039;) - $1&amp;amp;amp;amp;quot; | tee -a &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot;
}

# Функция отправки в Telegram
send_telegram() {
    curl -s -X POST &amp;amp;amp;amp;quot;https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage&amp;amp;amp;amp;quot; \
        -d chat_id=&amp;amp;amp;amp;quot;${TELEGRAM_CHAT_ID}&amp;amp;amp;amp;quot; \
        -d text=&amp;amp;amp;amp;quot;$1&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt; /dev/null
}

# Создание бэкапа
create_backup() {
    local backup_path=&amp;amp;amp;amp;quot;${BACKUP_DIR}/$(date +%Y%m%d_%H%M%S)&amp;amp;amp;amp;quot;
    mkdir -p &amp;amp;amp;amp;quot;$backup_path&amp;amp;amp;amp;quot;
    
    log &amp;amp;amp;amp;quot;Создание бэкапа в $backup_path&amp;amp;amp;amp;quot;
    docker-compose -f &amp;amp;amp;amp;quot;$COMPOSE_FILE&amp;amp;amp;amp;quot; config &amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$backup_path/docker-compose.yml&amp;amp;amp;amp;quot;
    
    # Сохранение списка запущенных контейнеров
    docker-compose -f &amp;amp;amp;amp;quot;$COMPOSE_FILE&amp;amp;amp;amp;quot; ps &amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$backup_path/containers-state.txt&amp;amp;amp;amp;quot;
}

# Проверка здоровья контейнеров
check_health() {
    local timeout=$HEALTH_CHECK_TIMEOUT
    local elapsed=0
    
    while [ $elapsed -lt $timeout ]; do
        if docker-compose -f &amp;amp;amp;amp;quot;$COMPOSE_FILE&amp;amp;amp;amp;quot; ps | grep -q &amp;amp;amp;amp;quot;unhealthy&amp;amp;amp;amp;quot;; then
            log &amp;amp;amp;amp;quot;Обнаружен нездоровый контейнер&amp;amp;amp;amp;quot;
            return 1
        fi
        
        sleep 5
        elapsed=$((elapsed + 5))
    done
    
    return 0
}

# Основная логика обновления
main() {
    log &amp;amp;amp;amp;quot;=== Начало процесса обновления ===&amp;amp;amp;amp;quot;
    send_telegram &amp;amp;amp;amp;quot;🔄 Начинается обновление Docker контейнеров на $(hostname)&amp;amp;amp;amp;quot;
    
    cd &amp;amp;amp;amp;quot;$(dirname &amp;amp;amp;amp;quot;$COMPOSE_FILE&amp;amp;amp;amp;quot;)&amp;amp;amp;amp;quot; || exit 1
    
    # Создаём бэкап
    create_backup
    
    # Скачиваем новые образы
    log &amp;amp;amp;amp;quot;Скачивание новых образов&amp;amp;amp;amp;quot;
    if ! docker-compose pull; then
        log &amp;amp;amp;amp;quot;ERROR: Ошибка при скачивании образов&amp;amp;amp;amp;quot;
        send_telegram &amp;amp;amp;amp;quot;❌ Ошибка скачивания образов на $(hostname)&amp;amp;amp;amp;quot;
        exit 1
    fi
    
    # Проверяем наличие обновлений
    if ! docker-compose pull 2&amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;1 | grep -q &amp;amp;amp;amp;quot;Downloaded newer image&amp;amp;amp;amp;quot;; then
        log &amp;amp;amp;amp;quot;Обновлений не найдено&amp;amp;amp;amp;quot;
        send_telegram &amp;amp;amp;amp;quot;ℹ️ Обновления отсутствуют на $(hostname)&amp;amp;amp;amp;quot;
        exit 0
    fi
    
    # Сохраняем текущее состояние для возможного отката
    local old_images=$(docker images --format &amp;amp;amp;amp;quot;{{.Repository}}:{{.Tag}}&amp;amp;amp;amp;quot; | grep -v &amp;amp;amp;amp;quot;&amp;amp;amp;amp;lt;none&amp;amp;amp;amp;gt;&amp;amp;amp;amp;quot;)
    
    # Обновляем контейнеры
    log &amp;amp;amp;amp;quot;Обновление контейнеров&amp;amp;amp;amp;quot;
    if docker-compose up -d; then
        log &amp;amp;amp;amp;quot;Контейнеры обновлены, проверка здоровья&amp;amp;amp;amp;quot;
        
        # Проверяем здоровье
        if check_health; then
            log &amp;amp;amp;amp;quot;✅ Обновление успешно завершено&amp;amp;amp;amp;quot;
            send_telegram &amp;amp;amp;amp;quot;✅ Контейнеры успешно обновлены на $(hostname)&amp;amp;amp;amp;quot;
            
            # Удаляем старые образы
            docker image prune -f &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; &amp;amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;amp;quot; 2&amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;1
        else
            log &amp;amp;amp;amp;quot;❌ Health check провален, откат изменений&amp;amp;amp;amp;quot;
            send_telegram &amp;amp;amp;amp;quot;⚠️ Health check провален, выполняется откат на $(hostname)&amp;amp;amp;amp;quot;
            
            # Откат
            docker-compose down
            # Здесь можно добавить восстановление из бэкапа
            exit 1
        fi
    else
        log &amp;amp;amp;amp;quot;ERROR: Ошибка при обновлении контейнеров&amp;amp;amp;amp;quot;
        send_telegram &amp;amp;amp;amp;quot;❌ Критическая ошибка обновления на $(hostname)&amp;amp;amp;amp;quot;
        exit 1
    fi
    
    log &amp;amp;amp;amp;quot;=== Процесс обновления завершён ===&amp;amp;amp;amp;quot;
}

# Запуск
main &amp;amp;amp;amp;quot;$@&amp;amp;amp;amp;quot;

Заключение: моя рекомендации

После 20 лет в индустрии и тысяч запущенных контейнеров, вот моя схема использования:

  • Домашняя лаборатория / Dev среда: Watchtower с уведомлениями. Быстро, просто, работает.
  • Staging / малый бизнес: Docker Compose + Cron скрипт с бэкапами и проверками.
  • Production малого/среднего масштаба: Portainer для визуального контроля + скрипты обновления.
  • Enterprise Production: CI/CD pipeline (GitLab CI / GitHub Actions) + Keel для политик версионирования + полноценный мониторинг.
Поделитесь:

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх