Zurück zum Blog

Ghost auf Cloud Foundry betreiben

Lena Fuhrimann•

Dieser Blog läuft auf Ghost. Das ist eine ziemlich leichtgewichtige Blogging-Plattform auf Basis von Node.js. Schauen wir uns an, wie sie sich auf Cloud Foundry betreiben lässt.

Services erstellen

Um Ghost zu betreiben, brauchen wir zwei Services: eine Datenbank und einen E-Mail-Server. Erstellen wir zuerst die Datenbank. Ich verwende hier die Swisscom Application Cloud, du kannst aber jeden Cloud-Foundry-Anbieter nutzen. Wir erstellen einen kleinen MariaDB-Service, der wie MySQL funktioniert und daher von Ghost verwendet werden kann. Führe zum Erstellen folgenden Befehl aus:

$ cf create-service mariadbent usage blog-db

Jetzt können wir unseren E-Mail-Service erstellen. Am einfachsten geht das mit Mailgun. Erstelle einfach kostenlos ein Konto auf deren Seite und hinterlege die Credentials in einem User-Provided Service in Cloud Foundry, um beide zu verbinden:

$ cf create-user-provided-service mailgun -p '{ "username": "<your-mailgun-smtp-login>", "password": "<your-mailgun-password>" }'

Ersetze die Werte in <> durch deine jeweiligen Credentials.

Den Quellcode beschaffen

Den Quellcode von Ghost zu bekommen, ist mühelos. Besuche einfach die Releases-Seite und lade die neueste Version als ZIP-Archiv herunter. Entpacke es und wechsle im Terminal mit cd in den entsprechenden Ordner.

Entrypoint-Skript erstellen

Ghost muss über eine Konfigurationsdatei konfiguriert werden. Wir nennen unsere config.producton.json, da sie für einen produktiven Blog geeignet sein soll. Diese Datei sagt Ghost, wo es seine Datenbank findet, welchen E-Mail-Server es nutzen soll und wie es den Blog generell betreiben soll.

In Cloud Foundry werden Services dynamisch konfiguriert, was in einer einfachen JSON-Datei nicht möglich ist. Wir umgehen das mit einem Bash-Skript, das die Umgebung ausliest und die Konfigurationsdatei zur Laufzeit erzeugt. Erstelle im Root-Verzeichnis deiner App eine neue Datei namens entrypoint-cf.sh und füge folgenden Inhalt ein:

#!/bin/bash

set -e -u

# App-URL
app_uri="$(echo "${VCAP_APPLICATION}" | jq -r '.application_uris[0] // ""')"
app_url="https://${app_uri}"

# Datenbank
db_credentials="$(echo "${VCAP_SERVICES}" | jq -r '.["mariadbent"][0].credentials // ""')"
if [ -z "${db_credentials}" ]; then
  echo "Error: Please bind a MariaDB service" >&2
  exit 1
fi
db_host="$(echo "${db_credentials}" | jq -r '.host // ""')"
db_username="$(echo "${db_credentials}" | jq -r '.username // ""')"
db_password="$(echo "${db_credentials}" | jq -r '.password // ""')"
db_database="$(echo "${db_credentials}" | jq -r '.database // ""')"

# E-Mail-Service
email_credentials="$(echo "${VCAP_SERVICES}" | jq -r '.["user-provided"][0].credentials // ""')"
if [ -z "${db_credentials}" ]; then
  echo "Error: Please bind an Email service" >&2
  exit 1
fi
email_username="$(echo "${email_credentials}" | jq -r '.username // ""')"
email_password="$(echo "${email_credentials}" | jq -r '.password // ""')"

# Konfigurationsdatei erstellen
jq -n "{
    url: \"${app_url}\",
    mail: {
        transport: \"SMTP\",
        options: {
            service: \"Mailgun\",
            auth: {
                user: \"${email_username}\",
                pass: \"${email_password}\"
            }
        }
    },
    database: {
        client: \"mysql\",
        connection: {
            host: \"${db_host}\",
            user: \"${db_username}\",
            password: \"${db_password}\",
            database: \"${db_database}\"
        }
    },
    server: {
        host: \"0.0.0.0\",
        port: ${PORT}
    }
}" > config.production.json

