Skip to content

Развёртывание

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}_apptemplite/cms:latestPHP-FPM, Laravel + Templite CMS
{prefix}_nginxnginx:alpineВнутренний веб-сервер (HTTP → FPM), за NPM
{prefix}_dbmysql:8.0База данных
{prefix}_redisredis:alpineКэш, сессии, очереди
{prefix}_queuetemplite/cms:latestWorker очередей (queues: default, images, email)
{prefix}_crontemplite/cms:latestПланировщик задач (запускает schedule:run каждую минуту)

{prefix} — значение COMPOSE_PROJECT_NAME из .env (по умолчанию templite).

Конфигурация описывается в файле docker-compose.prod.yml, который входит в репозиторий CMS.

Подготовка

1. Создать общую сеть для NPM

Однократно (если NPM ещё не настроен):

bash
docker network create proxy

2. Создать docker-compose.prod.yml

Сохраните рядом с будущим проектом:

yaml
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: bridge

3. Создать .env

Положить рядом с docker-compose.prod.yml. Минимум:

dotenv
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_NAMETempliteНазвание сайта
APP_PATH./public_htmlПуть на хосте, где хранится код Laravel (bind-mount)
COMPOSE_PROJECT_NAMEtempliteПрефикс имён контейнеров
DB_DATABASEtemplite_cmsИмя БД
DB_USERNAMEtempliteПользователь БД
PUID / PGID1000UID/GID хоста для маппинга прав в bind-mount
MAIL_MAILERlogДрайвер почты Laravel

4. Запустить

bash
docker compose -f docker-compose.prod.yml up -d

Образ автоматически скачается с Docker Hub. Билд не требуется — docker compose ... build для prod не работает (у образа нет build-контекста в compose-файле).

5. Первый запуск

При первом старте контейнер app выполняет:

  1. Создаёт Laravel-проект в ${APP_PATH} через composer create-project.
  2. Устанавливает templite/cms через composer require.
  3. Генерирует .env приложения с реальными значениями (APP_KEY создаётся через key:generate).
  4. Накатывает миграции CMS.
  5. Публикует pre-built ассеты админки.
  6. Создаёт первого администратора по ADMIN_LOGIN / ADMIN_PASSWORD.
  7. Сигнализирует готовность через touch /tmp/templite-ready (healthcheck это видит).

Процесс занимает ~2 минуты. Healthcheck с start_period: 180s даёт запас.

Следить за прогрессом:

bash
docker compose -f docker-compose.prod.yml logs -f app

Nginx 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

bash
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-версия, системные пакеты):

bash
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-сервис.

Дамп БД

bash
docker compose -f docker-compose.prod.yml exec -T db \
  mysqldump -u root -p"$DB_ROOT_PASSWORD" templite_cms \
  > backup-$(date +%F).sql

Архивация storage

bash
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} (default templite), разделитель — подчёркивание: templite_app, templite_nginx. Не дефис.
  • Если меняли APP_PATH, то Forward Hostname в NPM остаётся тем же (это имя контейнера, а не путь).
  • Не публикуйте .env (ни рядом с compose, ни .env приложения внутри public_html/) в Git.

Связанные разделы

Распространяется под лицензией MIT.