Почему автоматическое обновление контейнеров — это важно
Представьте: у вас 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;quot;com.centurylinklabs.watchtower.enable=true&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;#039;3.8&amp;amp;amp;#039;
services:
web:
image: nginx:latest
container_name: web-server
restart: unless-stopped
ports:
- &amp;amp;amp;quot;80:80&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;quot;/var/log/docker-update.log&amp;amp;amp;quot;
echo &amp;amp;amp;quot;$(date &amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;#039;) - Начало обновления контейнеров&amp;amp;amp;quot; &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot;
# Скачиваем последние образы
docker-compose pull &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot; 2&amp;amp;amp;gt;&amp;amp;amp;amp;1
# Проверяем, есть ли обновления
if docker-compose pull | grep -q &amp;amp;amp;quot;Downloaded newer image&amp;amp;amp;quot;; then
echo &amp;amp;amp;quot;$(date &amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;#039;) - Найдены обновления, перезапуск...&amp;amp;amp;quot; &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot;
# Останавливаем контейнеры
docker-compose down &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot; 2&amp;amp;amp;gt;&amp;amp;amp;amp;1
# Запускаем с новыми образами
docker-compose up -d &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot; 2&amp;amp;amp;gt;&amp;amp;amp;amp;1
# Удаляем старые образы
docker image prune -f &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot; 2&amp;amp;amp;gt;&amp;amp;amp;amp;1
echo &amp;amp;amp;quot;$(date &amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;#039;) - Обновление завершено успешно&amp;amp;amp;quot; &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot;
else
echo &amp;amp;amp;quot;$(date &amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;#039;) - Обновлений не найдено&amp;amp;amp;quot; &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot;
fi
Настройка cron для автоматического запуска
# Делаем &amp;lt;a class=&amp;quot;wpil_keyword_link&amp;quot; href=&amp;quot;https://it-apteka.com/category/scripts/&amp;quot; title=&amp;quot;Скрипты&amp;quot; data-wpil-keyword-link=&amp;quot;linked&amp;quot; data-wpil-monitor-id=&amp;quot;67&amp;quot;&amp;gt;скрипт&amp;lt;/a&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;quot;Content-Type: application/json&amp;amp;amp;quot; \
-d &amp;amp;amp;#039;{&amp;amp;amp;quot;username&amp;amp;amp;quot;:&amp;amp;amp;quot;admin&amp;amp;amp;quot;,&amp;amp;amp;quot;password&amp;amp;amp;quot;:&amp;amp;amp;quot;your-password&amp;amp;amp;quot;}&amp;amp;amp;#039; \
-k | jq -r &amp;amp;amp;#039;.jwt&amp;amp;amp;#039;)
# Обновление всех контейнеров в стеке
curl -X POST &amp;amp;amp;quot;https://portainer.local:9443/api/stacks/1/git/redeploy?pullImage=true&amp;amp;amp;quot; \
-H &amp;amp;amp;quot;Authorization: Bearer $TOKEN&amp;amp;amp;quot; \
-k
Способ 4: Kubernetes-подход с Keel
Если вы уже используете Kubernetes или хотите enterprise-решение для Docker Swarm, обратите внимание на Keel. Это как Watchtower на стероидах с поддержкой политик обновления.
Установка Keel для Docker
# docker-compose.yml для Keel
version: &amp;amp;amp;#039;3.8&amp;amp;amp;#039;
services:
keel:
image: keelhq/keel:latest
container_name: keel
restart: unless-stopped
ports:
- &amp;amp;amp;quot;9300:9300&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;quot;keel.policy=major&amp;amp;amp;quot; # обновлять на major версии
- &amp;amp;amp;quot;keel.trigger=poll&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;quot;@every 10m&amp;amp;amp;quot; \ myapp:1.0.0 # Политики обновления: # all - обновлять на любую версию # major - только major версии (1.x.x -&amp;amp;amp;gt; 2.x.x) # minor - только minor версии (1.1.x -&amp;amp;amp;gt; 1.2.x) # patch - только patch версии (1.1.1 -&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;gt; /opt/scripts/hooks.json &amp;amp;amp;lt;&amp;amp;amp;lt; &amp;amp;amp;#039;EOF&amp;amp;amp;#039;
[
{
&amp;amp;amp;quot;id&amp;amp;amp;quot;: &amp;amp;amp;quot;update-myapp&amp;amp;amp;quot;,
&amp;amp;amp;quot;execute-command&amp;amp;amp;quot;: &amp;amp;amp;quot;/scripts/update-myapp.sh&amp;amp;amp;quot;,
&amp;amp;amp;quot;command-working-directory&amp;amp;amp;quot;: &amp;amp;amp;quot;/opt/myapp&amp;amp;amp;quot;,
&amp;amp;amp;quot;pass-arguments-to-command&amp;amp;amp;quot;: [],
&amp;amp;amp;quot;trigger-rule&amp;amp;amp;quot;: {
&amp;amp;amp;quot;match&amp;amp;amp;quot;: {
&amp;amp;amp;quot;type&amp;amp;amp;quot;: &amp;amp;amp;quot;payload-hash-sha1&amp;amp;amp;quot;,
&amp;amp;amp;quot;secret&amp;amp;amp;quot;: &amp;amp;amp;quot;your-secret-key&amp;amp;amp;quot;,
&amp;amp;amp;quot;parameter&amp;amp;amp;quot;: {
&amp;amp;amp;quot;source&amp;amp;amp;quot;: &amp;amp;amp;quot;header&amp;amp;amp;quot;,
&amp;amp;amp;quot;name&amp;amp;amp;quot;: &amp;amp;amp;quot;X-Hub-Signature&amp;amp;amp;quot;
}
}
}
}
]
EOF
# Скрипт обновления
cat &amp;amp;amp;gt; /opt/scripts/update-myapp.sh &amp;amp;amp;lt;&amp;amp;amp;lt; &amp;amp;amp;#039;EOF&amp;amp;amp;#039;
#!/bin/bash
cd /opt/myapp
docker-compose pull
docker-compose up -d
docker image prune -f
echo &amp;amp;amp;quot;$(date) - Обновление выполнено&amp;amp;amp;quot; &amp;amp;amp;gt;&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;quot;CMD&amp;amp;amp;quot;, &amp;amp;amp;quot;curl&amp;amp;amp;quot;, &amp;amp;amp;quot;-f&amp;amp;amp;quot;, &amp;amp;amp;quot;http://localhost/health&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;quot;/backups/docker/$(date +%Y%m%d_%H%M%S)&amp;amp;amp;quot; mkdir -p &amp;amp;amp;quot;$BACKUP_DIR&amp;amp;amp;quot; # Бэкап volumes docker run --rm \ -v myapp_data:/data \ -v &amp;amp;amp;quot;$BACKUP_DIR&amp;amp;amp;quot;:/backup \ alpine tar czf /backup/myapp_data.tar.gz -C /data . # Сохранение конфигурации docker-compose config &amp;amp;amp;gt; &amp;amp;amp;quot;$BACKUP_DIR/docker-compose.yml&amp;amp;amp;quot; # Теперь можно безопасно обновлять docker-compose pull &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;#039;{{.State.Health.Status}}&amp;amp;amp;#039; web-test | grep -q &amp;amp;amp;quot;healthy&amp;amp;amp;quot;; then
echo &amp;amp;amp;quot;Тест пройден, обновляем продакшен&amp;amp;amp;quot;
docker-compose up -d web
docker-compose stop web-test
else
echo &amp;amp;amp;quot;Тест провален, откатываемся&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;quot;your-bot-token&amp;amp;amp;quot;
TELEGRAM_CHAT_ID=&amp;amp;amp;quot;your-chat-id&amp;amp;amp;quot;
send_telegram() {
curl -s -X POST &amp;amp;amp;quot;https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage&amp;amp;amp;quot; \
-d chat_id=&amp;amp;amp;quot;${TELEGRAM_CHAT_ID}&amp;amp;amp;quot; \
-d text=&amp;amp;amp;quot;$1&amp;amp;amp;quot;
}
# После обновления
if docker-compose up -d; then
send_telegram &amp;amp;amp;quot;✅ Контейнеры успешно обновлены на $(hostname)&amp;amp;amp;quot;
else
send_telegram &amp;amp;amp;quot;❌ ОШИБКА обновления контейнеров на $(hostname)!&amp;amp;amp;quot;
fi
Ошибка 3: Игнорирование логов обновлений
# Настройте ротацию логов и &amp;amp;lt;a class=&amp;amp;quot;wpil_keyword_link&amp;amp;quot; href=&amp;amp;quot;https://it-apteka.com/category/monitoring/&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;35&amp;amp;quot;&amp;amp;gt;мониторинг&amp;amp;lt;/a&amp;amp;gt;
cat &amp;amp;amp;gt; /etc/logrotate.d/docker-update &amp;amp;amp;lt;&amp;amp;amp;lt; &amp;amp;amp;#039;EOF&amp;amp;amp;#039;
/var/log/docker-update.log {
daily
rotate 30
compress
missingok
notifempty
create 0644 root root
}
EOF
# Проверка логов на ошибки каждый час
cat &amp;amp;amp;gt; /opt/scripts/check-docker-logs.sh &amp;amp;amp;lt;&amp;amp;amp;lt; &amp;amp;amp;#039;EOF&amp;amp;amp;#039;
#!/bin/bash
if grep -i &amp;amp;amp;quot;error\|failed&amp;amp;amp;quot; /var/log/docker-update.log | tail -20; then
echo &amp;amp;amp;quot;Обнаружены ошибки в логах обновлений!&amp;amp;amp;quot; | <a class="wpil_keyword_link" href="https://it-apteka.com/tag/mail/" title="mail" data-wpil-keyword-link="linked" data-wpil-monitor-id="105">mail</a> -s &amp;amp;amp;quot;Docker Update Errors&amp;amp;amp;quot; admin@company.com
fi
EOF
chmod +x /opt/scripts/check-docker-logs.sh
echo &amp;amp;amp;quot;0 * * * * /opt/scripts/check-docker-logs.sh&amp;amp;amp;quot; | crontab -
Продвинутый пример: полный production-ready скрипт
#!/bin/bash
# Полнофункциональный скрипт автообновления с уведомлениями и откатом
set -euo pipefail
# Конфигурация
COMPOSE_FILE=&amp;amp;amp;quot;/opt/myapp/docker-compose.yml&amp;amp;amp;quot;
BACKUP_DIR=&amp;amp;amp;quot;/backups/docker&amp;amp;amp;quot;
LOG_FILE=&amp;amp;amp;quot;/var/log/docker-update.log&amp;amp;amp;quot;
TELEGRAM_BOT_TOKEN=&amp;amp;amp;quot;your-token&amp;amp;amp;quot;
TELEGRAM_CHAT_ID=&amp;amp;amp;quot;your-chat-id&amp;amp;amp;quot;
MAX_RETRIES=3
HEALTH_CHECK_TIMEOUT=60
# Функция логирования
log() {
echo &amp;amp;amp;quot;$(date &amp;amp;amp;#039;+%Y-%m-%d %H:%M:%S&amp;amp;amp;#039;) - $1&amp;amp;amp;quot; | tee -a &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot;
}
# Функция отправки в Telegram
send_telegram() {
curl -s -X POST &amp;amp;amp;quot;https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage&amp;amp;amp;quot; \
-d chat_id=&amp;amp;amp;quot;${TELEGRAM_CHAT_ID}&amp;amp;amp;quot; \
-d text=&amp;amp;amp;quot;$1&amp;amp;amp;quot; &amp;amp;amp;gt; /dev/null
}
# Создание бэкапа
create_backup() {
local backup_path=&amp;amp;amp;quot;${BACKUP_DIR}/$(date +%Y%m%d_%H%M%S)&amp;amp;amp;quot;
mkdir -p &amp;amp;amp;quot;$backup_path&amp;amp;amp;quot;
log &amp;amp;amp;quot;Создание бэкапа в $backup_path&amp;amp;amp;quot;
docker-compose -f &amp;amp;amp;quot;$COMPOSE_FILE&amp;amp;amp;quot; config &amp;amp;amp;gt; &amp;amp;amp;quot;$backup_path/docker-compose.yml&amp;amp;amp;quot;
# Сохранение списка запущенных контейнеров
docker-compose -f &amp;amp;amp;quot;$COMPOSE_FILE&amp;amp;amp;quot; ps &amp;amp;amp;gt; &amp;amp;amp;quot;$backup_path/containers-state.txt&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;quot;$COMPOSE_FILE&amp;amp;amp;quot; ps | grep -q &amp;amp;amp;quot;unhealthy&amp;amp;amp;quot;; then
log &amp;amp;amp;quot;Обнаружен нездоровый контейнер&amp;amp;amp;quot;
return 1
fi
sleep 5
elapsed=$((elapsed + 5))
done
return 0
}
# Основная логика обновления
main() {
log &amp;amp;amp;quot;=== Начало процесса обновления ===&amp;amp;amp;quot;
send_telegram &amp;amp;amp;quot;🔄 Начинается обновление Docker контейнеров на $(hostname)&amp;amp;amp;quot;
cd &amp;amp;amp;quot;$(dirname &amp;amp;amp;quot;$COMPOSE_FILE&amp;amp;amp;quot;)&amp;amp;amp;quot; || exit 1
# Создаём бэкап
create_backup
# Скачиваем новые образы
log &amp;amp;amp;quot;Скачивание новых образов&amp;amp;amp;quot;
if ! docker-compose pull; then
log &amp;amp;amp;quot;ERROR: Ошибка при скачивании образов&amp;amp;amp;quot;
send_telegram &amp;amp;amp;quot;❌ Ошибка скачивания образов на $(hostname)&amp;amp;amp;quot;
exit 1
fi
# Проверяем наличие обновлений
if ! docker-compose pull 2&amp;amp;amp;gt;&amp;amp;amp;amp;1 | grep -q &amp;amp;amp;quot;Downloaded newer image&amp;amp;amp;quot;; then
log &amp;amp;amp;quot;Обновлений не найдено&amp;amp;amp;quot;
send_telegram &amp;amp;amp;quot;ℹ️ Обновления отсутствуют на $(hostname)&amp;amp;amp;quot;
exit 0
fi
# Сохраняем текущее состояние для возможного отката
local old_images=$(docker images --format &amp;amp;amp;quot;{{.Repository}}:{{.Tag}}&amp;amp;amp;quot; | grep -v &amp;amp;amp;quot;&amp;amp;amp;lt;none&amp;amp;amp;gt;&amp;amp;amp;quot;)
# Обновляем контейнеры
log &amp;amp;amp;quot;Обновление контейнеров&amp;amp;amp;quot;
if docker-compose up -d; then
log &amp;amp;amp;quot;Контейнеры обновлены, проверка здоровья&amp;amp;amp;quot;
# Проверяем здоровье
if check_health; then
log &amp;amp;amp;quot;✅ Обновление успешно завершено&amp;amp;amp;quot;
send_telegram &amp;amp;amp;quot;✅ Контейнеры успешно обновлены на $(hostname)&amp;amp;amp;quot;
# Удаляем старые образы
docker image prune -f &amp;amp;amp;gt;&amp;amp;amp;gt; &amp;amp;amp;quot;$LOG_FILE&amp;amp;amp;quot; 2&amp;amp;amp;gt;&amp;amp;amp;amp;1
else
log &amp;amp;amp;quot;❌ Health check провален, откат изменений&amp;amp;amp;quot;
send_telegram &amp;amp;amp;quot;⚠️ Health check провален, выполняется откат на $(hostname)&amp;amp;amp;quot;
# Откат
docker-compose down
# Здесь можно добавить восстановление из бэкапа
exit 1
fi
else
log &amp;amp;amp;quot;ERROR: Ошибка при обновлении контейнеров&amp;amp;amp;quot;
send_telegram &amp;amp;amp;quot;❌ Критическая ошибка обновления на $(hostname)&amp;amp;amp;quot;
exit 1
fi
log &amp;amp;amp;quot;=== Процесс обновления завершён ===&amp;amp;amp;quot;
}
# Запуск
main &amp;amp;amp;quot;$@&amp;amp;amp;quot;
Заключение: моя рекомендации
После 20 лет в индустрии и тысяч запущенных контейнеров, вот моя схема использования:
- Домашняя лаборатория / Dev среда: Watchtower с уведомлениями. Быстро, просто, работает.
- Staging / малый бизнес: Docker Compose + Cron скрипт с бэкапами и проверками.
- Production малого/среднего масштаба: Portainer для визуального контроля + скрипты обновления.
- Enterprise Production: CI/CD pipeline (GitLab CI / GitHub Actions) + Keel для политик версионирования + полноценный мониторинг.



