Cron-задачи в Docker: запуск и мониторинг

Обновлено 2 ноября 2026

Контейнеры рассчитаны на один долгоживущий процесс, а cron-задачи короткие и периодические. Из этого несоответствия растёт большинство проблем: задачи не видят переменные окружения, работают не в том часовом поясе, пишут ошибки в никуда или тихо перестают запускаться, когда контейнер заменили. Вот три распространённые схемы и как сделать каждую надёжной.

Три способа запускать задачи по расписанию

1. Cron на хосте запускает команду в контейнере

Crontab на сервере вызывает docker exec в работающем контейнере или запускает разовый контейнер через docker run --rm. Это самый простой вариант, образ менять не нужно:

# команда в работающем сервисе app
17 3 * * * docker compose -f /srv/app/compose.yml exec -T app php artisan report:send

# новый контейнер на каждый запуск
*/10 * * * * docker run --rm --env-file /srv/app/.env registry.example.com/sync:latest

-T отключает псевдотерминал, которого у cron нет; без этого флага docker compose exec падает. Минус схемы — расписание живёт на сервере, а не в репозитории.

2. Отдельный контейнер-планировщик

Запустите из того же образа отдельный контейнер, единственный процесс которого — планировщик. Подойдёт и классический cron, но для контейнеров удобнее supercronic: он читает обычный crontab, передаёт задачам переменные окружения контейнера и пишет их вывод в stdout, где его видит docker logs.

# compose.yml
services:
  app:
    image: registry.example.com/app:1.8
  scheduler:
    image: registry.example.com/app:1.8
    command: ["supercronic", "/app/crontab"]
    env_file: .env

Файл crontab лежит в репозитории вместе с кодом, поэтому расписания проходят ревью и выкатываются как всё остальное. Устанавливайте supercronic из релизов на GitHub и проверяйте опубликованную контрольную сумму в Dockerfile.

3. Cron внутри контейнера с приложением

Так можно, но понадобится менеджер процессов, чтобы запускать и cron, и приложение, а падение одного может уронить другое. Лучше выбрать вариант 2.

Ловушки

Убедитесь, что каждый запуск состоялся

Когда контейнер-планировщик остановился, выпал из нового деплоя или потерял crontab, ничего не запускается и никто не сообщает об ошибке. Добавьте пинг в каждую задачу, чтобы тишина превращалась в алерт. В образах Alpine есть wget из BusyBox; curl можно поставить через apk add curl.

# пинг только после успешного запуска
*/10 * * * * /app/sync.sh && wget -q -T 10 -O /dev/null https://ping.heartcron.com/<uuid>

# передать код выхода: 0 — успех, остальное сразу отправляет алерт
*/10 * * * * /app/sync.sh; wget -q -T 10 -O /dev/null https://ping.heartcron.com/<uuid>/$?

В Heartcron задайте проверке то же расписание и часовой пояс, что в crontab, и запас времени чуть больше обычной длительности задачи.

Другие руководства