Ghost en conteneur : rendre le thème Casper persistant

Ghost en conteneur : rendre le thème Casper persistant
Photo by Jeremy Bezanger / Unsplash

Les répertoire persistants

Pour des modifications des fichiers Ghost qui survivent à la recréation de conteneur (Stateful), l'équipe de Ghost conseille de faire un bind mount du répertoire /var/lib/ghost/content.

Ainsi, quand vous ajoutez du contenu à votre blog, ce contenu sera en fait écrit sur l'hôte. Si le conteneur vient à être effacé, ou recréé, le contenu est toujours là. Il peut même être sauvegardé à partir de l'hôte. Ghost appelle ça un répertoire Stateful, et Docker appelle ça un répertoire Persistent. Plusieurs façons de faire du persistent, dont le fameux répertoire Bind Mount.

Exemple du Bind Mount conseillé par Ghost :

/data/ghost/main:/var/lib/ghost/content

Cela signifie que le répertoire /data/ghost/main sur l'hôte sera /var/lib/ghost/content dans le conteneur.

Dans le docker-compose.yml servant à créer votre conteneur, cela se traduit par la configuration suivante :

(...)
    volumes:
      - /data/ghost/main:/var/lib/ghost/content
(...)

Problème du répertoire par défaut Casper

Le problème, c'est que le thème par défaut Casper n'est pas installé dans ce répertoire.

En effet, dans /var/lib/ghost/content (votre répertoire persistent côté conteneur), le répertoire themes/casper est en fait un lien symbolique vers /var/lib/ghost/current/content/themes/casper, qui lui n'est pas un répertoire bind mount partagé avec l'hôte.

Constatons.

Dans le conteneur :

root@7555949f3798:~# ls -lh /var/lib/ghost/content/themes/casper
lrwxrwxrwx 1 node node 44 May 17 19:52 /var/lib/ghost/content/themes/casper -> /var/lib/ghost/current/content/themes/casper
root@7555949f3798:~#

Même répertoire dans l'hôte :

# ls -lh /data/ghost/main/themes/casper
lrwxrwxrwx 1 ubuntu ubuntu 44 May 17 21:52 /data/ghost/main/themes/casper -> /var/lib/ghost/current/content/themes/casper
# ls /var/lib/ghost/current/content/themes/casper
ls: cannot access '/var/lib/ghost/current/content/themes/casper': No such file or directory

Illustration du problème

Cela ne pose de problème si vous ne désirez pas modifier ce thème par défaut.

En revanche, quand vous voulez (par exemple) ajouter la possibilité à vos lecteur de laisser des commentaires, il faut aller trifouiller le code du thème.
Vous modifiez ainsi /var/lib/ghost/current/content/themes/casper/post.hbs. Ca marche, c'est magnifique.

Sauf qu'à la prochaine recréation de votre conteneur, ces modifications ont disparu.
En effet, le répertoire /var/lib/ghost/current/content/themes/casper/ est simplement recréé à partir de l'image du conteneur.

NB : Notons que le problème n'existe pas pour des thèmes ajoutés, qui seront installés directement dans /var/lib/ghost/content.

Solution

Pour avoir des fichiers Casper qui survivent à la destruction, vous avez ainsi deux options :

  1. Supprimer le lien symbolique /var/lib/ghost/content/themes/casper et créer deux bind mounts :
    • Le bind mount d'origine :
    /data/ghost/main:/var/lib/ghost/content
    
    • Et un bind mount pour Casper :
    /data/ghost/casper:/var/lib/ghost/content/themes/casper
    
    • Cette solution est sale : vous vous retrouvez avec un bind mount à l'intérieur d'un autre bind mount. Même si en théorie ça marche, ça pourrait sans doute vous créer une arborescence qui pointe vers elle-même. Cela risque de créer un trou noir qui aspirerait tout autour, jusqu'à l'Internet lui-même.
  2. Créer un bind mount de la cible du lien symbolique et laisser le symlink faire son travail :
    • Le bind mount d'origine :
    /data/ghost/main:/var/lib/ghost/content
    
    • Et un bind mount pour la cible du symlink Casper /var/lib/ghost/current/content/themes/casper :
    /data/ghost/casper:/var/lib/ghost/current/content/themes/casper
    

