Migrating from Ghost to Jekyll

Published: Sep 1, 2021 by Isaac Johnson

In our last blog we worked out Github Pages and blogging with jekyll and Github sites. But what if you have a blog already, such as this one in Ghost. How can we migrate and will it be benificial? What does an Azure DevOps pipeline look like?

Today we’ll explore a few paths to creating a Jekyll based blog then show how we can import from Ghost and update our pipeline.

Setup

We will start by following this guide.

Starting a fresh blog

First, install Ruby

builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ sudo apt install ruby
[sudo] password for builder:
Reading package lists... Done
Building dependency tree
…

Next, we can follow the steps for our OS: https://jekyllrb.com/docs/installation/ubuntu/

sudo apt-get install ruby-full build-essential zlib1g-dev
echo '# Install Ruby Gems to ~/gems' >> ~/.bashrc
echo 'export GEM_HOME="$HOME/gems"' >> ~/.bashrc
echo 'export PATH="$HOME/gems/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
gem install jekyll bundler

At this point we have the neccessary tools to create a fresh blog instance.

Setup a new blog:

jekyll new fbsblog

And now test it:

$ cd fbsblog && bundle exec jekyll server
Configuration file: /home/builder/Workspaces/fbsblog/_config.yml
            Source: /home/builder/Workspaces/fbsblog
       Destination: /home/builder/Workspaces/fbsblog/_site
 Incremental build: disabled. Enable with --incremental
      Generating...
       Jekyll Feed: Generating feed for posts
                    done in 0.511 seconds.
                    Auto-regeneration may not work on some Windows versions.
                    Please see: https://github.com/Microsoft/BashOnWindows/issues/216
                    If it does not work, please upgrade Bash on Windows or run Jekyll with --no-watch.
 Auto-regeneration: enabled for '/home/builder/Workspaces/fbsblog'
    Server address: http://127.0.0.1:4000/
  Server running... press ctrl-c to stop.

and check out http://localhost:4000/

/content/images/2021/09/jekyll-ghost-01.png

Import from Ghost

Our next step is to fire up the Ghost blog which is how, up till this point, I’ve internally developed posts.

builder@DESKTOP-QADGF36:~/Workspaces/ghost-blog$ npm start

> ghost@2.13.1 start /home/builder/Workspaces/ghost-blog
> node index

[2021-09-01 16:09:48] WARN Theme's file locales/en.json not found.
[2021-09-01 16:09:48] INFO Ghost is running in development...
[2021-09-01 16:09:48] INFO Listening on: 127.0.0.1:2368
[2021-09-01 16:09:48] INFO Url configured as: http://localhost:2368/
[2021-09-01 16:09:48] INFO Ctrl+C to shut down
[2021-09-01 16:09:48] INFO Ghost boot 1.474s

If you use a SaaS service, you just need to access your Ghost backend.

/content/images/2021/09/jekyll-ghost-05.png

Go to the Labs area. From there you can “Export your content”

/content/images/2021/09/jekyll-ghost-06.png

This downloads a JSON file.

As i’m in WSL, i’ll copy that over and then add the Jekyll Ghost Importer GEM

builder@DESKTOP-QADGF36:~/Workspaces/psfntest2$ cp /mnt/c/Users/isaac/Downloads/fresh-brewed-science.ghost.2021-09-01.json ./ghost-export.json

builder@DESKTOP-QADGF36:~/Workspaces/psfntest2$ sudo gem install jekyll_ghost_importer
[sudo] password for builder:
Fetching jekyll_ghost_importer-1.1.0.gem
Fetching nokogiri-1.12.4-x86_64-linux.gem
Fetching reverse_markdown-1.4.0.gem
Successfully installed nokogiri-1.12.4-x86_64-linux
Successfully installed reverse_markdown-1.4.0
Successfully installed jekyll_ghost_importer-1.1.0
Parsing documentation for nokogiri-1.12.4-x86_64-linux
Installing ri documentation for nokogiri-1.12.4-x86_64-linux
Parsing documentation for reverse_markdown-1.4.0
Installing ri documentation for reverse_markdown-1.4.0
Parsing documentation for jekyll_ghost_importer-1.1.0
Installing ri documentation for jekyll_ghost_importer-1.1.0
Done installing documentation for nokogiri, reverse_markdown, jekyll_ghost_importer after 1 seconds
3 gems installed

Then you could import.. but i found a bit of a nuance. The older Jekyll used “feature_image” and the newer just assumes blog posts have “image”.

You can edit manually.. but my blog is rather hefty (51mb JSON) so i just used sed.

$ sed -i 's/"feature_image":/"image":/g' ghost-export.json

Then just import it.

builder@DESKTOP-QADGF36:~/Workspaces/psfntest2$ jekyll_ghost_importer ghost-export.json
Importing _drafts/themes.markdown
Importing _drafts/the-editor.markdown
Importing _posts/2019-02-02-and-so-it-begins.markdown
Importing _posts/2019-02-04-creating-a-blog.markdown
Importing _posts/2019-02-05-a-vault-tutorial.markdown
Importing _posts/2019-02-06-a-vault-tutorial-part-2.markdown
Importing _posts/2019-02-06-a-vault-tutorial-part-3.markdown
Importing _posts/2019-02-09-a-vault-tutorial-part-4.markdown
Importing _posts/2019-02-09-a-vault-tutorial-part-5.markdown
Importing _posts/2019-02-10-a-vault-tutorial-part-6.markdown
Importing _posts/2019-02-10-automating-updates-with-azure-devops.markdown
Importing _posts/2019-02-12-a-vault-tutorial-part-7.markdown
Importing _posts/2019-02-16-maven-and-nexus-tutorial-part-1.markdown
Importing _posts/2019-02-19-maven-and-nexus.markdown
Importing _posts/2019-02-21-maven-and-nexus-tutorial-part-3.markdown
Importing _posts/2019-02-16-greetings-and-salutations-on-this-fine-and-glorious-day.markdown
Importing _posts/2019-02-28-azure-devops-vsts-security-and-policies-part-1.markdown
Importing _posts/2019-02-28-azure-devops-vsts-security-and-policies-part-2.markdown
Importing _posts/2019-02-28-azure-devops-vsts-security-and-policies-part-3.markdown
Importing _posts/2019-03-02-azure-devops-vsts-security-and-policies-part-4.markdown
Importing _posts/2019-03-03-azure-devops-vsts-security-and-policies-part-5.markdown
Importing _posts/2019-03-10-automating-aks-deployments-like-a-boss-part-1.markdown
Importing _posts/2019-03-17-automating-aks-deployments-like-a-boss-part-2.markdown
Importing _posts/2019-03-25-switching.markdown
Importing _posts/2019-03-31-automating-aks-deployments-like-a-boss-part-3.markdown
Importing _posts/2019-04-07-automating-aks-deployments-like-a-boss-part-4.markdown
Importing _posts/2019-04-07-automating-aks-deployments-like-a-boss-part-5-appd.markdown
Importing _posts/2019-04-14-aks-and-service-mesh-istio-part-1.markdown
Importing _posts/2019-04-23-automating-aks-deployments-like-a-boss-part-6.markdown
Importing _posts/2019-04-30-automating-aks-deployments-like-a-boss.markdown
Importing _posts/2019-05-05-aks-and-stash-k8s-native-backups.markdown
Importing _posts/2019-05-08-the-other-clouds-linode-and-kubernetes.markdown
Importing _posts/2019-05-11-the-other-clouds-digital-ocean-and-kubernetes.markdown
Importing _posts/2019-05-17-the-other-clouds-vultr.markdown
Importing _posts/2019-05-20-rancher.markdown
Importing _posts/2019-05-26-harness.markdown
Importing _posts/2019-06-02-do-k8s-and-azdo-automation.markdown
Importing _posts/2019-06-09-do-k8s-and-azdo-automation-part-2.markdown
Importing _posts/2019-06-23-do-k8s-and-azdo-automation-part-3.markdown
Importing _posts/2019-07-01-do-k8s-and-azdo-automation-part-4.markdown
Importing _posts/2019-07-03-where-it-began-gcp-and-kubernetes.markdown
Importing _posts/2019-07-06-gcp-app-engine-standard.markdown
Importing _posts/2019-07-12-gcp-app-engine-flex.markdown
Importing _posts/2019-07-14-gcp-app-engine-flex-vs-standard-followup.markdown
Importing _posts/2019-07-26-untitled.markdown
Importing _posts/2019-08-08-automating-aks-deployments-like-a-boss-part-9-aks-aad-refinements.markdown
Importing _posts/2019-08-19-kubernetes-aspen-mesh.markdown
Importing _posts/2019-08-27-getting-started-with-k3s.markdown
Importing _posts/2019-09-08-k3s-on-win-10-wsl-2.markdown
Importing _posts/2019-09-08-on-a-personal-note.markdown
Importing _posts/2019-09-20-jfrog-enterprise-cicd-01.markdown
Importing _posts/2019-09-25-devops-stategery-iac-pipelines.markdown
Importing _posts/2019-10-06-ubuntu-multipass-better-than-docker.markdown
Importing _posts/2019-10-22-ubuntu-multipass-part-deux.markdown
Importing _posts/2019-10-28-lenovo-flex-6-14-2-in-1-a-developers-review.markdown
Importing _posts/2019-11-13-getting-started-skaffold.markdown
Importing _posts/2019-11-26-linode-lke-review.markdown
Importing _posts/2019-12-03-distributed-artifactory-and-kubernetes.markdown
Importing _posts/2019-12-22-k3os-almost-good-enough.markdown
Importing _posts/2020-01-12-getting-started-with-containerized-nexus.markdown
Importing _posts/2020-01-26-self-hosted-container-registry-in-kubernetes.markdown
Importing _posts/2020-02-09-civo-k8s-part-1.markdown
Importing _drafts/untitled-2.markdown
Importing _posts/2020-02-16-k3s-pi4-and-azure-devops.markdown
Importing _posts/2020-02-26-github-actions-with-pi4-k3s.markdown
Importing _posts/2020-03-04-k8s-and-redis-a-tale-of-layer-4-ingress.markdown
Importing _posts/2020-03-14-getting-started-with-keda.markdown
Importing _posts/2020-03-28-aks-and-ingress-again.markdown
Importing _posts/2020-04-03-aks-and-ingress-constraining-access.markdown
Importing _posts/2020-04-06-azure-arc-for-servers-preview.markdown
Importing _posts/2020-04-13-aks-and-newrelic.markdown
Importing _posts/2020-04-14-azure-devops-debt-pipelines-and-yaml.markdown
Importing _posts/2020-04-22-datadog-kubernetes-and-azdo.markdown
Importing _posts/2020-05-01-azure-devops-templates-and-integrating-teams-notifications.markdown
Importing _posts/2020-05-04-operationalization-in-day-2-operations.markdown
Importing _posts/2020-05-13-clickup-fb-workplace-and-gitlab-free-cheap-azdo-solutions-part-1.markdown
Importing _posts/2020-05-18-clickup-and-gitabl-part-2.markdown
Importing _posts/2020-05-21-azure-arc-for-kubernetes.markdown
Importing _posts/2020-05-27-whitesource-bolt-for-azdo-and-github.markdown
Importing _posts/2020-06-09-helm-3-creating-and-sharing-charts.markdown
Importing _posts/2020-06-23-codespaces.markdown
Importing _posts/2020-06-29-datadog-for-devops-metrics-dashboards-and-logs.markdown
Importing _posts/2020-07-12-diagrams-as-code-mermaid.markdown
Importing _posts/2020-07-16-notifications-email-with-logic-apps.markdown
Importing _posts/2020-07-29-k8s-and-krew-rbac-utilities.markdown
Importing _posts/2020-08-03-kubernetes-ssl-and-cert-manager.markdown
Importing _posts/2020-08-12-getting-started-with-packer-and-azdo.markdown
Importing _posts/2020-08-19-containerized-fortran-in-kubernetes.markdown
Importing _posts/2020-08-24-containerized-cobol-pascal-and-perl-based-bbs.markdown
Importing _posts/2020-09-03-kubernetes-serverless-knative.markdown
Importing _posts/2020-09-10-kubernetes-serverless-fission-io.markdown
Importing _posts/2020-09-14-kubernetes-serverless-kubeless.markdown
Importing _posts/2020-09-20-kubernetes-serverless-openfaas.markdown
Importing _posts/2020-10-03-vault-on-kubernetes-getting-started.markdown
Importing _posts/2020-10-06-vault-on-kubernetes-part-2-multiple-k8s-templates-and-external-ips.markdown
Importing _posts/2020-10-16-hashi-waypoint.markdown
Importing _posts/2020-10-23-vs-code-bridge-to-kubernetes.markdown
Importing _posts/2020-10-29-chef-habitat-getting-started.markdown
Importing _posts/2020-11-05-azure-devops-vm-deployment-environments.markdown
Importing _posts/2020-11-17-k3s-on-prem-getting-started.markdown
Importing _posts/2020-11-22-container-registries-ibm-cloud.markdown
Importing _posts/2020-11-23-alicloud-container-registry.markdown
Importing _posts/2020-12-01-observability-epsagon.markdown
Importing _posts/2020-12-09-oracle-cloud-vms-k3s-and-container-registry.markdown
Importing _drafts/logic-monitor-who-isnt-interested.markdown
Importing _posts/2020-12-17-rollbar-getting-started.markdown
Importing _posts/2020-12-31-azure-devops-agent-pools.markdown
Importing _posts/2021-01-08-azure-devops.markdown
Importing _posts/2021-01-15-azure-devops-deploying-to-iis-with-windows-vm-environments.markdown
Importing _posts/2021-01-20-terraform-cloud-getting-started.markdown
Importing _posts/2021-01-29-nomad-getting-started.markdown
Importing _posts/2021-02-06-private-ssh-git-with-azure-devops.markdown
Importing _posts/2021-02-10-chef-getting-started.markdown
Importing _posts/2021-02-18-azure-devops-secrets-in-files.markdown
Importing _posts/2021-02-26-revisiting-kubernetes-and-keda.markdown
Importing _posts/2021-03-05-terraform-and-azure-devops-a-bucket-of-yes.markdown
Importing _posts/2021-03-13-logdna-logging-and-alerting.markdown
Importing _posts/2021-03-19-k0s-mirantiss-little-k8s-distro.markdown
Importing _posts/2021-03-26-dapr-getting-started.markdown
Importing _posts/2021-04-03-dapr-part-2.markdown
Importing _posts/2021-04-09-dapr-part-3-digging-into-secrets.markdown
Importing _posts/2021-04-30-azure-dns.markdown
Importing _posts/2021-04-15-dapr-part-4-service-discovery-and-bindings.markdown
Importing _posts/2021-04-16-dapr-part-5-observability-telemetry-and-tracing.markdown
Importing _posts/2021-05-07-harbor-getting-started-with-an-on-prem-container-registry.markdown
Importing _posts/2021-05-13-crypto-mining-intro-not-for-profit.markdown
Importing _posts/2021-05-24-dapr-part-6-workflows.markdown
Importing _posts/2021-05-27-dapr-part-7-secure-our-services.markdown
Importing _posts/2021-06-03-dapr-part-8-middleware-pipelines-for-oauth.markdown
Importing _posts/2021-07-12-wiqautomations.markdown
Importing _posts/2021-06-07-dapr-k8s-events-bindings-kubewatch-and-azdo-webhook-triggers.markdown
Importing _posts/2021-06-11-kubewatch-to-akv-for-dr.markdown
Importing _posts/2021-06-24-feedback-forms-to-azdo-work-items.markdown
Importing _posts/2021-06-24-feedback-forms-to-azdo-work-items-part-2.markdown
Importing _posts/2021-07-08-ingest-email.markdown
Importing _posts/2021-07-22-jenkins-and-azdo.markdown
Importing _posts/2021-07-26-service-mesh-observability-kiali.markdown
Importing _posts/2021-08-05-azure-powershell-functions-getting-started.markdown
Importing _posts/2021-08-12-azure-powershell-functions-extending-automating-and-securing.markdown
Importing _posts/2021-08-18-kuma-mesh.markdown
Importing _posts/2021-08-24-github-blog-jekyll.markdown
141 posts imported ( 4 draft )

we can fire up Jekyll again to see it is working.

/content/images/2021/09/jekyll-ghost-07.png

We can tweak the main _config.yml file to match our blog

title: Fresh/Brewed Science
email: isaac.johnson@gmail.com
description: >- 
   Tales from Cloudy McCloudface.
baseurl: "" 
url: "https://freshbrewed.science"
twitter_username: nulubez
github_username:  idjohnson

# Build settings
markdown: kramdown
theme: minima
plugins:
  - jekyll-feed

pro-tip: If you have markdown with lots of braces (e.g. like you might find in helm charts) you can wrap sections that generate liquid errors with “raw” and “endraw”:

{% raw %}
   some markdown with lots of unescaped characters
{% endraw %}

and again, fire it up to see the results

$ bundle exec jekyll serve
Configuration file: /home/builder/Workspaces/jekyll-blog/_config.yml
            Source: /home/builder/Workspaces/jekyll-blog
       Destination: /home/builder/Workspaces/jekyll-blog/_site
 Incremental build: disabled. Enable with --incremental
      Generating...
       Jekyll Feed: Generating feed for posts
                    done in 5.825 seconds.
/home/builder/gems/gems/pathutil-0.16.2/lib/pathutil.rb:502: warning: Using the last argument as keyword parameters is deprecated
                    Auto-regeneration may not work on some Windows versions.
                    Please see: https://github.com/Microsoft/BashOnWindows/issues/216
                    If it does not work, please upgrade Bash on Windows or run Jekyll with --no-watch.
 Auto-regeneration: enabled for '/home/builder/Workspaces/jekyll-blog'
LiveReload address: http://127.0.0.1:35729
    Server address: http://127.0.0.1:4000/
  Server running... press ctrl-c to stop.
        LiveReload: Browser connected

/content/images/2021/09/jekyll-ghost-08.png

As for the base minima theme, we can actually use a few different colour patterns with it.

Change the Gemfile to pull the latest theme repo:

#gem "minima", "~> 2.0"
gem "minima", git: "https://github.com/jekyll/minima"

Then do a bundle install and update to pull that down

builder@DESKTOP-QADGF36:~/Workspaces/psfntest3$ bundle install
Following files may not be writable, so sudo is needed:
  /usr/local/bin
  /var/lib/gems/2.7.0
  /var/lib/gems/2.7.0/build_info
  /var/lib/gems/2.7.0/cache
  /var/lib/gems/2.7.0/doc
  /var/lib/gems/2.7.0/extensions
  /var/lib/gems/2.7.0/gems
  /var/lib/gems/2.7.0/specifications
Fetching https://github.com/jekyll/minima
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using public_suffix 4.0.6
Using addressable 2.8.0
Using bundler 2.1.4
Using colorator 1.1.0
Using concurrent-ruby 1.1.9
Using eventmachine 1.2.7
Using http_parser.rb 0.6.0
Using em-websocket 0.5.2
Using ffi 1.15.3
Using forwardable-extended 2.6.0
Using i18n 0.9.5
Using sass 3.7.4
Using jekyll-sass-converter 1.5.2
Using rb-fsevent 0.11.0
Using rb-inotify 0.10.1
Using listen 3.7.0
Using jekyll-watch 2.2.1
Using kramdown 1.17.0
Using liquid 4.0.3
Using mercenary 0.3.6
Using pathutil 0.16.2
Using rouge 3.26.0
Using safe_yaml 1.0.5
Using jekyll 3.8.7
Using jekyll-feed 0.15.1
Using jekyll-seo-tag 2.7.1
Using minima 2.5.1 from https://github.com/jekyll/minima (at master@3cdd14d)
Bundle complete! 6 Gemfile dependencies, 27 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
builder@DESKTOP-QADGF36:~/Workspaces/psfntest3$ bundle update
Following files may not be writable, so sudo is needed:
  /usr/local/bin
  /var/lib/gems/2.7.0
  /var/lib/gems/2.7.0/build_info
  /var/lib/gems/2.7.0/cache
  /var/lib/gems/2.7.0/doc
  /var/lib/gems/2.7.0/extensions
  /var/lib/gems/2.7.0/gems
  /var/lib/gems/2.7.0/specifications
Fetching https://github.com/jekyll/minima
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using public_suffix 4.0.6
Using addressable 2.8.0
Using bundler 2.1.4
Using colorator 1.1.0
Using concurrent-ruby 1.1.9
Using eventmachine 1.2.7
Using http_parser.rb 0.6.0
Using em-websocket 0.5.2
Using ffi 1.15.3
Using forwardable-extended 2.6.0
Using i18n 0.9.5
Using sass 3.7.4
Using jekyll-sass-converter 1.5.2
Using rb-fsevent 0.11.0
Using rb-inotify 0.10.1
Using listen 3.7.0
Using jekyll-watch 2.2.1
Using kramdown 1.17.0
Using liquid 4.0.3
Using mercenary 0.3.6
Using pathutil 0.16.2
Using rouge 3.26.0
Using safe_yaml 1.0.5
Using jekyll 3.8.7
Using jekyll-feed 0.15.1
Using jekyll-seo-tag 2.7.1
Using minima 2.5.1 from https://github.com/jekyll/minima (at master@3cdd14d)
Bundle updated!

then lastly, update the _config.yaml to add a skin parameter:

title: Fresh/Brewed Science
email: isaac.johnson@gmail.com
description: >-
   Tales from Cloudy McCloudface.
baseurl: ""
url: "https://freshbrewed.science/"
twitter_username: nulubez
github_username:  idjohnson

# Build settings
markdown: kramdown
theme: minima
plugins:
  - jekyll-feed

minima:
  skin: dark
  date_format: "%b %-d, %Y"

/content/images/2021/09/jekyll-ghost-09.png

There are three skins they added

  • dark
  • solarized
  • solarized-dark
  • classic (standard)

You can see more on it here.

Themes

This was great but then i wanted more themes.

I checked out the curated lists at

I really tried to make the Bajawa one work.. however i had issues.

In fact, anything outside the normal minima, i would get errors about the theme being fixed at Jekyll 3.* (to line up with Github pages) yet latest Jekyll is 4.*.

When i did manage to install a theme, tweaking the style got worse and worse…

/content/images/2021/09/jekyll-ghost-04.png

pro-tip: if in dancing between themes (and likely between many conflicting ruby gems), you can ‘scrub’ by clearing out the ruby cache:

root@DESKTOP-QADGF36:~# rm -rf /usr/lib/ruby/vendor_ruby/

Themes, the cheater way

The other way you can do themes is to just copy the theme repo. That is, instead of trying to create a fresh new Jekyll blog then shoehorn in the theme and styles and files. Just clone their repo (from the themes’ github) and then remove all the bits you don’t need.

This was a whole lot easier.

builder@DESKTOP-QADGF36:~/Workspaces$ git clone https://github.com/kulacino/bajawa
Cloning into 'bajawa'...
remote: Enumerating objects: 150, done.
remote: Counting objects: 100% (150/150), done.
remote: Compressing objects: 100% (101/101), done.
remote: Total 150 (delta 48), reused 135 (delta 36), pack-reused 0
Receiving objects: 100% (150/150), 1.90 MiB | 6.69 MiB/s, done.
Resolving deltas: 100% (48/48), done.
builder@DESKTOP-QADGF36:~/Workspaces$ cd bajawa/

if you do a bundle install now, you’ll see the error

builder@DESKTOP-QADGF36:~/Workspaces/bajawa$ bundle install
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Bundler could not find compatible versions for gem "bundler":
  In Gemfile:
    bundler (~> 1.16)

  Current Bundler version:
    bundler (2.1.4)
This Gemfile requires a different version of Bundler.
Perhaps you need to update Bundler by running `gem install bundler`?

Could not find gem 'bundler (~> 1.16)' in any of the relevant sources:
  the local ruby installation

Just update the gemspec to match the version referenced or whatever your current version is.

builder@DESKTOP-QADGF36:~/Workspaces/bajawa$ cat bajawa.gemspec | grep bundler
  spec.add_development_dependency "bundler", "~> 2.1.2"

Then i did the bundle install

builder@DESKTOP-QADGF36:~/Workspaces/bajawa$ bundle install
Warning: the running version of Bundler (2.1.2) is older than the version that created the lockfile (2.2.26). We suggest you to upgrade to the version that created the lockfile by running `gem install bundler:2.2.26`.
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Following files may not be writable, so sudo is needed:
  /usr/bin
  /usr/lib/ruby/gems/2.7.0
  /usr/lib/ruby/gems/2.7.0/build_info
  /usr/lib/ruby/gems/2.7.0/cache
  /usr/lib/ruby/gems/2.7.0/doc
  /usr/lib/ruby/gems/2.7.0/extensions
  /usr/lib/ruby/gems/2.7.0/gems
  /usr/lib/ruby/gems/2.7.0/specifications
Fetching rake 12.3.3


Your user account isn't allowed to install to the system RubyGems.
  You can cancel this installation and run:

      bundle install --path vendor/bundle

  to install the gems into ./vendor/bundle/, or you can enter your password
  and install the bundled gems to RubyGems using sudo.

  Password:
Installing rake 12.3.3
Fetching public_suffix 4.0.6
Installing public_suffix 4.0.6
Fetching addressable 2.8.0
Installing addressable 2.8.0
Fetching colorator 1.1.0
Installing colorator 1.1.0
Fetching eventmachine 1.2.7
Installing eventmachine 1.2.7 with native extensions
Fetching http_parser.rb 0.6.0
Installing http_parser.rb 0.6.0 with native extensions
Fetching em-websocket 0.5.2
Installing em-websocket 0.5.2
Fetching concurrent-ruby 1.1.9
Installing concurrent-ruby 1.1.9
Fetching i18n 1.8.10
Installing i18n 1.8.10
Fetching ffi 1.15.3
Installing ffi 1.15.3 with native extensions
Fetching sassc 2.4.0
Installing sassc 2.4.0 with native extensions


Fetching jekyll-sass-converter 2.1.0
Installing jekyll-sass-converter 2.1.0
Fetching rb-fsevent 0.11.0
Installing rb-fsevent 0.11.0
Fetching rb-inotify 0.10.1
Installing rb-inotify 0.10.1
Fetching listen 3.7.0
Installing listen 3.7.0
Fetching jekyll-watch 2.2.1
Installing jekyll-watch 2.2.1
Fetching rexml 3.2.5
Installing rexml 3.2.5
Fetching kramdown 2.3.1
Installing kramdown 2.3.1
Fetching kramdown-parser-gfm 1.1.0
Installing kramdown-parser-gfm 1.1.0
Fetching liquid 4.0.3
Installing liquid 4.0.3
Fetching mercenary 0.4.0
Installing mercenary 0.4.0
Fetching forwardable-extended 2.6.0
Installing forwardable-extended 2.6.0
Fetching pathutil 0.16.2
Installing pathutil 0.16.2
Fetching rouge 3.26.0
Installing rouge 3.26.0
Fetching safe_yaml 1.0.5
Installing safe_yaml 1.0.5
Fetching unicode-display_width 1.7.0
Installing unicode-display_width 1.7.0
Fetching terminal-table 2.0.0
Installing terminal-table 2.0.0
Fetching jekyll 4.2.0
Installing jekyll 4.2.0
Fetching jekyll-archives 2.2.1
Installing jekyll-archives 2.2.1
Fetching jekyll-feed 0.15.1
Installing jekyll-feed 0.15.1
Fetching jekyll-paginate 1.1.0
Installing jekyll-paginate 1.1.0
Fetching jekyll-redirect-from 0.16.0
Installing jekyll-redirect-from 0.16.0
Fetching jekyll-seo-tag 2.6.1
Installing jekyll-seo-tag 2.6.1
Fetching jekyll-sitemap 1.4.0
Installing jekyll-sitemap 1.4.0
Fetching jekyll-titles-from-headings 0.5.3
Installing jekyll-titles-from-headings 0.5.3
Using bajawa 0.1.0 from source at `.`
Using bundler 2.1.2
Bundle complete! 3 Gemfile dependencies, 37 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
builder@DESKTOP-QADGF36:~/Workspaces/bajawa$
builder@DESKTOP-QADGF36:~/Workspaces/bajawa$
builder@DESKTOP-QADGF36:~/Workspaces/bajawa$ bundle exec jekyll serve
Configuration file: /home/builder/Workspaces/bajawa/_config.yml
            Source: /home/builder/Workspaces/bajawa
       Destination: /home/builder/Workspaces/bajawa/_site
 Incremental build: disabled. Enable with --incremental
      Generating...
                    done in 0.221 seconds.
                    Auto-regeneration may not work on some Windows versions.
                    Please see: https://github.com/Microsoft/BashOnWindows/issues/216
                    If it does not work, please upgrade Bash on Windows or run Jekyll with --no-watch.
 Auto-regeneration: enabled for './'
    Server address: http://127.0.0.1:4000/
  Server running... press ctrl-c to stop.