# DB initialisieren und migrieren
./node_modules/.bin/knex-migrator init
./node_modules/.bin/knex-migrator migrate

# Die App starten
npm start

Mache das Skript dann mit folgendem Befehl ausführbar:

$ chmod +x entrypoint-cf.sh

Dieses Skript holt sich alle nötigen Umgebungsvariablen und erzeugt mit jq (das im Node.js-Buildpack vorinstalliert ist) einen JSON-String mit der Konfiguration, der dann in eine Datei config.production.json geschrieben wird. Danach führt das Skript (falls nötig) eine Datenbankmigration aus und startet die App.

Jetzt müssen wir Cloud Foundry nur noch sagen, dass es zum Starten der App dieses Skript ausführen soll, statt direkt npm start aufzurufen (der Standard für Node.js-Apps). Das machen wir in der Datei manifest.yml, aus der Cloud Foundry seine Anweisungen zum Betrieb einer App bezieht. Erstelle im Root-Verzeichnis der App eine neue Datei namens manifest.yml mit folgendem Inhalt:

applications:
  - name: my-blog
    memory: 256MB
    buildpacks:
      - https://github.com/cloudfoundry/nodejs-buildpack.git
    command: ./entrypoint-cf.sh
    services:
      - blog-db
      - mailgun
    env:
      NODE_ENV: production

Das weist CF an, das richtige Buildpack zu verwenden und die App mit unserem Entrypoint-Skript zu starten. Zudem setzt es die Umgebungsvariable NODE_ENV auf production, was Node.js und Ghost auf bessere Performance optimiert. Ausserdem sagt es Cloud Foundry, die beiden oben erstellten Services an unsere Blog-App zu binden.

Die Konfiguration unserer App ist damit fertig. Jetzt musst du nur noch folgenden Befehl ausführen, damit unser Blog in der Cloud läuft:

$ cf push

Willkommen in der fabelhaften Welt des Ghost-Bloggings!

Disqus hinzufügen (optional)

Deinen Leserinnen und Lesern das Kommentieren deiner Blogbeiträge zu ermöglichen, ist ein guter Weg, deinen Blog interaktiver zu machen. Disqus einzubinden, ist ein unkomplizierter Weg dorthin.

Besuche einfach deren Website, erstelle ein Konto und registriere deinen Blog als neue Site. Öffne dann content/themes/casper/post.hbs und suche in der Datei nach einem Kommentar zu Disqus. Dort gibt es einen Abschnitt, den du auskommentieren und in dem du die Beispiel-URL durch die deines Blogs ersetzen musst. Folge den Schritten, die im Kommentar beschrieben sind.

Danach führst du cf push aus – das war’s. Dein Blog ist jetzt interaktiv.

Object Storage hinzufügen (optional)

Irgendwann willst du Bilder und andere Assets hochladen, die aus deinem Blog erreichbar sind (z.B. für Header-Bilder von Beiträgen). Wenn du das jetzt tust, gehen die Bilder bei jedem Neustart der App verloren. Wir müssen diese Bilder also in einem S3-Storage ablegen. Die folgenden Abschnitte zeigen am Beispiel der Swisscom Application Cloud, wie das geht.

Folge dazu diesem Tutorial, um einen S3-Service samt Bucket zu erstellen, und nenne die Service-Instanz «blog-storage».

Als Nächstes müssen wir die App an unseren neu erstellten Service binden. Füge folgende Zeile im Abschnitt services unserer manifest.yml hinzu:

- blog-storage

Jetzt müssen wir den ghost-storage-adapter-s3 installieren, damit Ghost weiss, wie es mit unserem S3-Service sprechen soll. Führe dazu die Installationsbefehle vom obigen Link in dem Ordner aus, in dem du das Ghost-Repo hast. Cloud Foundry wird versuchen, die Abhängigkeiten für Ghost mit yarn zu installieren. Da die Installationsbefehle den S3-Storage-Adapter mit npm installieren, müssen wir das ändern. Das erreichst du ganz einfach, indem du die Datei yarn.lock entfernst:

