Развёртывание
Templite публикуется как Docker-образ templite/cms на Docker Hub. Production-развёртывание не требует Composer и Node.js на сервере — образ содержит готовый рантайм. Composer-команды выполняются внутри контейнера при первом запуске и при обновлениях.
Альтернативный сценарий
Если на сервере нет Docker (например, shared-хостинг), Templite ставится через standalone-установщик install.php — см. Установка.
Архитектура
Production-стек состоит из шести контейнеров. Они объединяются двумя сетями:
internal— внутренняя docker-сеть (БД, Redis, queue, cron).proxy— внешняя сеть для Nginx Proxy Manager (NPM), который терминирует SSL и проксирует трафик.
| Контейнер | Образ | Назначение |
|---|---|---|
{prefix}_app | templite/cms:latest | PHP-FPM, Laravel + Templite CMS |
{prefix}_nginx | nginx:alpine | Внутренний веб-сервер (HTTP → FPM), за NPM |
{prefix}_db | mysql:8.0 | База данных |
{prefix}_redis | redis:alpine | Кэш, сессии, очереди |
{prefix}_queue | templite/cms:latest | Worker очередей (queues: default, images, email) |
{prefix}_cron | templite/cms:latest | Планировщик задач (запускает schedule:run каждую минуту) |
{prefix} — значение COMPOSE_PROJECT_NAME из .env (по умолчанию templite).
Конфигурация описывается в файле docker-compose.prod.yml, который входит в репозиторий CMS.
Подготовка
1. Создать общую сеть для NPM
Однократно (если NPM ещё не настроен):
docker network create proxy2. Создать docker-compose.prod.yml
Сохраните рядом с будущим проектом:
services:
# PHP-FPM — приложение + первоначальная установка
app:
image: templite/cms:latest
container_name: ${COMPOSE_PROJECT_NAME:-templite}_app
restart: always
environment:
CONTAINER_ROLE: app
APP_DOMAIN: ${APP_DOMAIN:-localhost}
APP_NAME: ${APP_NAME:-Templite}
DB_CONNECTION: ${DB_CONNECTION:-mysql}
DB_HOST: db
DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE:-templite_cms}
DB_USERNAME: ${DB_USERNAME:-templite}
DB_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: redis
REDIS_PASSWORD: ${REDIS_PASSWORD}
ADMIN_LOGIN: ${ADMIN_LOGIN:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-}
PUID: ${PUID:-1000}
PGID: ${PGID:-1000}
volumes:
- ${APP_PATH:-./public_html}:/var/www
- nginx_conf:/opt/nginx
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "test -f /tmp/templite-ready"]
interval: 5s
timeout: 3s
retries: 60
start_period: 180s
networks:
- internal
# Nginx — внутренний веб-сервер (проксируется через NPM)
nginx:
image: nginx:alpine
container_name: ${COMPOSE_PROJECT_NAME:-templite}_nginx
restart: always
volumes:
- ${APP_PATH:-./public_html}:/var/www:ro
- nginx_conf:/etc/nginx/conf.d:ro
depends_on:
app:
condition: service_healthy
networks:
- proxy
- internal
# MySQL 8.0
db:
image: mysql:8.0
container_name: ${COMPOSE_PROJECT_NAME:-templite}_db
restart: always
environment:
MYSQL_DATABASE: ${DB_DATABASE:-templite_cms}
MYSQL_USER: ${DB_USERNAME:-templite}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
command: --default-authentication-plugin=mysql_native_password
networks:
- internal
# Redis — кэш, сессии, очереди
redis:
image: redis:alpine
container_name: ${COMPOSE_PROJECT_NAME:-templite}_redis
restart: always
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
networks:
- internal
# Queue worker — обработка фоновых задач
queue:
image: templite/cms:latest
container_name: ${COMPOSE_PROJECT_NAME:-templite}_queue
restart: always
environment:
CONTAINER_ROLE: queue
PUID: ${PUID:-1000}
PGID: ${PGID:-1000}
working_dir: /var/www
command: ["php", "artisan", "queue:work", "--queue=default,images,email", "--sleep=3", "--tries=3", "--max-time=3600"]
volumes:
- ${APP_PATH:-./public_html}:/var/www
depends_on:
app:
condition: service_healthy
networks:
- internal
# Cron — планировщик задач (каждую минуту)
cron:
image: templite/cms:latest
container_name: ${COMPOSE_PROJECT_NAME:-templite}_cron
restart: always
environment:
CONTAINER_ROLE: cron
PUID: ${PUID:-1000}
PGID: ${PGID:-1000}
volumes:
- ${APP_PATH:-./public_html}:/var/www
command: ["/bin/sh", "-c", "while true; do php /var/www/artisan schedule:run --no-interaction >> /dev/null 2>&1; sleep 60; done"]
depends_on:
app:
condition: service_healthy
networks:
- internal
volumes:
db_data:
redis_data:
nginx_conf:
networks:
proxy:
external: true
internal:
driver: bridge3. Создать .env
Положить рядом с docker-compose.prod.yml. Минимум:
APP_DOMAIN=example.com
DB_PASSWORD=your_secure_db_password
DB_ROOT_PASSWORD=your_secure_root_password
REDIS_PASSWORD=your_secure_redis_password
ADMIN_LOGIN=admin
ADMIN_PASSWORD=your_admin_passwordОпциональные параметры:
| Переменная | Default | Назначение |
|---|---|---|
APP_NAME | Templite | Название сайта |
APP_PATH | ./public_html | Путь на хосте, где хранится код Laravel (bind-mount) |
COMPOSE_PROJECT_NAME | templite | Префикс имён контейнеров |
DB_DATABASE | templite_cms | Имя БД |
DB_USERNAME | templite | Пользователь БД |
PUID / PGID | 1000 | UID/GID хоста для маппинга прав в bind-mount |
MAIL_MAILER | log | Драйвер почты Laravel |
4. Запустить
docker compose -f docker-compose.prod.yml up -dОбраз автоматически скачается с Docker Hub. Билд не требуется — docker compose ... build для prod не работает (у образа нет build-контекста в compose-файле).
5. Первый запуск
При первом старте контейнер app выполняет:
- Создаёт Laravel-проект в
${APP_PATH}черезcomposer create-project. - Устанавливает
templite/cmsчерезcomposer require. - Генерирует
.envприложения с реальными значениями (APP_KEYсоздаётся черезkey:generate). - Накатывает миграции CMS.
- Публикует pre-built ассеты админки.
- Создаёт первого администратора по
ADMIN_LOGIN/ADMIN_PASSWORD. - Сигнализирует готовность через
touch /tmp/templite-ready(healthcheck это видит).
Процесс занимает ~2 минуты. Healthcheck с start_period: 180s даёт запас.
Следить за прогрессом:
docker compose -f docker-compose.prod.yml logs -f appNginx Proxy Manager
В UI NPM создать Proxy Host:
- Domain Names:
example.com - Forward Hostname / IP:
{prefix}_nginx(например,templite_nginx) - Forward Port:
80 - Block Common Exploits: включить
- SSL: Request a new SSL certificate (Let's Encrypt) + Force SSL + HTTP/2 Support
После сохранения сайт доступен по HTTPS. Админка — на https://example.com/cms.
Обновление CMS
Обновление пакета templite/cms через Composer
docker compose -f docker-compose.prod.yml exec app composer update templite/cms --no-dev
docker compose -f docker-compose.prod.yml exec app php artisan cms:update
docker compose -f docker-compose.prod.yml exec app php artisan migrate --forceОбновление Docker-образа
Если изменился сам базовый образ (PHP-версия, системные пакеты):
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d --force-recreateСтруктура файлов на хосте
При первом старте app копирует Laravel-проект в ${APP_PATH:-./public_html} через bind-mount. После этого:
- Код Laravel (включая
vendor/) живёт на хосте, не в образе. - Команды Composer и Artisan выполняются внутри контейнера:
docker compose exec app .... - Папка
public_html/storage/содержит загруженные файлы, кэш и логи — её обязательно включать в бэкап.
Бэкапы
В поставку не входят — настраиваются через cron хоста или внешний backup-сервис.
Дамп БД
docker compose -f docker-compose.prod.yml exec -T db \
mysqldump -u root -p"$DB_ROOT_PASSWORD" templite_cms \
> backup-$(date +%F).sqlАрхивация storage
tar -czf storage-$(date +%F).tar.gz public_html/storageПодводные камни
Подводные камни
- Образ берётся с Docker Hub автоматически (
docker compose up -dсам делаетpull). Командаdocker compose buildне работает — у образа нет build-контекста в compose-файле. APP_KEYсоздаётся автоматически entrypoint-скриптом при первом старте. Генерировать вручную не нужно.ADMIN_PASSWORDсрабатывает только когда в БД ещё нет ни одного админа. Повторный запуск с тем же паролем не пересоздаёт админа и не сбрасывает существующего.- Имена контейнеров используют префикс
${COMPOSE_PROJECT_NAME}(defaulttemplite), разделитель — подчёркивание:templite_app,templite_nginx. Не дефис. - Если меняли
APP_PATH, то Forward Hostname в NPM остаётся тем же (это имя контейнера, а не путь). - Не публикуйте
.env(ни рядом с compose, ни.envприложения внутриpublic_html/) в Git.