Раньше я активно пользовался статическими генераторами сайтов, такими как Hugo. В отдельной статье я подробно описал преимущества этих решений.
Однако у таких генераторов есть значительный недостаток: отсутствие визуального редактора для написания статей. Все материалы приходится оформлять в формате Markdown, используя текстовые редакторы. Для написания статей я пользовался IDE WebStorm.
Через год я устал от необходимости писать статьи в IDE и начал искать замену для Hugo, которая бы поддерживала удобный визуальный редактор. В процессе поисков на просторах интернета мне порекомендовали обратить внимание на движок Ghost. Сейчас этот блог работает именно на нем 😄
Короткий обзор Ghost
Кратко расскажу о возможностях платформы Ghost. Если вы уже знакомы с этим движком, можете сразу переходить к разделу установки.
Удобный редактор статей
Редактор постов в Ghost поддерживает как Markdown, так и визуальное редактирование.
С помощью горячих клавиш и всплывающих меню можно легко менять шрифты, верстку и добавлять необходимые элементы.
Интеграции
Сначала я был рад наличию множества интеграций, думая, что это значительно расширяет функционал. Однако, оказалось, что большинство полезных для меня интеграций доступны только через сервис Zapier, который является платным и довольно дорогим.
В бесплатной версии Zapier можно настроить лишь одну интеграцию, например автоматическую публикацию в Twitter после выхода новой статьи. Однако сделать аналогичный постинг в Telegram уже не получится — для этого потребуется подписка.
Если у вас есть навыки программирования, то можно настроить любые интеграции бесплатно, используя Webhooks.
Ограничение доступа и монетизация
Для создателей контента отличная новость: Ghost поддерживает подписочную модель “из коробки”. Вы можете ограничить доступ к определённым статьям, делая их доступными только для зарегистрированных пользователей или платных подписчиков.
Платформа также поддерживает интеграцию с сервисами Stripe, Patreon и PayPal для приёма платежей, при этом Ghost не взимает комиссий за использование этих функций.
Прочие плюсы
- Публикация по расписанию
- Возможность командной работы: можно организовать небольшую редакцию с разграничением по ролям (редактор, автор и другие)
- RSS-лента
- Теги для постов
- SEO-оптимизация “из коробки”
- Активное развитие платформы: пока я писал эту статью, вышло несколько мажорных обновлений.
Минусы Ghost
Пожалуй, единственный спорный момент — это отсутствие поддержки кластеризации, и её, судя по всему, не будет.
Если вам понадобилась кластеризация, значит, вы делаете что-то не так. Оптимизируйте ваш Nginx.
- Официальная позиция разработчиков Ghost
Для проверки производительности я провёл простое нагрузочное тестирование. В целом, результаты оказались хорошими — блог на Hugo показал примерно такие же результаты под аналогичной нагрузкой.
На этом заканчивается короткий обзор. Более подробную информацию о возможностях Ghost можно найти на официальном сайте или в этой статье.
Есть два основных способа запустить сайт на Ghost:
- Самый простой и самый дорогой — это Ghost Pro. Разработчики Ghost предлагают полностью настроить платформу за вас. Всё, что останется — это писать статьи. Если программирование не ваша сильная сторона, это лучший вариант.
- Запуск на своём сервере — для технически подкованных специалистов или тех, кто готов попробовать сделать всё самостоятельно. Ниже представлена пошаговая инструкция по настройке Ghost на собственном сервере.
Что мы получим
В ходе этой статьи мы запустим сайт на Ghost, используя арендованный сервер. Для этого мы разберёмся, как настроить Ghost, Nginx, CertBot и MariaDB с помощью Docker Compose.
- Nginx будет использоваться в качестве веб-сервера.
- MariaDB послужит для хранения наших статей.
- CertBot позволит получить SSL-сертификат, чтобы сайт работал по протоколу HTTPS.
Также с помощью cron и небольшого скрипта мы настроим автоматическое обновление бесплатных сертификатов Let’s Encrypt.
Настройка Ghost на сервере
Первым шагом необходимо арендовать сервер. Я использую услуги хостинг-провайдера VDSina и полностью доволен их работой.
Я рассматриваю сервер на RHEL 9, поэтому все инструкции будут даны для этой операционной системы. Если вы выберете другую ОС, то возможны некоторые расхождения в процессе установки. После входа на сервер первым делом обновите пакеты командой:
sudo yum update
Для базовой защиты вашего Linux-сервера от взлома советую ознакомиться с этой статьей, где описаны такие шаги, как отключение входа под root, смена стандартного порта и другие методы повышения безопасности.
Установка Docker
Процесс установки Docker и Docker Compose уже описан в отдельной статье. В сети много гайдов, а официальная документация написана доступным языком.
Настройка DNS
Для работы сайта нужен домен. Если вы его ещё не приобрели, рекомендую воспользоваться REG.RU. Вот промокод на скидку 5%: E403-2760-B801-2B3B.
После покупки домена необходимо добавить ресурсные записи DNS для вашего домена. Если вы используете REG.RU, зайдите в список доменов, выберите нужный, затем нажмите “изменить”.
Удалите все существующие записи — они часто содержат IP-адреса регистратора для показа рекламы.
Теперь добавьте две новые записи, чтобы домен ассоциировался с вашим сервером:
Тип записи: A
Subdomain: @
Ip addres: ip_адрес_вашего_сервера
Тип записи: A
Subdomain: www
Ip addres: ip_адрес_вашего_сервера
Результат будет выглядеть примерно так:
Обновление DNS-записей может занять от 15 минут до 24 часов, в зависимости от регистратора. На REG.RU обновления обычно занимают около 15 минут.
Сертификат SSL
Пока обновляются ресурсные записи DNS, вернёмся к нашему серверу. В папке root
создадим директорию для сайта и перейдём в неё:
mdkir /root/site
cd /root/site
В 2024 году без HTTPS никуда, поэтому нам нужно получить SSL-сертификат для нашего домена. Для этого создадим специальный docker-compose файл и конфигурацию Nginx, которые будут использоваться исключительно для получения сертификата.
Создаём новый файл docker-compose.ssl.yaml
:
nano docker-composer.ssl.yaml
Содержимое файла:
version: '3.3'
services:
nginx:
image: nginx
container_name: blog-nginx
hostname: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.ssl.conf:/etc/nginx/nginx.conf
- ./temp:/site
- certbot-etc:/etc/letsencrypt
- certbot-var:/var/lib/letsencrypt
certbot:
image: certbot/certbot
container_name: blog-certbot
volumes:
- certbot-etc:/etc/letsencrypt
- certbot-var:/var/lib/letsencrypt
- ./temp:/var/www/html
depends_on:
- nginx
command: certonly --webroot --webroot-path=/var/www/html --email you@domain.com --agree-tos --no-eff-email --staging -d youdomain.com -d www.youdomain.com
volumes:
certbot-etc:
certbot-var:
Этот docker-compose файл создаёт контейнеры с Nginx и Certbot для получения и сохранения SSL-сертификата. Сертификаты будут сохранены в Docker volume certbot-var.
Теперь создадим конфигурацию Nginx:
mkdir nginx.ssl.conf
Содержимое файла:
events {
worker_connections 1024;
}
http {
include mime.types;
sendfile on;
server {
listen 80;
listen [::]:80;
server_name youdomain.com www.youdomain.com;
location / {
root /blog;
}
location ~ /.well-known/acme-challenge {
allow all;
root /blog;
}
}
}
Запускаем docker-compose:
sudo docker-compose -f docker-compose.ssl.yaml up
Если всё прошло успешно, вы увидите сообщение:
blog-certbot |
blog-certbot | Successfully received certificate.
blog-certbot | Certificate is saved at: /etc/letsencrypt/live/youdomain.com/fullchain.pem
blog-certbot | Key is saved at: /etc/letsencrypt/live/youdomain.com/privkey.pem
blog-certbot | This certificate expires on 2021-12-08.
blog-certbot | These files will be updated when the certificate renews.
blog-certbot |
blog-certbot | NEXT STEPS:
blog-certbot | - The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
blog-certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
blog-certbot exited with code 0
Останавливаем docker-compose комбинацией Ctrl+C
и удаляем контейнеры:
sudo docker-compose -f docker-compose.ssl.yaml down
На самом деле, мы не получили действующий сертификат, а только проверили возможность его получения — это необходимо, чтобы не превысить лимит запросов на сертификаты у Let’s Encrypt.
Теперь, чтобы получить реальный сертификат, замените --staging
на --force-renewal
в файле docker-compose.ssl.yaml
и перезапустите docker-compose. Если всё прошло успешно, вы увидите то же сообщение, но уже с действующим сертификатом.
blog-certbot |
blog-certbot | Successfully received certificate.
blog-certbot | Certificate is saved at: /etc/letsencrypt/live/youdomain.com/fullchain.pem
blog-certbot | Key is saved at: /etc/letsencrypt/live/youdomain.com/privkey.pem
blog-certbot | This certificate expires on 2021-12-08.
blog-certbot | These files will be updated when the certificate renews.
blog-certbot |
blog-certbot | NEXT STEPS:
blog-certbot | - The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
blog-certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
blog-certbot |
blog-certbot | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
blog-certbot | If you like Certbot, please consider supporting our work by:
blog-certbot | * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
blog-certbot | * Donating to EFF: https://eff.org/donate-le
blog-certbot | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
blog-certbot exited with code 0
После получения сертификатов остановите контейнеры:
docker-compose -f docker-compose.ssl.yaml down
Создание docker-compose
Теперь создадим новый файл docker-compose.yml
, который настроит окружение для нашего сайта с использованием Ghost, Nginx, MariaDB и CertBot. Откройте редактор командой:
nano docker-compose.yaml
Содержимое файла:
version: '3.3'
services:
mysql:
container_name: site-mysql
image: mariadb:${MYSQL_IMAGE_TAG}
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_USER: ghost
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_DATABASE: ghost_production
logging:
options:
max-size: "10m"
max-file: "5"
restart: always
volumes:
- ${MYSQL_HOST_PATH}:/var/lib/mysql
nginx:
image: nginx
container_name: site-nginx
hostname: nginx
restart: unless-stopped
logging:
options:
max-size: "10m"
max-file: "5"
ports:
- "80:80"
- "443:443"
depends_on:
- ghost
volumes:
- ./nginx/sites:/etc/nginx/conf.d
- ./nginx/ssl:/etc/nginx/ssl
- ./temp:/site
- certbot-etc:/etc/letsencrypt
- certbot-var:/var/lib/letsencrypt
ghost:
container_name: site-ghost
image: ghost:${GHOST_IMAGE_TAG}
hostname: ghost
logging:
options:
max-size: "10m"
max-file: "5"
environment:
url: ${BLOG_URL}
database__client: mysql
database__connection__host: mariadb
database__connection__database: ghost_production
database__connection__user: ghost
database__connection__password: ${MYSQL_PASSWORD}
depends_on:
- mariadb
restart: always
volumes:
- ${GHOST_HOST_PATH}:/var/lib/ghost/content
certbot:
image: certbot/certbot
container_name: blog-certbot
logging:
options:
max-size: "10m"
max-file: "5"
volumes:
- certbot-etc:/etc/letsencrypt
- certbot-var:/var/lib/letsencrypt
- ./temp:/var/www/html
depends_on:
- nginx
command: certonly --webroot --webroot-path=/var/www/html --email you@domain.com --agree-tos --no-eff-email --staging -d yourdomain.com -d www.yourdomain.com
volumes:
certbot-etc:
certbot-var:
networks:
default:
name: "site"
Мы создаем несколько контейнеров:
- MariaDB: используется для хранения данных сайта.
- Nginx: работает как веб-сервер.
- Ghost: основной движок сайта.
- CertBot: отвечает за получение и обновление SSL-сертификатов каждые 90 дней.
Создайте файл .env
для хранения конфигурационных данных:
nano .env
Содержимое файла:
export GHOST_IMAGE_TAG=5.97.1
export MYSQL_IMAGE_TAG=8.0.36
export BLOG_URL=https://youbomain.com
export MYSQL_ROOT_PASSWORD=password_root
export MYSQL_PASSWORD=password
export MYSQL_HOST_PATH=./mysqldb
export GHOST_HOST_PATH=./www
GHOST_IMAGE_TAG
— версия Ghost. Посмотреть текущую актуальную можно в Docker Hub.MYSQL_IMAGE_TAG
— версия MySQLBLOG_URL
— ваш домен.MYSQL_ROOT_PASSWORD
– пароль root для MySQL.MYSQL_PASSWORD
– пароль для базы данных Ghost.MYSQL_HOST_PATH
иGHOST_HOST_PATH
– пути к локальным директориям для MySQL и Ghost.
Создайте необходимые директории для конфигурации Nginx:
mkdir -p nginx/ssl nginx/sites
Для безопасного соединения сгенерируйте ключ DH:
openssl dhparam -out ./ssl/dhparam.pem 4096
Генерация может занять продолжительное время. У меня она заняла минут десять.
Создайте файл конфигурации Nginx:
nano ./sites/site.conf
Содержимое файла:
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
location ~ /.well-known/acme-challenge {
allow all;
root /site;
}
location / {
return 301 https://yourdomain.com$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_buffer_size 8k;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_protocols SSLv3 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
ssl_ecdh_curve secp384r1;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffering off;
proxy_cache_valid 200 30m;
proxy_cache_valid 404 1m;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_pass http://ghost:2368;
}
}
Запустите приложение командой:
docker-compose up
Перейдите по адресу вашего домена и проверьте, запустился ли сайт.
Если всё работает, остановите текущий запуск Ctrl+C и запустите снова с флагом -d для работы в фоновом режиме:
docker-compose up -d
Теперь можно перейти по адресу https://yourdomain.com/ghost для создания администратора панели управления Ghost и начать работу с контентом!
Обновление сертификата
Сертификаты Let’s Encrypt действительны 90 дней, поэтому важно настроить автоматическое обновление. Один из удобных способов — создание задания через cron
. В этом разделе мы настроим cron-задачу, которая будет автоматически обновлять сертификат и перезагружать конфигурацию Nginx.
Создайте новый скрипт для обновления сертификатов:
nano ssl_renew.sh
Добавьте следующий код:
#!/bin/bash
/usr/local/bin/docker-compose -f /root/site/docker-compose.yaml run certbot renew --dry-run && /usr/local/bin/docker-compose -f /root/site/docker-compose.yaml kill -s SIGHUP nginx
Этот скрипт использует docker-compose run
, чтобы запустить контейнер Certbot для обновления сертификатов, срок действия которых подходит к концу. Затем с помощью сигнала SIGHUP происходит перезагрузка Nginx для применения обновлений.
Сделайте файл исполняемым:
chmod +x ssl_renew.sh
Для тестирования скрипта выполните его с флагом --dry-run
, который имитирует обновление сертификатов без фактического выполнения:
sh ssl_renew.sh
Убедитесь, что симуляция прошла успешно.
Откройте crontab для редактирования:
crontab -e
Добавьте задачу cron, чтобы запускать скрипт каждые 5 минут (для тестирования):
*/5 * * * * /root/site/ssl_renew.sh >> /var/log/cron.log 2>&1
После этого сохраните файл в редакторе vi с помощью клавиш Esc, затем введите :wq
и нажмите Enter. Через несколько минут проверьте логи cron:
tail -f /var/log/cron.log
Вы должны увидеть вывод, подтверждающий успешное обновление сертификатов.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for sadtech.org and www.sadtech.org
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/sadtech.org/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Killing site-nginx ...
Killing site-nginx ... done
После успешного тестирования измените cron-задачу, чтобы скрипт выполнялся раз в день:
0 0 * * * /root/site/ssl_renew.sh >> /var/log/cron.log 2>&1
Не забудьте убрать флаг --dry-run
из скрипта ssl_renew.sh
, чтобы Certbot фактически обновлял сертификаты.
Заключение
В этой статье мы разобрали пошаговую установку и настройку сайта на Ghost с использованием Docker Compose. Сначала мы рассмотрели создание инфраструктуры с контейнерами для Ghost, Nginx, MariaDB и Certbot, что позволило организовать устойчивую и автоматизированную среду для работы сайта. Использование Docker Compose значительно упростило развертывание всех необходимых компонентов, обеспечивая гибкость и легкость в управлении контейнерами.
Мы также настроили SSL-сертификаты с помощью Let’s Encrypt, что является обязательным условием для обеспечения безопасности современных веб-сайтов. Был продемонстрирован процесс генерации и автоматического обновления сертификатов через cron-задачи. Это гарантирует, что ваш сайт всегда будет защищен и доступен по безопасному протоколу HTTPS.
Особое внимание было уделено настройке и оптимизации Nginx, который выступает в роли веб-сервера, перенаправляющего запросы на Ghost и обрабатывающего SSL-трафик. Мы рассмотрели детали конфигурации, такие как поддержка SSL, правила проксирования запросов и использование DH-ключей для дополнительной защиты.
Наконец, мы настроили автоматическое обновление SSL-сертификатов с помощью Certbot и cron, что позволяет вам не беспокоиться о сроках действия сертификатов.
Теперь у вас есть полноценный работающий сайт на Ghost с SSL и полной автоматизацией развертывания и обновления.