Getting off Bluehost Wordpress

I've had a Bluehost account for years.....years!  Maybe since 2011-2012 or so, if I had to guess.  It was hard to pass up: the teaser price was insanely cheap, and it was unlimited bandwidth and tens of GB more storage than needed, along with mail service.  I paid to register the klempay.com domain through them as well.  I bumped up from the most basic shared account to the next rung up, the Shared Plus.  It looks like they've lowered the price of Plus even more..but I'm grandfathered into my higher rate (not the kind of grandfathering that's desirable!).  My last renewal was a yearly payment at $13.99/month.  It looks like the full rate is now $10.99/month ($5.95/month during the teaser year).

Setting up a Wordpress site was super easy, as the cPanel dashboard that Bluehost provides to manage the account includes a slew of ready to go one click installs.  I put a few different blogs on there, with most of the activity on travel.klempay.com.  This was the site used to keep the family back home abreast of our honeymoon travel, and then later other trips, like a Maui babymoon.

This worked well enough, but had a few drawbacks:

  • Setting everything up via a one click install doesn't build any knowledge of what's happening under the hood...not well suited to tinkering!  It's totally possible to ssh into the Bluehost account and muck around with the install, but everything is really geared towards cPanel usage.  The lack of knowledge is problematic when something barfs when doing some updating of the system in cPanel or the Wordpress admin tabs; without knowing much about the innards, it's kind of a brick wall.  In this case, it was a Wordpress plugin requiring a newer version of PHP than the old one currently configured...but Bluehost bombing when picking a newer PHP version to use for the site.
  • I didn't use the included email account at all; all of the @klempay.com email addresses just forwarded to our gmail.com accounts.
  • It is slow.  Super slow.  It's the other side of the cut rate shared server coin! The underpowered instance could be somewhat dealt with a few ways (like use of a CDN for for the blog content), but all of the dynamically generated content was slow to come.  The meager horsepower of the instance was such that even using cPanel when managing the account was painfully sluggish.

I thought I'd get away from Bluehost and switch over to a regular VPS from DigitalOcean/Vultr/[lots of other competitors]; those tend to start at $5/month, and going up to $10/month would still be a bit cheaper than Bluehost...yet surely more horsepower.  With a vanilla Ubuntu install, I could set up things as needed and not rely on the crutch of cPanel.  The ability to use Docker for most anything I'd run allowed me to get some degree of prepackaged software (akin to the role that cPanel once provided me), but with more control and in a clean and self contained way.  

This is all of the stuff in the current cPanel. SO. MUCH. STUFF.

My first thought was to just stand up a containerized Wordpress on the DO VPS tha I acquired (I had a Vultr one for a while as well; switching VPS providers is much easier than getting off Bluehost).  This is where the abysmal horsepower of the Bluehost shared server turned out to be a major issue: the feature to export existing site to a zip file runs and times out.  It's unable to zip up the content (I was doing this for travel.klempay.com, the site most in need of preservation) and images in the default timeout period (30 seconds).  There are zillions of third party Wordpress plugins to do site backups, many of which are also used for site transfers (backup a site and restore it to another site).  Many of them are paid, but I tried a few free ones.  Nothing fit the bill.

I don't use a bunch of plugin-provided Wordpress functionality; I just needed a text-heavy site with some ability to have some inline pictures, and perhaps a few small galleries.  A static site generator would probably do, although I think writing everything in Markdown might be a bit annoying.  Ghost seems to be a happy medium, and so I went that route.  Ghost has a Wordpress plugin to export a site for import to Ghost.  The exporter ran fine, but it doesn't actually bundle any of the media; it just included all of the media URLs (pointing to where they lived on the Bluehost site).  I found someone else with this same problem, and a blog post detailing the approach: Migrate from Wordpress to Ghost.

The approach here involved exporting my old site using the Ghost exporter, and then using regex-based find/replace in VS Code to transform all of the image paths in the export file to the structure used by Ghost.  In his post, he used a Wordpress plugin called Export Media Library to get all of his prior site's linked media exported to transfer to the server hosting Ghost.  I couldn't do this: this was one of the plugins requiring a newer PHP version than my Bluehost site was running, and Bluehost's functionality to pick a newer PHP version bombed.  I just went the brue force method: ssh into the Bluehost machine and zip -r on all of the media for the blog.  I scp'ed that over to my DO VPS, and was able to extract it to the Ghost site. With that done, and the paths fixed up via the editing detailed in the blog post...my import of the export file into Ghost worked fine, giving me the old site now running here on this Ghost site.  I had to iterate on it a few times, as (not sure if the current Ghost approach differs a bit from that late 2018 blog post or what) a few things were not valid links, particularly featured images for a given post.  After figuring out what the correct paths should be (as the images did exist, just that the fixed up export file was fixed up incorrectly), that was resolved as well.

With CDN duties mostly taken care of on the old Wordpress site via the already-present-and-set-up Jetpack plugin (with the Photo CDN, now called Image CDN), I needed something for this Ghost site.  No problem: I just enabled Cloudflare (who I switched to as my registrate and DNS services a few years ago) to proxy this domain.

The dockerized Ghost setup I'm using is fired up from a docker compose file that also starts up nginx-proxy for proxying and serving duties as well as letsencrypt-ngnix-proxy-companion, which handles the automatic renewal and installation of a Let's Encrypt cert for the site.  The cloudflare-ddns image keeps the DNS entry up to date, and watchtower restarts all of these containers when newer versions are available to be pulled.  I'm using this:

version: '3.1'
services:
  nginx-proxy:
    image: jwilder/nginx-proxy:alpine
    container_name: ghost_nginx-proxy
    restart: always
    ports:
      - "0.0.0.0:80:80"
      - "0.0.0.0:443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./nginx/dhparam:/etc/nginx/dhparam:rw
      - ./nginx/certs:/etc/nginx/certs:ro
      - ./nginx/vhost.d:/etc/nginx/vhost.d
      - ./nginx/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf
      - ./nginx/nginx.html:/usr/share/nginx/html
    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"
  nginx-proxy-companon:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: ghost_nginx-proxy-companion
    restart: always
    depends_on:
      - "nginx-proxy"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./nginx/certs:/etc/nginx/certs:rw
      - ./nginx/vhost.d:/etc/nginx/vhost.d
      - ./nginx/nginx.html:/usr/share/nginx/html
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy
  ghost:
    image: ghost:latest
    container_name: ghost
    restart: always
    depends_on:
      - "nginx-proxy"
    ports:
      - "127.0.0.1:8100:2368"
    volumes:
      - ./ramblings/data/ghost:/var/lib/ghost/content
      - ./ramblings/data/ghost/ramblingsconfig.production.json:/var/lib/ghost/config.production.json
    environment:
      - url=https://ramblings.klempay.com
      - VIRTUAL_HOST=ramblings.klempay.com
      - LETSENCRYPT_HOST=ramblings.klempay.com
      - LETSENCRYPT_EMAIL=corbett.klempay@gmail.com

The last step was to protect the installation by setting up some automated backup.  I pay an extra $2/month at DO to have it do periodic backups, but I made use of some guides such as this one to set up automated backup of my DO server to my Synology 218+ via restic.  As mentioned in some posts, there are two tricks in getting the restic repository (which serves as the backup target) to initialize:

  1. Realizing that Synology's sftp server surfaces the user's home directory as / (rather than its true path, which is /volume1/homes/username).  This is mentioned in the aforementioned post.
  2. Fixing up permissions on some files used by ssh on the Synology side; without fixing those permission issues, the ssh (and thus sftp) will call out (you can see this when running in verbose mode) that key-based login is not permitted due to improper permissions on the relevant files on the target's side.  This permission goes unmentioned in the above restic-to-Synology setup blog post, but my search turned up other pages that talked about this permission issue and how to resolve it.  The issue would need to be addressed to do be able to log in via keys instead of passwords; it is not a restic-specific issue (it's just that restic obviously can't have a person needing to type a login password every time cron kicks it off).  One such post detailing this is Using SSH key authentification on a Synology NAS for remote rsync backups.  This person is dealing with the issue to use it for rsync, but we need to solve the same problem to use restic.

The last thing I'll get around to at some point is publishing a bunch of full photo galleries (the travel blog just had some highlights posted in each post; the full honeymoon set is hundreds-at-least, for example) to be served up off of the Synology itself.  I had formerly had them on Flickr, but with the discontinuation of their prior free tier, that is all dead now.  There appears to be a Lightroom export plugin to publish Lightroom galleries to Synology devices, so that will probably be the easiest route.

With all of this done, I can finally disable the yearly auto-renew on my Bluehost account...