Создаем блог на Ghost

Рассказываю о преимуществах движка для блогов Ghost. Также делюсь пошаговой инструкцией по настройке своего Ghost сайта.

· 11 минуты на чтение
Создаем блог на Ghost

Раньше я активно пользовался статическими генераторами сайтов, такими как 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
Оптимизация NGINX
Оптимизация веб сервера nginx позволит вам ускорить ваш сайт, сократив время ответа сервера.

Для проверки производительности я провёл простое нагрузочное тестирование. В целом, результаты оказались хорошими — блог на 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 и полностью доволен их работой.

По этой реферальной ссылке вы получите 10% скидки навсегда.

Я рассматриваю сервер на RHEL 9, поэтому все инструкции будут даны для этой операционной системы. Если вы выберете другую ОС, то возможны некоторые расхождения в процессе установки. После входа на сервер первым делом обновите пакеты командой:

sudo yum update

Для базовой защиты вашего Linux-сервера от взлома советую ознакомиться с этой статьей, где описаны такие шаги, как отключение входа под root, смена стандартного порта и другие методы повышения безопасности.

Гайд по защите Linux сервера от взлома
Базовая защита от взлома вашего linux сервера. Запрет входа для root пользователя, смена стандартного порта и другие способы защиты.

Более подробно о защите сервера, я писал в этой статье

Установка Docker

Процесс установки Docker и Docker Compose уже описан в отдельной статье. В сети много гайдов, а официальная документация написана доступным языком.

Установка Docker и Docker Compose
Гайдов, как устанавливать docker полно в интернете. Официальная документации написана доступным языком. Команды установки docker для CentOS 8 и RHEL 8. sudo yum install -y yum-utils sudo yum-config-manager --add-repo 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.

Настройка 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 минут.

Спонсор поста 3

Сертификат 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:
В строке 27 замените youdomain.com на ваш домен, а you@domain.com — на вашу почту.

Этот 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;
        }
    }
}
Не забудьте в строке 12 заменить youdomain.com на ваш домен.

Запускаем 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"
Замените значения домена и почты на свои в строке 57.

Мы создаем несколько контейнеров:

  • 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 — версия MySQL
  • BLOG_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;
     }
}
Замените домен на ваш в строках 5, 13, 20 и 22.

Запустите приложение командой:

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 и полной автоматизацией развертывания и обновления.

Struchkov Mark
Struchkov Mark
Задавайте вопросы, если что-то осталось не понятным👇