Раньше я был адептом статических генераторов сайтов, таких как Hugo. Подробнее о преимуществах статических генераторов сайтов я писал в отдельной статье.
Но есть существенный минус таких генераторов: Нет визуального редактора для статей, их приходится оформлять в формате Markdown через "блокнот".
Я использовал IDE WebStorm для написания статей.
Через год я утомился писать все в IDE, поэтому начал искать замену для Hugo с удобным визуальным редактором. На просторах интернета мне посоветовали обратить внимание на движок Ghost. Теперь этот блог работает на нем 😄
Короткий обзор Ghost
Коротко расскажу о возможностях Ghost. Если вы и так все знаете, то переходите к установке.
Удобный редактор статей
Редактор постов поддерживает Markdown и визуальное редактирование текста.
С помощью горячих клавиш или всплывающих меню можно легко менять шрифты, вёрстку и добавлять нужные элементы.
Интеграции
Сначала я был рад интеграциям, думал что они расширяют функционал и это круто. По факту большинство нужных мне интеграций доступны только через сервис Zapier. А он платный и дорогой.
Бесплатно в Zapier можно сделать только одну интеграцию, например автоматический постинг в твиттер после публикации новой статьи. Но сделать постинг в телеграм уже не получится, нужно покупать подписку.
При желании и навыках программирования, можно бесплатно написать любые интеграции с использованием Webhook.
Ограничение доступа и монетизация
Для контент мейкеров будет отдичная новость, что Ghost поддерживает из коробки подписочную модель. Вы можете делать статьи только для зарегистрированных пользователей, или только для платных подписчиков.
Имеется возможность прикрутить для оплаты Stripe, Patreon или PayPal. Сама платформа Ghost не берёт никаких комиссий.
Прочие плюсы
- Возможность публикации по рассписанию
- Командная работа. Вы можете организовать небольшую редакуцию. Присутствует разграничение по ролям: редактор, автор и прочие.
- RSS лента
- Теги для постов
- SEO Оптимизация сайта из коробки
- Активно развивающийся. Пока я писал статью, вышло несколько мажорных обновлений.
Минусы Ghost
Пожалуй единственный спорный момент это отсутствие кластеризации и ее не будет. Буквально официальная позиция создателей Ghost по этому вопросу: "Если вам понадобилась кластеризация, значит вы делает что-то не так. Оптимизируйте ваш Nginx."
Я решил прогнать простенькое нагрузочное тестирование. В целом результаты хорошие, блог на Hugo показывал примено такие же результаты под такой же нагрузкой.
На этом заканчиваем короткий обзор и переходим к установке. Более подробный обзор возможностей смотрите на официальном сайте Ghost или вот в этой статье.
Есть два варианта создания сайта Ghost:
- Самый простой и самый дорогой – это Ghost Pro. Создатели Ghost предлагают все настроить за вас, вам останется только писать статьи. Если программирование это не про вас, то это лучший вариант.
- Если вы технически подкованный специалист, или не боитесь попробовать самостоятельно, то ниже вы найдете поэтапную инстркцию, как запустить Ghost на своем сервере.
Что мы получим
В процессе статьи, мы запустим свой Ghost сайт на арендованном сервере. Для этого мы разберемся как зпустить Ghost, Nginx, CertBot и MarinaDB в Docker-Compose.
Nginx мы будем использовать в качестве веб-сервера. MarinaDB для хранения наших статей, а CertBot позволит нам получать ssl сертификат, чтобы наш сайт работал на https.
Также с помощью cron и небольшого скрипта, мы настроим автоматичекое обновления бесплатных сертификатов LetEncript.
Настройка Ghost на сервере
Для начала нужно арендовать сервер. Я пользуюсь услугами хостинг-провайдера TimeWeb и полностью ими доволен.
Я рассматриваю CentOS 8, поэтому все инструкции пишу для него, если вы выберете другую ОС, то можете столкнуться с проблемами. Первым делом после входа в систему обновляем пакеты.
yum update
Установка Docker
Обновление займет какое-то время. После обновления необходимо установить docker и docker-compose.
docker:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce docker-ce-cli
sudo systemctl start docker
sudo systemctl enable --now docker
docker-compose:
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
Проверяем, что все прошло успешно:
docker-compose --version
docker-compose version 1.29.2, build 5becea4c
Настройка DNS
Для нашего сайта нам нужен домен. Если вы его еще не купили, то рекомендую REG.RU. А вот промокод на скидку 5%: E403-2760-B801-2B3B
После покупки нам необходимо добавить ресурсные записи DNS для домена.
Если вы использовали REG.RU, то необходимо зайти в список ваших доменов, нажать на необходимый домен и там вы увидите
Нажмите изменить, удалите все что там есть, часто там установлены ip регистратора для рекламы услуг. Теперь вам нужно добавить две записи, чтобы наш домен ассоциировался с нашим сервером.
Тип записи: A
Subdomain: @
Ip addres: ip_адрес_вашего_сервера
Тип записи: A
Subdomain: www
Ip addres: ip_адрес_вашего_сервера
Результат будет выглядить как-то так:
Обновление ресурсных записей может занять от 15 минут до 24 часов. Все зависит от регистратора. На reg.ru обновление занимает обычно минут 15.
Сертификат SSL
Пока ресурсные записи обновляются возвращаемся к нашему серверу. В папке root создаем папку site и перейдем в нее.
mdkir /root/site
cd /root/site
В 2021 году без 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:
Этот композ содержит контейнер с 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;
}
}
}
Запускаем композ:
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
Останавливаем композ сочетанием Ctrl+C
и убиваем контейнеры командой:
sudo docker-compose -f docker-compose.ssl.yaml down
На самом деле мы не получили сертификат, а лишь проверили возможность его получения. Это нужно чтобы не превысить лимит получения сертификатов, который устанавливает LetEncript. Теперь мы точно уверены, что нам дадут сертификат.
Чтобы получить действующие сертификаты нужно заменить --staging
на --force-renewal
в docker-compose.ssl.yaml
Если все пройдет успешно, то увидите вот это
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
Мы получили сертификат и сохранили его в volume докера. Снова убиваем запущенные контейнеры Ctrl+C
:
docker-compose -f docker-compose.ssl.yaml down
Создание docker-compose
Теперь создаем новый файл композа, который будет запускать окружение для нашего сайта: Ghost, Nginx, MarinaDB и CertBot.
nano docker-compose.yaml
Содержимое файла:
version: '3.3'
services:
mariadb:
container_name: site-mariadb
image: mariadb:${MARIADB_IMAGE_TAG}
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_USER: ghost
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_DATABASE: ghost_production
restart: always
volumes:
- ${MYSQL_HOST_PATH}:/var/lib/mysql
nginx:
image: nginx
container_name: site-nginx
hostname: nginx
restart: unless-stopped
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
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
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"
Мы создаем несколько контейнеров:
- MarinaDB – там хранятся наши посты
- Nginx в качестве веб-сервера
- Ghost как движок для сайта
- CertBot для обновления сертификата каждые 90 дней.
Также создаем файл с переменными для docker-compose:
nano .env
Содержимое файла:
export GHOST_IMAGE_TAG=4.21.0
export MARIADB_IMAGE_TAG=latest
export BLOG_URL=https://youbomain.com
export MYSQL_ROOT_PASSWORD=password_root
export MYSQL_PASSWORD=password
export MYSQL_HOST_PATH=./mariadb
export GHOST_HOST_PATH=./www
GHOST_IMAGE_TAG
– версия Ghost. На момент написания статьи актуальная 4.21.0. Посмотреть текущую актуальную можно в Docker Hub.-
MARIADB_IMAGE_TAG
– версия MarinaDB. Берется последняя доступная версия. BLOG_URL
– адрес вашего сайта. Заменить на свой домен.MYSQL_ROOT_PASSWORD
– пароль для root пользователя БД.MYSQL_PASSWORD
– пароль БД для пользователя ghost.MYSQL_HOST_PATH
иGHOST_HOST_PATH
– пути к файлам в контейнере для БД и Ghost.
Теперь создаем папки для конфигурации nginx:
mkdir nginx
cd nginx
mkdir ssl
mkdir sites
Для безопасного SSL соединения сгенерируем ключ 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;
}
}
Переходим обратно в папку site и запускаем наш новый композ:
docker-compose up
Переходим по адресу нашего домена и смотрим на результат
Видим что все получилось. Наш сайт запустился с дефолтной Ghost темой. Также на сайте уже есть стандартные обучающие статьи, которые вы можете удалить из Панели Управления (ПУ).
Мы убедились что все работает. Теперь запустим композ в фоновом режиме, для этого останавливаем текущий запуск Ctrl+C
и запускаем по новой с флагом -d
:
docker-compose up -d
Теперь нам нужно создать администратора для панели управления Ghost. Для этого нужно перейти по адресу https://yourdomain.com/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
и переопределения команды command
на другую команду — субкоманду renew
, которая будет обновлять сертификаты, срок действия которых близок к окончанию. Мы включили параметр --dry-run
, чтобы протестировать наш скрипт.
Закройте файл после завершения редактирования. Сделайте его исполняемым:
chmod +x ssl_renew.sh
Проверяем, что скрипт отработает нормально:
sh ssl_renew.sh
Симуляция прошла успешно. Теперь создаем задачу cron:
crontab -e
По умолчанию открывается редактор vi. Чтобы редактировать файл вам нужно нажать клавишу i
после чего вставить:
*/5 * * * * /root/site/ssl_renew.sh >> /var/log/cron.log 2>&1
После этого нажмите esc
, а далее клавишу :
. После этого введите wq
и нажмите Enter
. Таким образом мы установили задачу на выполнение каждые 5 минут. Через 5 минут проверяем результат выполнения:
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
Теперь вы можете изменить файл crontab
для настройки ежедневного интервала. Чтобы запускать скрипт каждый день в полночь, например, вы должны изменить последнюю строку файла, которая должна выглядеть следующим образом:
0 0 * * * /root/site/ssl_renew.sh >> /var/log/cron.log 2>&1
Не забудьте удалить флаг --dry-run
из скрипта ssl_renew.sh
. Если этого не сделать, то сертификат не будет обновляться.
Заключение
Мы разобрались как запустить сайт Ghost на своем сервере в docker-compose. Теперь вы можете писать свои статьи, в следующей статье я расскажу, как прикрутить к вашему сайту систему комментариев Remark42.