Cela fait des mois que je souhaite créer un blog minimaliste, design, performant et optimisé SEO mais je ne suis jamais passé à l’action et pour cause, je manque cruellement de temps.

Dans cet article je vous partage mes réflexions et surtout un guide comme raccourci vous permettant de faire de même.

Pourquoi Ghost ?

Après avoir envisagé WordPress, Strapi, et même SvelteKIT avec de simples fichiers Markdown, je me suis tourné vers Ghost qui :

  • est basé sur Node JS
  • offre de remarquables performances
  • propose du Markdown
  • intègre nativement certaines fonctionnalités essentielles

Tout cela en restant ridiculement simple aussi bien dans l’utilisation que l’installation et la maintenance.

C’est également Open Source, robuste, minimaliste et surtout très agréable à utiliser.

En bref cela colle parfaitement à mes besoins et mes valeurs.

Installation de Ghost sur un VPS

Je déteste dépendre d’un service tiers sur lequel je n’ai pas la main à 100% mais je dois avouer que l’auto hébergement colle aussi bien avec mon côté bricoleur et…Mon envie de tout contrôler. Du coup un VPS avec mes petites applications en « self-hosted », imaginez bien que pour un autiste comme moi c’est le top.

Prérequis

  • Pour suivre ce guide vous devez connaître les bases de l'éditeur de texte "nano" et avoir installé une version récente de docker sur votre machine.
  • Être en capacité d’ajouter un enregistrement de type IPV4 (A) sur son nom de domaine pour le faire pointer sur le VPS.
  • Comprendre et maîtriser l’utilisation du serveur web permettant le reverse proxy et le SSL comme Caddy, Apache ou encore Nginx.

Création du docker compose

  1. Connectez-vous sur votre serveur via SSH et commencez par créer un dossier pour Ghost sur lequel vous aller vous positionner.
mkdir ghost
cd ghost
  1. Utilisez nano pour créer un fichier "docker-compose.yml".
nano docker-compose.yml
  1. Insérez ceci dans le fichier :
services:

  ghost:
    image: ghost:5-alpine
    restart: always
    ports:
      - 3000:2368
    environment:
      # see https://ghost.org/docs/config/#configuration-options
      database__client: mysql
      database__connection__host: db
      database__connection__user: root
      database__connection__password: <votre-mot-de-passe>
      database__connection__database: ghost
      # this url value is just an example, and is likely wrong for your environment!
      url: https://<nom-de-domaine>.com
      # contrary to the default mentioned in the linked documentation, this image defaults to NODE_ENV=production (so development mode needs to be explicitl>
      #NODE_ENV: development
    volumes:
      - ghost:/var/lib/ghost/content
      - ./config.production.json:/var/lib/ghost/config.production.json

  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: <votre-mot-de-passe>
    volumes:
      - db:/var/lib/mysql

  phpmya:
    image: phpmyadmin/phpmyadmin:latest
    container_name: phpmyadmin2
    environment:
      PMA_ARBITRARY: 1
      PMA_HOST: db
      UPLOAD_LIMIT: 20M
    ports:
      - 8080:80
    depends_on:
      - db
    restart: always


volumes:
  ghost:
  db:

Ce docker compose inclus à la fois le CMS Ghost mais également la base de données MySQL sur laquelle il repose et également PhpMyAdmin qui peut permettre certaines choses qui ne sont normalement pas possible. Je pense notamment à la suppression d’un commentaire indésirable sans qu’un message du type « Ce commentaire a été supprimé » n’apparaisse.

⚠️
Pensez à remplacer <votre-mot-de-passe> par un mot de passe sécurisé et <nom-de-domaine> par le votre.

Créer le fichier de configuration Ghost

Ce fichier de configuration

Utilisez nano pour créer un fichier "config.production.json".

nano config.production.json

Insérez ceci dans le fichier :

{
  "url": "http://localhost:2368",
  "server": {
    "port": 2368,
    "host": "::"
  },
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": [
      "file",
      "stdout"
    ]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/lib/ghost/content"
  }
}

Lancer l’app

Il est temps de lancer notre application !

Exécutez la commande suivante.

docker compose up -d

Bravo ! Votre Ghost fonctionne, vous pouvez en attester en visitant cette adresse : « <ip-de-votre-vps>:3000/ghost ».

