Under the hood of our blogging platform

This article is a REX on how we choose and implement our blogging platform to meet our needs. Comparison between Ghost, Wordpress, Medium and Dev.to.

Under the hood of our blogging platform

Two years ago, at the beginning of 2020, we wanted to improve the visibility of Michelin's internal IT so we decided to create our technology blog, but we had to choose the platform to host it. This article is a REX on how we chose and implemented our blogging platform to meet our needs.

TL;DR We use GHOST to 'dust off the image of IT in the industry'

Why a Michelin technical blog ?

Michelin is known worldwide for its tires and tire-related services but less for its internal IT strategies / technologies / organization to support end-to-end design to services around tires.

Why launching this blog managed by Michelin IT experts? Our great team :-) wanted to:

  • Demystify and share what Michelin does with IT, both at technical and organizational levels.
  • Deploy a continuous learning platform to share ideas & and our expertise for innovative technologies, our passion for the next generation of software.
  • Give back to the community how we have implemented our solutions based on common components.

Many companies share technical news and achievements of their IT teams;

Below, find a list of some of our great peers sharing their technical insights, experience and useful tips.

OVHcloud Blog
Innovation for Freedom
Decathlon Technology – Medium
Empowering The Sport Tech Community.
Google Developers Blog
News and insights on Google platforms, tools, and events.
Netflix TechBlog
Learn about Netflix’s world class engineering efforts, company culture, product developments and more.
The Official Microsoft Blog

Our platform Choice

We wanted a straightforward platform to share blog posts and pages with a community.

Our decision drivers were:

  • Worldwide availability without restriction
  • Easy to use
  • Moderation feature
  • Customizable design
  • Easily extendable

Based on the market analysis and existing technical blogs, we have shortlisted four blogging platforms:

Let's see in detail our choice:

Medium

Medium is one of the best blogging platform with a vast community but not available in every country.

Pro and cons:

  • User-friendly interface and editing features
  • Large user base
  • Saas platform
  • Paid subscription needed for readers
  • Blocked in China mainland

dev.to

Is becoming the best place to share and exchange technical articles around development topics.

Pro and cons:

  • User-friendly interface
  • Free for everyone
  • Saas platform
  • Focused on technical and development oriented articles.
  • Young platform with small user base.

Wordpress

It is the most widely used content management system (CMS) to create any website (landing page, blog, corporate site, ...). Its primary disadvantage is that it meets all needs without targeting one in particular.

Pro and cons:

  • Very important technical community to adapt wordpress.
  • Extremely extendable in terms of design and functionalities.
  • Open source tool.
  • Saas or Self Hosted
  • Many plugins to validate/use before having something usable.
  • Too many functionalities for our needs.

Ghost

Ghost is a powerful app for blog creators to publish, share, and grow a business around their content. It comes with modern tools to publish content.

Pro and cons:

  • User-friendly interface, UX is top notch
  • Open source tool.
  • Focused as only a blogging platform.
  • Saas or Self Hosted
  • Not fully cloud ready.

Finally, we decided to retain Ghost because this tool is made to meet the needs of a blog, and important actors use it in IT like Mozilla, Revolut, Cloudflare, OpenAI, Apple, etc...

Organization

In parallel with the sourcing of the blogging platform, we defined an editorial guideline and a process with the Michelin communication Team to publish articles.

By editorial guidelines, we mean defining our playground, such as the tone to be used, the language of articles, the topics to be covered, the profiles of contributors...

Then we worked on defining the identity of our blog.

For the graphical part, the blog had to respect the corporate identity and style guide (colors, fonts, ...). Our efforts were therefore concentrated on finding a slogan to carry this identity that we wanted a little provocative while being clear and punchy.

And we found it :

Ready to dust off the image of IT in the industry?

Implementation

First, we decided to use Ghost as a SaaS service for the first year by subscribing to Ghost Pro to use the service quickly.

Ghost pro offers several price tiers for a managed service with patches, backups, CDN, SSL certificate management, with limitations on the number of users and administrators.

Theme creation

After that, we created a Michelin theme based on the default Ghost theme (Casper). We updated the font and color of the default theme to respect the Michelin Group's visual identity and extended it to integrate some libraries.

Two libraries have been added to the default theme:

  • tarteaucitron.js to easily respect the privacy of your visitors and ask for their consent on third-party services and cookies.
  • medium-zoom.js to zoom on the images of a post.
GitHub - michelin/IT-Blog-Theme: Michelin IT blog theme
Michelin IT blog theme. Contribute to michelin/IT-Blog-Theme development by creating an account on GitHub.
Michelin Ghost theme repository in Github

Architecture

After one year of using the blog in SaaS with Ghost Pro, we encountered one issue with the license. The number of people who could publish a post was limited to 15 users (staff users) in the largest license without paying an extra 5€/month/user.

So we decide to host and deploy the blog in Michelin infrastructure.

Recently the number of staff users changed to unlimited in the Ghost Pro Business license.

Azure Paas Services

