TL:DR - Goodbye Ghost CMS, it was nice knowing you, hello there Hugo SSG! The new site is finally up, and sooooo much faster –> https://alexwitherspoon.com/
Why the change?
After a great deal of coffee, and thinking - I came down to a few simple ideas.
I wanted…
- all my site content to be in one place, and be less impacted when 3rd party sites change/go down.
- cheaper and less complicated to operate, a static site of some kind was ideal.
- a landing page that was more performant, but the ability to host a rich blog and other content.
- a better way to manage photos in a responsive way, since many of my posts are media rich.
- content to be written as much in markdown native as possible.
Oh Ghost, I used to really really like you :D
Ghost as a CMS showed up on the scene as an evolution from Wordpress - it paired down functionality, was minimalistic yet performant, and solved many common challenges the blogger would have. Then… I suspect they got very popular, and the simple elegance dissapeared with new GUI tools that ruined the beautifully simple and straight forward markdown editor.
So I picked Hugo, like 6 billion other people before me
There are definitely some ways of using Hugo that would not meet my goals, like relying too much on 3rd party services for content. The majority of the content is fully served locally from content in the source repo, and all web optimization is done during the build, which I know I can confidently do anywhere I can run docker and a few commands if I needed to.
If you’d like to try what I did, there are some great simple how-to docs, and help out there, I’d suggest starting here: https://gohugo.io/hosting-and-deployment/hosting-on-netlify/
Shortcodes - hack in some cool factor
Despite all I tried, Hugo, and well all platforms are leaning on a series of more complex hacks APIs for invoking more automated and integrated ways to embed, and interact with other web content. I did manage to keep my Hugo Shortcodes minimal, and largely stock. Here are all the shortcodes I have enabled as of this writing:
The New Workflow
Everything starts with content hosted in a private git repo on GitHub.com
I store content in folders like this:
I create a Pull Request for each post/page and add all images, markup, and any other content into a folder contained in the PR. This keeps all source content organized, and in change control. As I edit, each change within the pull request is generated in aprivate preview so I can check that I like it. - Then when I am happy, I merge the pull request to master, and the public visible website is rebuilt and deployed.
I can do this from either the command line, or from Github’s GUI, both methods work pretty good. Eventually when it’s supported, I’ll probably give a look at Netlify CMS
Currently I am using Netlify.com as the build and hosting environment. They get a webhook whenever a change is made in Github, and they perform a lot of great checks right out of the box. I also use the Hugo Pipeline system to take my orginal image content, and compress it for use on the web. I love this because it makes viewing my website possible on slower connections, but also leaves my original source content organized in place in my repo - the web optimized images are throw away, and can be cheaply re-generated to whatever size is needed, now or down the road.
Google Domains is the DNS registrar I use. and points directly at Netlify, and that’s it. It just works, and when the content is compiled, it’s effectively on cruise control until it’s time to add more content.
The Coffee is ready
And that’s it, pour some coffee and stare in amazement, and boring dependable static HTML and CSS conjured like magic!
Example logs from website build
5:30:28 PM: Build ready to start
5:30:30 PM: build-image version: 42bca793ccd33055023c56c4ca8510463a56d317
5:30:30 PM: buildbot version: 15854c92f1b74f9c77ad951b5536193c76eff25e
5:30:30 PM: Fetching cached dependencies
5:30:30 PM: Starting to download cache of 247.5MB
5:30:31 PM: Finished downloading cache in 1.199503271s
5:30:31 PM: Starting to extract cache
5:30:34 PM: Finished extracting cache in 2.791739189s
5:30:34 PM: Finished fetching cache in 4.089049865s
5:30:34 PM: Starting to prepare the repo for build
5:30:34 PM: Preparing Git Reference pull/7/head
5:30:35 PM: Starting build script
5:30:35 PM: Installing dependencies
5:30:36 PM: Started restoring cached node version
5:30:38 PM: Finished restoring cached node version
5:30:39 PM: v8.12.0 is already installed.
5:30:39 PM: Now using node v8.12.0 (npm v6.4.1)
5:30:40 PM: Attempting ruby version 2.3.6, read from environment
5:30:41 PM: Using ruby version 2.3.6
5:30:41 PM: Using PHP version 5.6
5:30:41 PM: Installing Hugo 0.49
5:30:41 PM: Started restoring cached go cache
5:30:41 PM: Finished restoring cached go cache
5:30:41 PM: unset GOOS;
5:30:41 PM: unset GOARCH;
5:30:41 PM: export GOROOT='/opt/buildhome/.gimme/versions/go1.10.linux.amd64';
5:30:41 PM: export PATH="/opt/buildhome/.gimme/versions/go1.10.linux.amd64/bin:${PATH}";
5:30:41 PM: go version >&2;
5:30:41 PM: export GIMME_ENV='/opt/buildhome/.gimme/env/go1.10.linux.amd64.env';
5:30:41 PM: go version go1.10 linux/amd64
5:30:41 PM: Installing missing commands
5:30:41 PM: Verify run directory
5:30:41 PM: Executing user command: hugo -v
5:30:41 PM: WARN 2018/10/29 00:30:41 No translation bundle found for default language "en"
5:30:41 PM: WARN 2018/10/29 00:30:41 Translation func for language en not found, use default.
5:30:41 PM: WARN 2018/10/29 00:30:41 i18n not initialized, check that you have language file (in i18n) that matches the site language or the default language.
5:30:41 PM: INFO 2018/10/29 00:30:41 Using config file: /opt/build/repo/config.toml
5:30:41 PM: Building sites … INFO 2018/10/29 00:30:41 syncing static files to /opt/build/repo/public/
5:30:41 PM: INFO 2018/10/29 00:30:41 found taxonomies: map[string]string{"tag":"tags", "category":"categories"}
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/acquisition/page/1/index.html" translated to "tags/acquisition/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/agile/page/1/index.html" translated to "tags/agile/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/blog/page/1/index.html" translated to "tags/blog/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/brakes/page/1/index.html" translated to "tags/brakes/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/bridge-building/page/1/index.html" translated to "tags/bridge-building/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/build-log/page/1/index.html" translated to "tags/build-log/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/cabin/page/1/index.html" translated to "tags/cabin/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/categories/page/1/index.html" translated to "categories/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/cms/page/1/index.html" translated to "tags/cms/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/college/page/1/index.html" translated to "tags/college/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/debian/page/1/index.html" translated to "tags/debian/page/1/index.html"
5:31:20 PM: INFO 2018/10/29 00:31:20 Alias "/tags/drive/page/1/index.html" translated to "tags/drive/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/ebike/page/1/index.html" translated to "tags/ebike/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/electric-bike/page/1/index.html" translated to "tags/electric-bike/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/focus-rs/page/1/index.html" translated to "tags/focus-rs/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/ford/page/1/index.html" translated to "tags/ford/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/ford-rs/page/1/index.html" translated to "tags/ford-rs/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/github/page/1/index.html" translated to "tags/github/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/hpde/page/1/index.html" translated to "tags/hpde/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/japan/page/1/index.html" translated to "tags/japan/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/leadership/page/1/index.html" translated to "tags/leadership/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/lego/page/1/index.html" translated to "tags/lego/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/logs/page/1/index.html" translated to "logs/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/logs/page/1/index.html" translated to "tags/logs/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/mindstorms/page/1/index.html" translated to "tags/mindstorms/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/mk3/page/1/index.html" translated to "tags/mk3/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/networking/page/1/index.html" translated to "tags/networking/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/oregon/page/1/index.html" translated to "tags/oregon/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/org-design/page/1/index.html" translated to "tags/org-design/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/organizational-models/page/1/index.html" translated to "tags/organizational-models/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/portland-state-university/page/1/index.html" translated to "tags/portland-state-university/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/projects/page/1/index.html" translated to "projects/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/projects/page/1/index.html" translated to "tags/projects/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/psu/page/1/index.html" translated to "tags/psu/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/publications/page/1/index.html" translated to "publications/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/remote-work/page/1/index.html" translated to "tags/remote-work/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/robotics/page/1/index.html" translated to "tags/robotics/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/rs/page/1/index.html" translated to "tags/rs/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/scca/page/1/index.html" translated to "tags/scca/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/site-reliability-engineering/page/1/index.html" translated to "tags/site-reliability-engineering/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/sre/page/1/index.html" translated to "tags/sre/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/taco/page/1/index.html" translated to "tags/taco/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/page/1/index.html" translated to "tags/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/toyota/page/1/index.html" translated to "tags/toyota/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/travel/page/1/index.html" translated to "tags/travel/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/vacation/page/1/index.html" translated to "tags/vacation/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/vpn/page/1/index.html" translated to "tags/vpn/page/1/index.html"
5:31:21 PM: INFO 2018/10/29 00:31:20 Alias "/tags/vyperpvn/page/1/index.html" translated to "tags/vyperpvn/page/1/index.html"
5:31:24 PM:
5:31:24 PM: | EN
5:31:24 PM: +------------------+-----+
5:31:24 PM: Pages | 126
5:31:24 PM: Paginator pages | 6
5:31:24 PM: Non-page files | 62
5:31:24 PM: Static files | 17
5:31:24 PM: Processed images | 168
5:31:24 PM: Aliases | 48
5:31:24 PM: Sitemaps | 1
5:31:24 PM: Cleaned | 0
5:31:24 PM: Total in 42214 ms
5:31:24 PM: Caching artifacts
5:31:24 PM: Started saving pip cache
5:31:24 PM: Finished saving pip cache
5:31:24 PM: Started saving emacs cask dependencies
5:31:24 PM: Finished saving emacs cask dependencies
5:31:24 PM: Started saving maven dependencies
5:31:24 PM: Finished saving maven dependencies
5:31:24 PM: Started saving boot dependencies
5:31:24 PM: Finished saving boot dependencies
5:31:24 PM: Started saving go dependencies
5:31:24 PM: Finished saving go dependencies
5:31:24 PM: Build script success
5:31:24 PM: Starting to deploy site from 'public'
5:31:26 PM: Starting post processing
5:31:29 PM: Post processing done
5:31:29 PM: Site is live
5:31:45 PM: Finished processing build request in 1m15.10291209s
Finally slaying the docker container
It gave a fight right to the end, finally I just killed the container after the container refused to stop, and then started purging data, almost 12GB in all when done between docker and the content for my site.
root@core:~# docker stop log.alexwitherspoon.com
log.alexwitherspoon.com
root@core:~# docker rm log.alexwitherspoon.com
Error response from daemon: You cannot remove a running container d3faeb5b16882cc7b1d094f0516ccf7421c32ea26f2e6ce0e2b15f3bb584668b. Stop the container before attempting removal or force remove
root@core:~# docker kill log.alexwitherspoon.com
log.alexwitherspoon.com
root@core:~# docker rm log.alexwitherspoon.com
log.alexwitherspoon.com
root@core:~# docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all build cache
Are you sure you want to continue? [y/N] y
Deleted Images:
untagged: ghost@sha256:24587eef8f18487c0c5d537723b9b40267ea130a40e48778f2323fbe110eb13a
untagged: ghost@sha256:2cb1f35d9d6508afa57876e504e8819701b30a7eb8d803ca3f72ebf043ecd91b
untagged: ghost@sha256:eaf5ef5970e6a9b7c4f7279e4135233f49c5f5f486fd982abe0cd73dc440c433
deleted: sha256:9134c074ca5c5bb9311ea7eab6c1474516f5ed06aacea8a7ba7b171b5263e842
deleted: sha256:fb917573ca0472724b0e1984d7eeb7cbd03873777d2fca5474d3ccc55f8d6c76
Total reclaimed space: 7.248GB
root@core:~# rm -R /opt/log.alexwitherspoon.com/