Cachet в Docker Compose: готовый файл
Автоматический статус-монитор вроде Uptime Kuma честно показывает «сервис недоступен», но не умеет рассказать пользователям человеческим языком, что случилось, когда почините и что уже сделано. Cachet закрывает именно эту нишу: это не монитор, а витрина коммуникации во время инцидента — компоненты сервисов, история инцидентов с таймлайном статусов, метрики и подписчики, которым уходит письмо при обновлении. Ниже — рабочий docker-compose.yml, на котором Cachet поднимается вместе с MySQL и Redis, и разбор нюансов первого запуска, которые обычно съедают время.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что такое Cachet и когда он нужен
Cachet — открытый PHP/Laravel-проект для публичных статус-страниц, из того же жанра, что Statuspage.io или Instatus, только self-hosted. У него три ключевые сущности. Компоненты (Components) — сервисы, которые вы показываете пользователям: «API», «Веб-сайт», «Платежи», «База данных» — у каждого свой статус (Operational, Performance Issues, Partial Outage, Major Outage), их можно группировать в Component Groups. Инциденты (Incidents) привязаны к компонентам и ведут таймлайн: «Investigating» → «Identified» → «Watching» → «Fixed», с текстом на каждом шаге — это и есть человеческая коммуникация, которой не хватает голому uptime-монитору. Подписчики (Subscribers) — email-адреса, получающие письмо при создании и обновлении инцидента, без ручной рассылки.
Важно понимать разницу с автоматическим мониторингом: Cachet сам по себе ничего не проверяет — он не ходит по URL и не пингует порты. Статусы и инциденты либо выставляются руками, либо приходят через API от внешнего монитора. Если нужна именно автоматическая страница «зелёная точка = сервис жив», проще решить задачу через Uptime Kuma — статус-страница там генерируется из самих проверок без единой строчки кода. Cachet имеет смысл, когда важна ручная или полуавтоматическая коммуникация с пользователями во время инцидента, а не просто индикатор аптайма.
Проект в последние пару лет развивается медленно — перед установкой сверьтесь с состоянием репозитория на GitHub и датой последнего релиза образа на Docker Hub, чтобы не тащить на прод давно не обновлявшийся PHP-стек с открытыми CVE.
Что подготовить на хосте
Понадобится Docker с Compose plugin — если ещё не установлены, разверните по инструкции про установку Docker Compose для продакшена на VPS. Дальше — домен, который будет указывать на сервер (Cachet чувствителен к правильному APP_URL), и, если нужны email-уведомления подписчикам, доступ к SMTP-серверу — тестировать его лучше сразу с реальными кредами, а не откладывать на потом.
Структура каталогов:
mkdir -p ~/cachet-stack
cd ~/cachet-stack
Cachet — это Laravel-приложение, а значит ему по-настоящему нужны только два внешних сервиса: реляционная БД (MySQL, Postgres или даже SQLite для маленьких инсталляций) и, желательно, Redis — под кеш, сессии и очередь задач, на которой держится отправка писем подписчикам.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверГотовый docker-compose.yml
x-cachet-env: &cachet-env
APP_KEY: ${APP_KEY}
APP_ENV: production
APP_DEBUG: "false"
APP_URL: https://status.example.com
DB_DRIVER: mysql
DB_HOST: db
DB_DATABASE: cachet
DB_USERNAME: cachet
DB_PASSWORD: ${DB_PASSWORD}
CACHE_DRIVER: redis
SESSION_DRIVER: redis
QUEUE_DRIVER: redis
REDIS_HOST: redis
MAIL_DRIVER: smtp
MAIL_HOST: ${MAIL_HOST}
MAIL_PORT: 587
MAIL_USERNAME: ${MAIL_USERNAME}
MAIL_PASSWORD: ${MAIL_PASSWORD}
MAIL_ENCRYPTION: tls
MAIL_FROM_ADDRESS: status@example.com
MAIL_FROM_NAME: "Status Page"
services:
cachet:
image: cachethq/docker:latest
container_name: cachet
restart: unless-stopped
ports:
- "8000:8000"
environment: *cachet-env
volumes:
- cachet_storage:/var/www/html/storage
depends_on:
- db
- redis
networks:
- cachet
cachet-worker:
image: cachethq/docker:latest
container_name: cachet-worker
restart: unless-stopped
command: php artisan queue:work --sleep=3 --tries=3
environment: *cachet-env
volumes:
- cachet_storage:/var/www/html/storage
depends_on:
- db
- redis
networks:
- cachet
db:
image: mysql:8.0
container_name: cachet-db
restart: unless-stopped
environment:
MYSQL_DATABASE: cachet
MYSQL_USER: cachet
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- cachet_db:/var/lib/mysql
networks:
- cachet
redis:
image: redis:7-alpine
container_name: cachet-redis
restart: unless-stopped
volumes:
- cachet_redis:/data
networks:
- cachet
volumes:
cachet_storage:
cachet_db:
cachet_redis:
networks:
cachet:
driver: bridge
Рядом .env для самого Compose (не путать с .env Laravel-приложения внутри контейнера — здесь только секреты для подстановки в compose-файл):
DB_PASSWORD=замените-на-свой-пароль
DB_ROOT_PASSWORD=замените-на-свой-пароль
APP_KEY=
MAIL_HOST=smtp.example.com
MAIL_USERNAME=apikey
MAIL_PASSWORD=замените-на-реальный-ключ
Второй контейнер cachet-worker — не избыточность, а рабочая необходимость: с QUEUE_DRIVER: redis письма подписчикам ставятся в очередь, а обрабатывает её именно воркер с queue:work. Без него письма тихо копятся в Redis и никогда не уходят. Если email не критичен или страница совсем маленькая, воркер можно убрать, поставив QUEUE_DRIVER: sync — тогда письмо уходит синхронно в момент сохранения инцидента, ценой небольшой задержки ответа админке.
Первый запуск: APP_KEY, миграции, установка
APP_KEY — это ключ шифрования Laravel (сессии, куки, часть данных в БД), генерировать его нужно один раз до старта, а не давать пустым — с пустым ключом приложение либо не стартует, либо падает при первой попытке что-то зашифровать. Сгенерировать можно прямо через временный контейнер:
docker run --rm cachethq/docker:latest php artisan key:generate --show
Полученную строку (вида base64:...) вставьте в .env как значение APP_KEY. Дальше поднимаете стек и накатываете миграции:
docker compose up -d db redis
docker compose up -d cachet
docker compose exec cachet php artisan migrate --force
После первой миграции откройте https://ваш-домен (или http://ip:8000 для проверки без домена) — Cachet сам покажет веб-мастер установки: проверку соединения с БД, выбор часового пояса и языка интерфейса, создание первого администратора. Это единственный шаг, который проще пройти через браузер, чем искать соответствующую artisan-команду. Отдельно поднимите воркер, если решили использовать очередь: docker compose up -d cachet-worker.
Частая ошибка на этом шаге — забыть, что APP_URL должен буквально совпадать со схемой и доменом, по которому реально открывается страница. Laravel использует это значение для ссылок на статику и в письмах — несовпадение чаще всего выглядит как «страница открылась без стилей» или «в письме подписчику битая ссылка на инцидент».
Компоненты, группы и инциденты: структура статус-страницы
Прежде чем создавать первый инцидент, стоит один раз продумать структуру компонентов — переделывать её на живой странице с историей менее приятно. Заводите компонент на каждый пользователь-заметный сервис, а не на каждый внутренний микросервис: читателю не интересно, что упала внутренняя очередь, ему интересно, работает ли сайт и API.
Типичная структура для проекта с несколькими продуктами:
- Группа «Основной сайт»: компоненты «Веб-приложение», «API», «Личный кабинет».
- Группа «Инфраструктура»: компоненты «База данных», «Файловое хранилище», «Email-рассылка».
- Группа «Региональные ноды»: если у вас сервера в разных локациях (RU/US/UK), логично завести по компоненту на регион — так пользователь из конкретной локации сразу видит, касается ли инцидент именно его.
Создание инцидента в интерфейсе — это выбор связанного компонента, статуса на таймлайне (Investigating/Identified/Watching/Fixed) и текста апдейта; каждое новое обновление добавляется отдельной записью в тот же таймлайн, а не перезаписывает предыдущую — история остаётся видна целиком, что и отличает Cachet от простого баннера «идут работы».
Отдельная сущность — Metrics: числовые графики (например, среднее время ответа), которые можно пушить в Cachet через API отдельными точками и показывать рядом со статусами компонентов. Для большинства проектов метрики не обязательны — начните с компонентов и инцидентов, добавляйте их только если они реально что-то объясняют читателю страницы.
Автоматизация через API: инциденты не только руками
Ручное создание инцидентов работает, но у Cachet есть REST API, и типичный рабочий паттерн — не заводить статусы вручную при каждом сбое, а автоматически создавать инцидент, когда сработал алерт от системы мониторинга. Токен API создаётся в профиле администратора, дальше запрос выглядит так:
curl -X POST https://status.example.com/api/v1/incidents \
-H "X-Cachet-Token: ваш_api_токен" \
-H "Content-Type: application/json" \
-d '{
"name": "Повышенное время ответа API",
"message": "Наблюдаем деградацию, разбираемся в причине.",
"status": 1,
"component_id": 2,
"component_status": 3,
"notify": true
}'
Поле notify: true включает рассылку подписчикам по этому инциденту. Такой запрос легко дёргать из вебхука Alertmanager, из скрипта, реагирующего на падение проверки в Uptime Kuma, или из логики, похожей на мониторинг cron-задач через Healthchecks.io — принцип тот же: внешнее событие бьёт по API, а не человек кликает в интерфейсе. Нативных интеграций со Slack/Telegram у Cachet нет — если нужны уведомления команде в мессенджер, а не только email подписчикам, эту логику придётся навесить отдельно поверх того же API.
Обратный прокси, HTTPS и типичные проблемы
Наружу пробрасывать порт 8000 напрямую не стоит — перед Cachet нужен реверс-прокси с TLS, например Nginx как reverse-proxy с сертификатом Let's Encrypt. Важно прокидывать заголовки X-Forwarded-For и X-Forwarded-Proto, иначе в логах будет светиться IP прокси вместо реального клиента.
Проблемы, которые чаще всего всплывают на старте:
- Белый экран или 500 при первом заходе. Обычно пустой или неверный
APP_KEY— сгенерируйте заново командой выше и перезапустите контейнер. - Мастер установки зацикливается на шаге проверки БД. Контейнер
cachetстартовал раньше, чем MySQL успел инициализироваться — добавьтеdepends_onс healthcheck наdb, либо просто перезапуститеcachetчерез минуту после первого старта стека. - Письма подписчикам не приходят. Либо не поднят
cachet-workerприQUEUE_DRIVER: redis, либо неверные SMTP-креды — проверьте логи воркера:docker compose logs -f cachet-worker. - Стили и скрипты не грузятся, страница «голая». Почти всегда несовпадение
APP_URLс реальным адресом, по которому открыт сайт — проверьте схему (http/https) и домен буквально посимвольно. - Ошибки записи в
storage. Каталог/var/www/html/storageдолжен принадлежать пользователю, от которого работает PHP-FPM внутри контейнера — с именованным volume из примера это решается само; при bind-mount с хоста права выставляются вручную.
Если Cachet всё ещё капризничает, начинайте диагностику с docker compose logs по каждому сервису отдельно, а не по всему стеку разом.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли обойтись без Redis и воркера, только с MySQL?
Да, для маленькой страницы это рабочий вариант: поставьте CACHE_DRIVER: file, SESSION_DRIVER: file, QUEUE_DRIVER: sync — письма подписчикам будут отправляться синхронно, замедляя ответ при сохранении инцидента, но без Redis приложение работает.
Чем Cachet принципиально отличается от статус-страницы на Uptime Kuma?
Uptime Kuma генерирует статус-страницу автоматически из результатов своих проверок — просто, но без ручного текста и полноценной истории апдейтов. Cachet — инструмент коммуникации: статусы и текст инцидентов задаются руками или через API, зато вы контролируете, что именно написано пользователям.
Нужна ли отдельная база данных, если MySQL на сервере уже есть под другие проекты?
Нет, заведите отдельную базу и пользователя для Cachet и укажите их в DB_* переменных вместо контейнера db из примера — тогда сервис db в compose-файле просто не нужен.
Как перенести Cachet на другой сервер?
Бэкапьте volume cachet_db (или дамп MySQL) и volume cachet_storage — вместе с идентичным APP_KEY в .env этого достаточно, ключ менять нельзя, иначе расшифровка существующих сессий и части настроек сломается.
Что делать, если официальный образ давно не обновлялся?
Проверьте Docker Hub на дату последнего пуша тега перед установкой; для внутренней или некритичной статус-страницы старый образ допустим, для публичной страницы важного сервиса стоит рассмотреть более активно поддерживаемую альтернативу.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →