Running Ghost Blog On SmartOS

SmartOS Nov 11, 2019

Ghost runs great in a SmartOS / Joyent Smart Machine.  For the most part, the standard Ghost installation procedure works with a few minor tweaks.  This post document the process we took to get a production ready Ghost deployment on a Smart Machine.

The install guide I recommend is the official Ghost install guide for Ubuntu:
https://docs.ghost.org/install/ubuntu/

I'll document the deviations I took from the official install guide.

First deviation - is the "Update packages".  Smart Machines uses "pkgin" as it's package manager so there is no "apt".  Instead, run:

pkgin update.

Next is the "Install NGINX" section.  We're using Traefik as a front end to Ghost as a backend so we did not install NGINX.  I'll be writing a Ghost Front End solutions guide TBA.  You can skip this section, ghost will be available on it's default http port 2368.

In the "Install MySQL" section, we'll use pkgin again:

pkgin in mysql-server.

In the "Install Node.js" section, we'll use the pkgin equivalent

pkgin in nodejs.

Joyent's team keeps their Node.js packages very up-to-date.  At the time of this writing, the Node.js package version is 10.14.2.  Ghost recommends "Node v10 Dubnium LTS" so we're good here.

We'll need one extra step here which is to install "npm":

pkgin in npm.

Once this has been done, you will be able to run:

sudo npm install ghost-cli@latest -g.

Choose to ignore the "unsupported operating system" warning.

Once Ghost is installed, you will be able to to run: ghost start

Debug Information:OS: sunos, v5.11Node Version: v10.14.2Ghost-CLI Version: 1.9.9Environment: productionCommand: 'ghost start'
ghost ls works as well.

However, ghost stop does not work in the smart machine.  This is because it uses the npm module fkill.  For this, I give you the one-liner hack for ghost stop:

kill -6 lsof -p|grep 2368|awk '{print $1}'

I know, not pretty.  But it works.  For pretty and automatic restart functionality, you will need to install a SMF (Service Manifest) service. Here is one I wrote and it works great.

<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">

<service_bundle type='manifest' name='ghost'>

<service name='application/ghost' type='service' version='1.7.0'>

     <dependency
          name='network'
          grouping='require_all'
          restart_on='none'
          type='service'>
          <service_fmri value='svc:/milestone/network:default' />
     </dependency>
     <dependency
          name='filesystem-local'
          grouping='require_all'
          restart_on='none'
          type='service'>
     <service_fmri value='svc:/system/filesystem/local:default' />
     </dependency>



     <exec_method
          type='method'
          name='start'
          exec='/home/blog/start_ghost.sh'
          timeout_seconds='300' />

     <exec_method type="method" name="stop" exec=":kill -INT" timeout_seconds="10" />

     <exec_method type="method" name="refresh" exec=":kill -HUP" timeout_seconds="10" />

       <property_group name="ghost" type="application">
            <propval name="home" type="astring" value="/home/blog/ghost/" override="true"/>
        </property_group>

        <instance name='default' enabled='true'>
          <method_context>
            <method_credential user="blog" group="blog"  privileges='basic,net_privaddr,PRIV_NET_ACCESS' />
            <method_environment>
                <envvar name="TMP" value="/tmp/" />
                <envvar name='PATH' value='/usr/local/sbin:/usr/local/bin:/opt/local/sbin:/opt/local/bin:/usr/sbin:/usr/bin:/sbin'/>
            </method_environment>
          </method_context>
        </instance>


     <stability value='Evolving' /> 

     <template> 
          <common_name> 
               <loctext xml:lang='C'> 
               Ghost Blog
               </loctext> 
          </common_name> 
          <documentation> 
          </documentation> 
     </template> 

</service> 

</service_bundle>

This assumes the username used to install Ghost is "blog" and the directory that ghost was installed in is "ghost".  In this case, the home directory is /home/blog/ghost.  You will need to adjust as necessary.

You will also need a start_ghost.sh bash helper script to start ghost.

#!/bin/bash
cd /home/blog/ghost
/opt/local/bin/ghost start

This ensures that ghost is run from the proper directory.  I'm sure there is a more elegant way to do this in SMF, so if you are a SMF jock, please chime in a comment below!

Save the above xml file as "ghost.xml" and run svccfg import ghost.xml.

Now ghost will automatically start at boot and you can control the ghost process using:

# svcs -vx ghost
svc:/application/ghost:default (Ghost Blog)
 State: online since March 10, 2018 at 09:50:57 AM PDT
 See: /var/svc/log/application-ghost:default.log
# svcadm enable ghost
# svcadm disable ghost

base-64-lts     18.4.0    smartos  zone-dataset  2019-01-21

Of course, you can use a LX Brand machine Ubuntu 16.04 machine and you could just follow the official install guide exactly and it would work perfectly.

ubuntu-16.04    20170403  linux    lx-dataset    2017-04-03

The entire Ghost deployment on a Smart Machine is less than 300MB.

That's all for today.  Stay tuned for more DevOps Usefulness here!