$ rm yarn.lock

Als Nächstes müssen wir unser Skript entrypoint-cf.sh anpassen, damit es den S3-Service einbezieht. Füge die folgenden Zeilen dort ein, wo die Service-Variablen gelesen werden:

# Storage-Service
s3_credentials="$(echo "${VCAP_SERVICES}" | jq -r '.["dynstrg"][0].credentials // ""')"
if [ -z "${s3_credentials}" ]; then
  echo "Error: Please bind an S3 service" >&2
  exit 1
fi
s3_endpoint="$(echo "${s3_credentials}" | jq -r '.accessHost // ""')"
s3_namespace="$(echo "${s3_credentials}" | jq -r '.namespace // ""')"
s3_access_key_id="$(echo "${s3_credentials}" | jq -r '.accessKey // ""')"
s3_secret_access_key="$(echo "${s3_credentials}" | jq -r '.sharedSecret // ""')"

Füge dann den folgenden Teil zum JSON-Konfigurations-Template am Ende der Datei hinzu:

    storage: {
      active: \"s3\",
      s3: {
        endpoint: \"${s3_endpoint}\",
        assetHost: \"https://${s3_namespace}.ds11s3ns.swisscom.com/${S3_BUCKET_NAME}\",
        accessKeyId: \"${s3_access_key_id}\",
        secretAccessKey: \"${s3_secret_access_key}\",
        bucket: \"${S3_BUCKET_NAME}\"
      }
    },

Damit weist du Ghost an, den S3-Storage-Adapter zu verwenden.

Jetzt musst du unsere App nur noch erneut pushen:

$ cf push

Und das war’s! Alle Bilder, die du jetzt über die Ghost-Admin-Konsole hochlädst, landen in deinem S3-Service.

Syntax-Highlighting hinzufügen (optional)

Mit highlight.js bekommst du sauberes Syntax-Highlighting für die Code-Snippets in deinen Blogbeiträgen (siehe Beispiel oben). Es unterstützt viele Programmiersprachen und verschiedene Themes.

Um es in deinen Blog zu bringen, fügst du die folgenden Snippets einfach im Bereich «Code injection» deiner Ghost-Einstellungen ein:

Blog-Header:

<style>
  .hljs {
    color: #a9b7c6;
    background: #282b2e;
    display: block;
    overflow-x: auto;
    padding: 0.5em;
  }
  .hljs-number,
  .hljs-literal,
  .hljs-symbol,
  .hljs-bullet {
    color: #6897bb !important;
  }
  .hljs-keyword,
  .hljs-selector-tag,
  .hljs-deletion {
    color: #cc7832 !important;
  }
  .hljs-variable,
  .hljs-template-variable,
  .hljs-link {
    color: #629755 !important;
  }
  .hljs-comment,
  .hljs-quote {
    color: #808080 !important;
  }
  .hljs-meta {
    color: #bbb529 !important;
  }
  .hljs-string,
  .hljs-attribute,
  .hljs-addition {
    color: #6a8759 !important;
  }
  .hljs-section,
  .hljs-title,
  .hljs-type {
    color: #ffc66d !important;
  }
  .hljs-name,
  .hljs-selector-id,
  .hljs-selector-class {
    color: #e8bf6a !important;
  }
  .hljs-emphasis {
    font-style: italic;
  }
  .hljs-strong {
    font-weight: bold;
  }
</style>

Blog-Footer:

<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script>
  hljs.initHighlightingOnLoad();
</script>

Wenn du ein anderes Theme verwenden willst (das obige heisst androidstudio), musst du das minifizierte CSS deines Themes in die <style>-Tags des Headers kopieren und danach bei allen Farben !important ergänzen, damit sie nicht vom Theme von Ghost überschrieben werden.

Damit wird highlight.js geladen und initialisiert. Klicke auf «Save», um deinen Blog zu aktualisieren, und geniesse farbiges Syntax-Highlighting!