/content/images/2021/09/jekyll-ghost-10.png

Eventually I settled on a theme I liked that had the images and content in a layout that looked good to my eyes (mere-blog-theme).

Setting up the AzDO pipeline.

After tweaking the local version to the point i visually liked it, it was time to add it to AzDO.

First I needed some common templates that i used in the Ghost blog:

templates/aws-release.yml:

jobs:
- job:
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  # now uses site artifact
  - task: DownloadBuildArtifacts@0
    inputs:
      buildType: "current"
      downloadType: "single"
      artifactName: "site"
      downloadPath: "_drop"

  - bash: |
      #!/bin/bash
      export
      set -x
      export DEBIAN_FRONTEND=noninteractive
      sudo apt-get -yq install tree
      cd ..
      pwd
      tree .
    displayName: 'Bash Script Debug'

  - task: AmazonWebServices.aws-vsts-tools.S3Upload.S3Upload@1
    displayName: 'S3 Upload: ${{ parameters.awsBucket }} all'
    inputs:
      awsCredentials: ${{ parameters.awsCreds }}
      regionName: 'us-east-1'
      bucketName: ${{ parameters.awsBucket }}
      sourceFolder: '$(System.DefaultWorkingDirectory)/_drop/site'
      filesAcl: 'public-read'

templates/notifications.yml

jobs:
- job:
  pool:
    vmImage: 'ubuntu-latest'
    
  steps:
  - bash: |
      #!/bin/bash
      set -x
      umask 0002
      
      cat > ./post.json <<'endmsg'
      {
              "@type": "MessageCard",
              "@context": "https://schema.org/extensions",
              "summary": "1 new build message",
              "themeColor": "0078D7",
              "sections": [
                      {
                              "activityImage": "https://ca.slack-edge.com/T4AQPQN8M-U4AL2JFC3-g45c8854734a-48",
                              "activityTitle": "$(Build.SourceVersionAuthor)",
                              "activitySubtitle": "$(Build.SourceVersionMessage) - $(Build.SourceBranchName) - $(Build.SourceVersion)",
                              "facts": [
                                      {
                                              "name": "Keywords:",
                                              "value": "$(System.DefinitionName)"
                                      },
                                      {
                                              "name": "Group:",
                                              "value": "$(Build.BuildNumber)"
                                      }
                              ],
                              "text": "Build $(Build.BuildNumber) has completed. Pipeline started at $(System.PipelineStartTime). ${{ parameters.awsBucket }} bucket updated and ${{ parameters.siteUrl }} is live.  The world now has just that much more joy in it.\n<br/>\n  Peace and love.",
                              "potentialAction": [
                                      {
                                              "@type": "OpenUri",
                                              "name": "View conversation"
                                      }
                              ]
                      }
              ]
      }
      endmsg
      
      curl -X POST -H "Content-Type: application/json" -d @post.json https://outlook.office.com/webhook/ccef6213-asdf-asdf-asdf-adsfasdfasdf@92a2e5a9-asdf-asdf-asdf-asdfasdfasdf/IncomingWebhook/24casdfasdfasdfasdfasdfasdfd/26a39c32-asdf-asdf-asdf-asdf
    displayName: 'Teams Posting'

Lastly, my azure-pipelines.yml was suprisingly short this time. Unlike with Ghost that i had to fire up the server then crawl the site before painfully sedding localhost paths, i just needed to render it out with bundle exec jekyll build.

azure-pipelines.yml

trigger:
- main
- develop

pool:
  vmImage: 'ubuntu-latest'

variables:
- group: AZDOSecrets

stages:
  - stage: build
    jobs:
      - job: start_n_sync
        displayName: start_n_sync
        continueOnError: false
        steps:
        - task: UseRubyVersion@0
          inputs:
            versionSpec: '>= 2.5'

        # install Jekyll
        - script: |
            gem install jekyll bundler
            bundle install --retry=3 --jobs=4
          displayName: 'bundle install'
        
        #b build the site
        - script: |
            bundle install
            bundle exec jekyll build
          displayName: 'jekyll'
        
        # create and publish the "site" archive
        - task: CopyFiles@2
          displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)'
          inputs:
            SourceFolder: '_site'
            TargetFolder: '$(Build.ArtifactStagingDirectory)'

        - task: PublishBuildArtifacts@1
          inputs:
            pathtoPublish: '$(Build.ArtifactStagingDirectory)'
            artifactName: site


  - stage: release_prod
    dependsOn: build
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
    - template: templates/aws-release.yml
      parameters:
        awsCreds: freshbrewed
        awsBucket: freshbrewed.science
        awsFinalBucket: freshbrewed.science

  - stage: build_notification_prod
    dependsOn: release_prod
    jobs:
    - template: templates/notifications.yml
      parameters:
        siteUrl: https://freshbrewed.com
        awsBucket: freshbrewed.com
        awsFinalBucket: freshbrewed.science
        
  - stage: release_test
    dependsOn: build
    condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
    - template: templates/aws-release.yml
      parameters:
        awsCreds: freshbrewed
        awsBucket: freshbrewed-test
        awsFinalBucket: freshbrewed.science

  - stage: build_notification_test
    dependsOn: release_test
    jobs:
    - template: templates/notifications.yml
      parameters:
        siteUrl: http://freshbrewed-test.s3-website-us-east-1.amazonaws.com/
        awsBucket: freshbrewed-test
        awsFinalBucket: freshbrewed.science