Configurer votre DNS et votre « reverse proxy »

C’est pas le tout d’avoir Ghost, il faut maintenant lui associer un nom de domaine.

J’ajoute donc un enregistrement dans la zone DNS de mon nom de domaine de type « A » qui contient l’ip de mon serveur afin de le faire pointer vers le VPS.

timtiret.com.		14400 IN A 185.158.107.160

Et je modifie la configuration de caddy pour ajouter une redirection de port :

timtiret.com {
    reverse_proxy localhost:3000 {
      flush_interval -1
    }
}

Après avoir redémarré Caddy, mon blog est accessible depuis mon nom de domaine !

Traduction du thème

En tant que bon français, l’idée de rendre accessible le dev à d’autres qui ne parlent pas anglais me plaît bien. Pour être cohérent, j’ai donc décidé de traduire les boutons d’abonnement du thème « solo » que j’ai choisi.

Dans ce but, on va se connecteur sur le conteneur docker, installer nano et modifier le fichier thème.

Entrer dans le Shell du conteneur

  1. On cherche l’id du conteneur où est Ghost
docker ps
  1. On entre dans le terminal du conteneur en mode interactif (-it)
  docker exec -it <id-du-conteneur>  /bin/sh

Installation de nano sur Alpine

  1. On met à jour les dossiers et on installe nano
apk update
apk add nano

Modification du thème Ghost

  1. On va dans le dossier du thème que l’on souhaite modifier
cd /var/lib/ghost/content/themes/<votre-theme>
  1. On édite le fichier voulu (ex : index.hbs)
nano index.hbs

Sortie et redémarrage du conteneur docker

  1. On sort du conteneur
exit
  1. On redémarre ghost
docker compose restart ghost

Configuration SMTP pour les e-mails transactionnels

En essayant de m’« abonner » à mon propre blog, je me suis rendu compte que ça ne fonctionnait pas.

Une erreur s’affichait, m’envoyant me faire cuire un œuf - et vérifier les logs…

Il s’agissait d’un e-mail transactionnel qui avait décidé de ne pas partir. Normal, je n’ai configuré aucune adresse e-mail.

Si vous n’avez pas déjà d’adresse e-mail disponible en SMTP facilement, je vous conseille d’utiliser le service Brevo qui offre 300 e-mails transactionnels par mois gratuitement. Pour ma part je dispose d’un « Mailu » lui aussi auto hébergé qui me permet de faire cela.

Accéder au fichier de configuration Ghost

Connectez-vous à nouveau en SSH à votre VPS et accédez à nouveau au fichier de configuration créé plus tôt :

nano ghost/config.production.json

Configurer son serveur SMTP

Actuellement, voici à quoi ressemble l’objet email :

  "mail": {
    "transport": "Direct"
  },

Pour utiliser un serveur SMTP, il suffit de le remplacer par quelque-chose qui ressemble à ceci :

  "mail": {
    "transport": "SMTP",
    "options": {
      "host": "<adresse-de-votre-serveur-smtp>",
      "port": 465,
      "secure": true,
      "from": "Timothée Monnier <contact@timotion.fr>",
      "auth": {
        "user": "<contact@timotion.fr>",
        "pass": "<votre-mot-de-passe>"
      }
    }
  },

J’ai volontairement laissé mon prénom et mon nom pour plus de clarté mais sachez que ce qu’il y a dans le from est le nom qui s’affiche lorsqu’un utilisateur reçois un email.

Redémarrer Ghost

Pour appliquer la nouvelle configuration, vous pouvez redémarrer le conteneur Ghost.

cd ghost
docker compose restart ghost

Affichage de code

Nativement il est possible d'afficher du code mais c'est pas très beau, ni efficace, j'ai donc décider d'ajouter la coloration syntaxique ("syntax highlighting") et un bouton de copie.

Pour ce faire, il suffit d'injecter un outil nommé "Prismjs".

Coloration syntaxique

On se rend donc dans les paramètres de Ghost puis tout en bas dans la section "Advanced" sur le menu "Code injection".

Voici ce qu'il faut ajouter dans l'onglet "Site header".

<script src="https://cdn.jsdelivr.net/npm/prismjs/prism.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs/plugins/autoloader/prism-autoloader.min.js" defer></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs/themes/prism-okaidia.min.css">