The principal requirement of Ghost is to use a Mysql database. So we created a dedicated "Azure database for Mysql" with an Azure private endpoint to allow connections to the database only from it and block all other access.

By default, Ghost stores any asset uploaded locally to its filesystem and delivers them via the same Ghost front-end service which delivers web pages. To improve performance for assets loading and decrease server load we use a custom storage adapter to manage assets directly through Azure blob storage service.

We set up our Azure blob storage to allow public blob access to be able to retrieve images from internet and we deploy Azure Front Door on top of the blob storage to minimize latency. Azure Front Door is a content delivery network (CDN) service that delivers high performance and scalability for our content. This CDN allows us to serve worldwide assets with a custom DNS Anycast managed by Microsoft.

These Azure services are deployed in the same Azure resource group dedicated to our blog production environment.

Ghost engine

To avoid maintaining a virtual Machine dedicated to hosting the blog, we deployed Ghost in Kubernetes using the community docker image.

Our Azure implementation of Ghost

Based on this Docker Image we extended it by adding the custom storage adapter and the Michelin theme.

FROM docker.michelin.com/ghost:4.xx.x-alpine

# Add storage adapter
RUN mkdir -p content/adapters/storage
COPY ghost-azure-storage/ content/adapters/storage/ghost-azure-storage/

# Add Michelin theme
RUN mkdir -p content/themes/Michelin
ADD Michelin.zip content/themes/
RUN unzip content/themes/Michelin.zip -d content/themes/Michelin

To deploy the application into Kubernetes we used a Kustomize template.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ghost
  labels:
    app.kubernetes.io/name: ghost
    app.kubernetes.io/managed-by: kustomize
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: ghost
  replicas: 1
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ghost
    spec:
      containers:
        - name: ghost
          image: ghost-image
          ports:
            - name: https
              containerPort: 2368
          resources:
            limits:
              cpu: 1000m
              memory: 1Gi
            requests:
              cpu: 350m
              memory: 512Mi

For this deployment, only one replica was needed as Ghost doesn't currently support High Availability. So it is not possible today to deploy multiple instances of Ghost behind a load-balancer without issues (cf documentation).

Ghost does not consume much in terms of resources knowing that the images are served with a CDN.

We have here 1 CPU and 1GiB of RAM in limit.

Deployment container configuration:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ghost
spec:
  template:
    spec:
      containers:
        - name: ghost
          volumeMounts:
            - name: config
              mountPath: /var/lib/ghost/config.production.json
              subPath: config.production.json
              readOnly: true
          readinessProbe:
            httpGet:
              path: /ghost/api/v4/admin/site/
              port: https
              httpHeaders:
                - name: X-Forwarded-Proto
                  value: https
                - name: Host
                  value: blogit.michelin.io
            initialDelaySeconds: 30
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 6
            successThreshold: 1
          livenessProbe:
            httpGet:
              path: /ghost/api/v4/admin/site/
              port: https
              httpHeaders:
                - name: X-Forwarded-Proto
                  value: https
                - name: Host
                  value: blogit.michelin.io
            initialDelaySeconds: 120
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 6
            successThreshold: 1
      volumes:
        - name: config
          secret:
            secretName: config

We use and declare a volume 'config' managed as a secret in kubernetes based on a specific json configuration file to configure the service.

Kubernetes also needs to monitor the state of the service (liveness and readiness), for that it needs API monitoring endpoints. By default, ghost don't provide health API endpoints, so we used the admin API to monitor the application state as a workaround.

For the probe, the initialDelaySeconds is more important on liveness than readiness because some database schema migrations can take time.

Continuous integration & deployment

Finally, to ease deployment and upgrade we implemented a custom CI/CD with Gitlab to build, archive, promote and deploy our blogging platform.

Gitlab CI/CD pipeline to deploy in production

Steps details :

  • build : Construct the Azure Blob Storage Adapter with NPM
  • test (Only for Merge Request): launch a dry run on Kustomize template and create the docker image locally with Kaniko.
  • archive : build and push the docker image to our docker registry with Kaniko
  • promote : flag the constructed image as ready to be deployed in production
  • deploy : apply Kustomize template in the production cluster to update the desired application state.

Now, for each update or each code changes in the Git repository, a pipeline is triggered for Merge Request, we build and test containers. On the main branch we build, archive and deploy to acceptance environment and on a specific git tag the above pipeline deploys to production.

Conclusion

Hosting the Ghost platform ourselves leveraging Azure PaaS services and Michelin internal services allows us to put in place the "eat your own dog food" practice for this blogging platform.

Today's only downside to deploying Ghost in a container is that Ghost v4 is stateful. By default, Ghost caches all created blog posts, pages on the file system to improve performance. But this prevents us from having multiple blog engine instances to perform high availability.

To conclude, I love the simplicity of the ghost platform, it is precisely what we needed. Something simple, powerful with a large community.
Ghost also natively offers integration with some popular services like Zapier, Google Analytics, AMP, ... to extend the current platform's capabilities.
For example, we integrated Ghost and our Twitter account @michelinIT_eng to notify our community of new blog posts. A great first step to reinforce our external visibility.