Mise en oeuvre de la solution

Avec le conteneur Ghost démarré (dont le nom est ici acme_ghost_prod) :

  1. Sur l'hôte : copiez le répertoire Casper (qui n'existe que dans le conteneur) vers l'hôte :
cd /data/ghost/
docker cp acme_ghost_prod:/var/lib/ghost/current/content/themes/casper/ .
  • Sur l'hôte : modifier les permissions sur le répertoire :
    • Mettez les mêmes droits que le répertoire de données original déjà existant /data/ghost/main
    # ls -ltrhd /data/ghost/main
    drwx------ 11 ubuntu ubuntu 4.0K May 18 01:34 /data/ghost/main
    # chown -R ubuntu:ubuntu /data/ghost/casper
    # chmod 700 /data/ghost/casper
    

NB : attention, ne pas utiliser l'option -R avec le chmod pour ne pas écraser les permissions existantes. Changez seulement les propriétaires.

  1. Modifiez le docker-compose.yml de votre conteneur acme_ghost_prod.
    Dans la liste des volumes, ajoutez le bind mount Casper après le bind mount d'origine :
(...)
    volumes:
      - /data/ghost/main:/var/lib/ghost/content
      - /data/ghost/casper:/var/lib/ghost/current/content/themes/casper/
(...)
  1. Recréez maintenant le conteneur à partir du docker-compose.yml (les modifications dans docker-compose.yml sont détectées par docker-compose, le conteneur est alors détruit et recréé) :
docker-compose up -d

Résultat

Connectez-vous à votre site pour vérifier que tout va bien, que Ghost peut bien accéder au thème Casper.

Si ce n'est pas le cas, vérifiez bien les chemins (à partir de l'intérieur du conteneur) et les permissions.

Error-500

Voici ce que vous obtenez :
A l'intérieur du conteneur, quand vous modifiez un fichier du thème Casper, ces modifications sont également écrites dans l'hôte. Ainsi, si le conteneur est détruit, les modifications survivent.

Par exemple, dans l'objectif d'ajouter les commentaires Disqus à mes articles, je vais réaliser une copie de sauvegarde du fichier, à partir de l'intérieur du conteneur :

user@host$ docker exec -it acme_ghost_prod /bin/bash
root@bcf642faf42e:~# cd /var/lib/ghost/content/themes/casper
root@bcf642faf42e:/var/lib/ghost/content/themes/casper# cp post.hbs post.hbs.bak
root@bcf642faf42e:/var/lib/ghost/content/themes/casper# exit
exit
user@host$

Dans l'hôte maintenant, je verrai ce nouveau fichier post.hbs.bak :

user@host$ cd /data/ghost/casper
user@host$ ls
assets  author.hbs  default.hbs  error-404.hbs  error.hbs  gulpfile.js  index.hbs  LICENSE  package.json  page.hbs  partials  post.hbs  post.hbs.bak  README.md  tag.hbs  yarn.lock

Si je décide de supprimer mon conteneur et de le recréer, je verrai toujours mon fichier de secours post.hbs.bak, et toute modification dans mon fichier post.hbs sera toujours là :

user@host$ docker-compose down
Removing acme_ghost_prod ... done
user@host$ docker-compose up -d
Creating acme_ghost_prod ... done
user@host$ docker exec -it acme_ghost_prod /bin/bash
root@79915c9ec7a4:/var/lib/ghost# ls /var/lib/ghost/content/themes/casper
LICENSE  README.md  assets  author.hbs  default.hbs  error-404.hbs  error.hbs  gulpfile.js  index.hbs  package.json  page.hbs  partials  post.hbs  post.hbs.bak  tag.hbs  yarn.lock
root@79915c9ec7a4:/var/lib/ghost#