Ici, j'utilise le thème Okaidia que je préfère personnellement mais vous pouvez remplacer prism-okaidia.min.css pour avoir un autre thème.

En voici la liste :

  • Tomorrow : prism-tomorrow.min.css
  • Dark : prism-dark.min.css
  • Funky : prism-funky.min.css
  • Okaida : prism-okaidia.min.css
  • Twilight : prism-twilight.min.css
  • Coy : prism-coy.min.css
  • Solarized : prism-solarizedlight.min.css

Bouton permettant la copie

De même, pour afficher le petit bouton "copy", voici le code à ajouter.

<script src='https://cdn.jsdelivr.net/npm/prismjs/plugins/toolbar/prism-toolbar.min.js' defer></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js" defer></script>
<style>
  pre[class*="language-"],
code[class*="language-"] {
  color: #d4d4d4;
  font-size: 14px;
  text-shadow: none;
  font-family: Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono",
    "Courier New", monospace;
  direction: ltr;
  text-align: left;
  white-space: pre;
  word-spacing: normal;
  word-break: normal;
  line-height: 1.5;
  -moz-tab-size: 4;
  -o-tab-size: 4;
  tab-size: 4;
  -webkit-hyphens: none;
  -moz-hyphens: none;
  -ms-hyphens: none;
  hyphens: none;
}
pre[class*="language-"]::selection,
code[class*="language-"]::selection,
pre[class*="language-"] *::selection,
code[class*="language-"] *::selection {
  text-shadow: none;
  background: #75a7ca;
}
@media print {
  pre[class*="language-"],
  code[class*="language-"] {
    text-shadow: none;
  }
}
pre[class*="language-"] {
  padding: 1em;
  margin: 0.5em 0;
  overflow: auto;
  background: #1e1e1e;
}
:not(pre) > code[class*="language-"] {
  padding: 0.1em 0.3em;
  border-radius: 0.3em;
  color: #db4c69;
  background: #f9f2f4;
}
pre[data-line] {
  position: relative;
}
pre[class*="language-"] > code[class*="language-"] {
  position: relative;
  z-index: 1;
}
.line-highlight {
  position: absolute;
  left: 0;
  right: 0;
  padding: inherit 0;
  margin-top: 1em;
  background: #f7ebc6;
  box-shadow: inset 5px 0 0 #f7d87c;
  z-index: 0;
  pointer-events: none;
  line-height: inherit;
  white-space: pre;
}
pre[class*="language-"].line-numbers {
  position: relative;
  padding-left: 3.8em;
  counter-reset: linenumber;
}
pre[class*="language-"].line-numbers > code {
  position: relative;
  white-space: inherit;
}
.line-numbers .line-numbers-rows {
  position: absolute;
  pointer-events: none;
  top: 0;
  font-size: 100%;
  left: -3.8em;
  width: 3em;
  letter-spacing: -1px;
  border-right: 1px solid #999;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.line-numbers-rows > span {
  display: block;
  counter-increment: linenumber;
}
.line-numbers-rows > span:before {
  content: counter(linenumber);
  color: #999;
  display: block;
  padding-right: 0.8em;
  text-align: right;
}
div.code-toolbar {
  position: relative;
}
div.code-toolbar > .toolbar {
  position: absolute;
  top: 0.3em;
  right: 0.2em;
  transition: opacity 0.3s ease-in-out;
  opacity: 0.5;
}
div.code-toolbar:hover > .toolbar {
  opacity: 1;
}
div.code-toolbar:focus-within > .toolbar {
  opacity: 1;
}
div.code-toolbar > .toolbar .toolbar-item {
  display: inline-block;
}
div.code-toolbar > .toolbar a {
  cursor: pointer;
}
div.code-toolbar > .toolbar button {
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  line-height: normal;
  overflow: visible;
  padding: 0;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}
div.code-toolbar > .toolbar a,
div.code-toolbar > .toolbar button,
div.code-toolbar > .toolbar span {
  color: #bbb;
  font-size: 0.8em;
  padding: 0 0.5em;
  box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2);
  border-radius: 0.5em;
}
</style>

J'avoue, le CSS ne vient pas de moi et je ne me suis pas amusé à l'analyser mais je l'ai légèrement modifié et je trouve que cela rend bien.

Merci pour votre lecture.

Ghost : Créer facilement un blog