After i added the Github repo, the build worked great.

/content/images/2021/09/jekyll-ghost-11.png

And i could test: http://freshbrewed-test.s3-website-us-east-1.amazonaws.com/index.html

/content/images/2021/09/jekyll-ghost-12.png

Adding Search

Bing

I have a soft spot for Bing. I have no idea why. I guess i just wish it would do better. But i figured, hey, i’ll give that a try first.

You first need to create a ‘Bing Custom Search’ from the Azure Portal

/content/images/2021/09/jekyll-ghost-13.png

You can keep it in the Free tier:

/content/images/2021/09/jekyll-ghost-14.png

But you can see how many transactions per second you can service as well as other options here: https://www.microsoft.com/en-us/bing/apis/pricing

Next we go to the custom portal URL: https://www.customsearch.ai/

After logging in, we can start to create a search instance

/content/images/2021/09/jekyll-ghost-15.png

Add a URL

/content/images/2021/09/jekyll-ghost-16.png

and then we can build UI

/content/images/2021/09/jekyll-ghost-17.png

Back at the portal, we can get that search key from our Keys and Endpoints back in the portal:

/content/images/2021/09/jekyll-ghost-18.png

Once pasted, I could click Publish in the upper right.

/content/images/2021/09/jekyll-ghost-19.png

Now i can go to the Hosted UI page to get a Javascript embed or URL to use:

/content/images/2021/09/jekyll-ghost-20.png

https://ui.customsearch.ai/hosted-page?customconfig=0ce93d31-8080-497f-91da-e18fa9f33019&version=latest&market=en-US&q=

or use the script:

<script type="text/javascript" 
    id="bcs_js_snippet"
    src="https://ui.customsearch.ai/api/ux/rendering-js?customConfig=0ce93d31-8080-497f-91da-e18fa9f33019&market=en-US&version=latest&q=">
</script>

But i saw no results:

/content/images/2021/09/jekyll-ghost-26.png

Adding to Bing Manually

using Bing Webmaster Tools, I tried adding the site manually

/content/images/2021/09/jekyll-ghost-21.png

This required me to upload a veification file to my site. I did that quickly via AWS management console

/content/images/2021/09/jekyll-ghost-22.png

note: make sure to set public (AWS always defaults to private)

/content/images/2021/09/jekyll-ghost-23.png

Then I went back to the verification tool and clicked verify

/content/images/2021/09/jekyll-ghost-24.png

…according to Bing, they know it, but have yet to crawl it. so we can request indexing

/content/images/2021/09/jekyll-ghost-25.png

but as you can search bing presently, other than image search, freshbrewed.science just does not come up.

DuckDuckGo

The documentation on creating a custom search with DuckDuckGo is a little sparse. But i did find success with this this little widget builder.

/content/images/2021/09/jekyll-ghost-02.png

DuckDuckGo actually works fine and pulls in results.

Google

Google makes it rather easy. I’m sure they will charge you if you want to remove all adverts. But assuming google search is good enough and fast enough, it was just a quick two clicks through their wizard here: https://programmablesearchengine.google.com/cse/create/getcode?cx=85735040fe22c7387

/content/images/2021/09/jekyll-ghost-03.png

Summary

We took what we started last time and rolled it to production. We did this by both firing a fresh Jekyll blog and also cloning out of a theme to resolve dependency issues.

Next we showed how to convert the JSON export from Ghost to Jekyll markdown (albeit, this markdown could really be used anywhere). Lastly, we walked through adding Search three ways (Bing, Duckduckgo and Google).

Why did we do this? There is no majik in Jekyll. It’s more about using a format more condusive to my workflow. To date I’ve worked out blog posts in google docs. When they are ready i have to copy and paste into Ghost, which has to be fired up locally. Lastly, i’ve had to double enter my images because google docs actually copies a reference to a hosted image, not an image file. It’s really quite jenky and makes it a challenge to work on more than one post at a time.

Additionally, I was spurred by the need to move to a more supportable platform. Ghost was eating posts on the mainpage and short of hosting the actual app in a container or removing all my old content, i didn’t see a path to resolving this. My builds were constantly skirtting the hour limit on the free tier of Azure Pipelines and at least once i had to pay for a month of premium to unblock deployments.

The next steps will be to continue to tweak the format. However, now that my content is in proper markdown, there is a lot more freedom in fixing/changing things.

jekyll getting-started blog azdo

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes