Learning Ops Tooling, Part Four - Pulling and running the blog

Learning Ops Tooling, Part Four - Pulling and running the blog

Last time we looked at using Ansible to install the dependencies for the ghost blog engine. This time we are going to extend the playbook to install the actual engine.

Pulling down the ghost and installing it

You can get the lastest version of ghost as a zip file from the following location:


And we can create an Ansible task to do this and save it a temp location:

- name: Get latest copy of ghost
    url: https://ghost.org/zip/ghost-latest.zip
    dest: /tmp/ghost-latest.zip

The the next two tasks in the list take this downloaded zip of ghost and extract it to the location /var/www/ghost:

- name: Creates directory
    path: /var/www/ghost 
    state: directory

- name: unzip ghost
    remote_src: yes
    src: /tmp/ghost-latest.zip
    dest: /var/www/ghost

Then we can use the Ansible npm task to install all the dependencies in production:

- name: npm install ghost
    path: /var/www/ghost
    production: yes

And then finally ghost requires a json config file that defines a number of things such as which port it should run on, where the database is and its username/password. This we copy across from our local on to the remote server, using the same method we used to copy the mysql .my.cnf file:

- name: copy ghost config file across   
    src: 'templates/ghost-config.json'
    dest: '/var/www/ghost/config.production.json'
    owner: root
    mode: 0600

And thats it. To reiterate, thats:

  • pull down the latest version of ghost
  • extract it to /var/www/ghost/
  • Run npm install
  • Copy across the config file

The next step is to run the migrations agains the database so it is ready to run.

Runnning database migrations using Knex

Ghost uses a database migration tool they built themselves called Knex. It takes the mysql credentials defined in the ghost-config.json file and migrates the database to the latest state. But the first thing we need to do is pull it down:

- name: get knex migrator
    name: knex-migrator
    global: yes

Note the global flag for global installation meaning Knex can be run from anywhere.

Then because there is no Ansible module for running Knex we have to run a raw script directly to invoke it:

- name: run scripts
  shell: 'NODE_ENV=production knex-migrator init'
    chdir: /var/www/ghost

Here we are saying to run knex-migrator init In production mode. But we are also telling Ansible to change directories to /var/www/ghost before doing so. This is the location of the config file we copied across.

Setting up Ghost on Nginx

Now that ghost is installed we need to tell nginx to redirect any requests to port 80 on to our ghost application which will be running on port 2368.

The way Nginx works is by allowing you do define a set of configuration files for all available files in /etc/nginx/sites-available/ and then you can sym link these files to /etc/nginx/sites-enabled/ when you want to put them live.

When ever you put a site live you'll also need to restart Nginx so that the new configuration takes effect.

the config file for our ghost site is pretty simple:

server {
    listen 80;
    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;

Here we are saying to listen to port 80 and pass that request on to

And once again we can just copy this file across and symlink it using Ansible:

- name: copy ghost config file across
    src: 'templates/nginx.conf'
    dest: '/etc/nginx/sites-available/ghost'
    owner: root
    mode: 0600

- name: enable ghost site
    src: '/etc/nginx/sites-available/ghost'
    dest: '/etc/nginx/sites-enabled/ghost'
    state: link

- name: restart nginx
    name: nginx
    state: restarted

Note, that when the second task runs its "state" is set to "link" so that ansible knows to just do a symlink. The last task makes use of the service module to restart nginx.

Set the Site Running with Systemd

At this point we could just could just navigate to /var/www/ghost and run npm start --production and the site would start up. But the problem with this is that when we restart the server we want the site to come back up without running it manually. To accomplish this we can use a tool called systemd that will manage a service for us that will take care of the restarting of the ghost site.

Systemd comes as part of the linux distro but we still need to create the service. To do this we can copy a systemd config file from our local machine on the server, symlink it to the systemd service directory and then set it running.

The config file is pretty self explanatory:

Description=Ghost systemd service for blog: localhost

ExecStart=/usr/bin/npm start --production


The "Unit" section is descriptive. The "Service" section says that it wants to exec npm start --production in the /var/www/ghost folder and it always wants to restsart it.

And to upload it we need the following four Ansible tasks:

- name: Creates directory
    path: /usr/lib/systemd/system
    state: directory

- name: copy systemd unit file across
    src: 'templates/ghost_localhost.service'
    dest: '/usr/lib/systemd/system/ghost_localhost.service'
    owner: root
    mode: 0600
- name: link ghost unit file
    src: '/usr/lib/systemd/system/ghost_localhost.service'
    dest: /lib/systemd/system/ghost_localhost.service
    state: link

- name: start ghost localhost
    name: ghost_localhost
    state: restarted
    enabled: yes

The last task here has something important that caught me out. When starting the ghost service, if you don't specify enabled as true the service won't automatically start after restarts of the server.

And there you have it. That's everything you need to define in an ansible playbook to pull down and run ghost on a server. It took a long time to go through and work this out and I'm willing to admit some of is probably wrong, but it works.

The next thing I'm going to look at is using (Packer)[https://www.packer.io] to create a VM image so I can deploy this to "the cloud".

Things I need to fix going forwards

This playbook isn't perfect and whilst researching Ansible I know there are some improvements I can make in the future. So I've made myself a short list of things to go back to:

  • Split the playbook up into (roles)[http://docs.ansible.com/ansible/latest/playbooks_reuse_roles.html] to make it more managable.
  • Introduce variables so that I'm not storing passwords in plain text.
  • I need to look into user management and make sure I'm running things as the correct user.
  • Use variables in the template files as these too contain passwords