Host multiple Ghost blog sites on the same server. Develop and deploy your theme updates with ease. Most expeditious.
I wanted to start using Ghost for my blogs. For those who don't know, Ghost is a spiffy new Open Source platform solely created to manage and publish blogs. I was drawn to Ghost because it is a Node.js application, and because it uses Markdown for publishing.
This guide shows one way to set up a Ghost blog dev environment to
- host multiple blogs sites on a single server and
- use Git to deploy your theme updates.
I am using the following dev/hosting platforms:
- I develop on a Mac
- I host production on an Ubuntu 13.04 x64 VM at DigitalOcean
Overview of the Environment:
- Mac development has Node.js and Ghost app installations
- Production Linux VM uses Nginx to route requests to the respective Ghost blog Node apps
- Production node apps run as Upstart services to stay up
- Each blog app has its own Ghost installation
- Git tracks only custom theme files or code edits
- Git deploy hooks in production allow one command deployments for each blog site
Install Ghost on Development Mac
Be sure you have Node.js installed first.
Sign up for Ghost: https://ghost.org/signup
Follow their instructions to download and extract the zip file.
If you plan to manage multiple blogs, it will help if you stay consistent with where you put your blog projects. I keep all mine right off the root of my user folder. So for this example, move all the extracted ghost files to ~/yourblog
.
Now install (build) the ghost app:
cd ~/yourblog
npm install --production
Then rename the config.example.js to config.js to avoid any confusion. (You can keep the config.example.js file for reference if you rather - Ghost will create the config.js file from it the first time you start Ghost.)
The config.js file is used to manage your specific environment settings. Ghost keeps separate settings for development and production environments here. When you start the Ghost app, you pass an argument to control which environment settings you want Ghost to use.
Edit the development section of the config.js file to assign an open port you want to use. Ghost defaults to 2368. I find it easiest to use the same port for development and production. So here I change the port to 8000 for this example:
development: {
// The url to use when providing links to the site, E.g. in RSS and email.
url: 'http://localhost:8000',
database: {
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '/content/data/ghost-dev.db')
},
debug: false
},
server: {
// Host to be passed to node's `net.Server#listen()`
host: '127.0.0.1',
// Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
port: '8000'
}
So while your at it, let's change the production section as well. Also, set the url
to match the domain of your blog:
production: {
url: 'http://yourblogdomain.com',
mail: {},
database: {
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '/content/data/ghost.db')
},
debug: false
},
server: {
// Host to be passed to node's `net.Server#listen()`
host: 'localhost',
// Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
port: '8000'
}
},
Now test the install (npm start
uses --development
by default):
cd ~/yourblog
npm start
Open a web browser to http://localhost:8000
to verify Ghost is running.
To learn about setting up and switching Ghost themes, visit the Ghost documentation.
Troubleshooting: Watch the terminal's live logging to see what might be causing any potential issues.
Initiate Your Custom Theme(s)
If you're developing your own theme, copy the default Casper theme into your own theme folder:
cd ~/yourblog/content/themes
cp -r casper yourtheme
Setup Git Version Control
Make sure you have Git installed: git-scm.com or Homebrew, etc.
Now go to your project cd ~/yourblog
and run git init
to create a local repository for the blog site.
You will have different Ghost files installed in production (due to the binaries and such created during installation), so you want to be sure to only track code changes to your theme files, or any additional Ghost core files as you may need to edit. To accomplish this, use a .gitignore
file to filter out all files except those you want to manage and deploy to production.
This can be tricky because .gitignore is optimized to ignore files, not include them.
In your ~/yourblog
directory, add a new file named .gitignore
and use something like this:
# Ignore everything first:
/*
# Then un-ignore the following:
!.gitignore
# --- Ghost core server index.js file ---
# --- Only needed if you edit this file ---
core/*
!/core/
core/server/*
!/core/server/
/core/server/*
!/core/server/index.js
# ---- Theme files -----
content/*
!/content/
content/themes/*
!/content/themes/
content/themes/yourtheme/*
!/content/themes/yourtheme/
!/content/themes/yourtheme/*
The trick is to first specifically ignore each directory that you then re-include files from.
You can test your .gitignore file by running this in your ~/yourblog directory:
git add .
git commit -m "init commit"
git ls-tree --full-tree -r HEAD
This will add all the trackable files and the last command show you the files that were added and are now tracked by Git. You should only see the files you expect. If not, edit your .gitignore file until it works as required for your needs. You don't want the core Ghost files (unless you asked for some specifically). You want to also make sure it is picking up the files you plan on developing and deploying. To "un-track" a file in Git that was added by accident, use git rm --cached <file>
.
Create and Configure VM Server
I use DigitalOcean and create a Linux VM using the latest version of Ubuntu.
ssh into your VM, then update Ubuntu:
apt-get update
apt-get upgrade
apt-get dist-upgrade
Install Git:
apt-get install git
Create your git user who will deploy updates to production. I call the user git
:
adduser git
Install Node.js:
apt-get install python-software-properties python g++ make
apt-get install software-properties-common
add-apt-repository ppa:chris-lea/node.js
apt-get update
apt-get install nodejs
Install Ghost:
You want to log in or change your user to git
for the following steps. This will help ensure that the permissions settings allow git user to deploy files to production when we get to that step. Also create a directory to hold the production blog app.
Putting the git repository in the git user home directory (
/home/git
) makes for a nice easy path when we set up the remote git repo in development.
Note: Be sure to use the latest version of ghost where I have ghost-0.4.0.zip
for this example.
su git
cd /home/git
mkdir yourblog
mkdir tmp
cd tmp
wget https://ghost.org/zip/ghost-0.4.0.zip
unzip -uo ghost-0.4.0.zip -d /home/git/yourblog
cd /home/git/yourblog
npm install --production
mv config.example.js config.js
Note: If unzip is not installed yet, log in as root su -
and install it: apt-get install unzip
If you want to test your Ghost install at this point (before getting to Nginx), remember to edit the config.js file to add the correct host and port settings for your server. For a quick test, change the port to 80, run npm start --production
from your /home/git/yourblog directory, then point a web browser to your server and verify that you see the default Ghost blog page.
Troubleshooting: As in development, hopefully the interactive logging will show you any issues.
Install Nginx
This will allow you to run multiple Ghost blog apps on the server. We will use Nginx as a reverse proxy to route user requests to the appropriate Node.js app.
Log in as root for this step:
su -
apt-get install nginx
service nginx start
Test Nginx is running now by visiting your server in a web browser. You should see the Nginx splash screen.
Configure Nginx
For each of the Ghost blog sites you want to host, create an Nginx .conf file. You can use any name for the .conf file, but matching your blog name will help avoid confusion:
cd /etc/nginx/conf.d
cat > yourblog.conf
Now paste in this file content, modified for your setup:
server {
listen 80;
server_name yourblogdomain.com www.yourblogdomain.com;
location / {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
The important settings to modify are the server_name
and proxy_pass
lines.
This file tells Nginx to listen for any requests on port 80 for yourblogdomain.com or www.yourblogdomain.com and route them to http://localhost:8000
which is the host and port we set up in our Ghost config.js file. (You will assign different unique port numbers for each subsequent Ghost app you want to host.)
Note: You can also combine all your blogs in one .conf file - just put one server {} block after another. Also, you can use *.yourdomain.com to grab any host names and route them. I did have one issue where I needed to specifically add mydomain.com *.mydomain.com
for it to route requests to mydomain.com with no host prefix.
To test the Nginx reverse proxy process:
- Restart Nginx to enable the configuration:
service nginx restart
(or usenginx -s reload
) cd ~/yourblog
andnpm start --production
to start the Ghost Node app- Point your web browser to yourblogdomain.com and verify you see your blog now
If you map more than a few domain names, the proxy may fail and you will get something like the following error in the Nginx error log:
could not build the server_names_hash, you should increase server_names_hash_bucket_size: 32
To increase the server_names_hash_bucket_size
, edit the nginx.conf file using vi:
vi /etc/nginx/nginx.conf
The following line may be commented out with a #
at the front of the line. Delete the #
to re-activate the line:
server_names_hash_bucket_size 64;
Restart Nginx. If 64 does not fix the problem at first, increase it by a powers of 2, until it works (64, 128, 256, etc.)
Troubleshooting: To better see what is going on, you can monitor the Nginx error or access logs while you try to reach the server:
tail /var/log/nginx/access.log -f
tail /var/log/nginx/error.log -f
tail
will show you the last few lines of the log, and the -f
will let you monitor any new log entries as they come in. Use ctrl-c
to exit.
Another key check: Your
proxy_pass
destination in the Nginx .conf file must match the productionserver: {host: 'XXX'}
host setting in your Ghost config.js file. If you use localhost, it must be used in both files. Or if you use your server ip address, it must be used in both places as well!
Setup Git Deployment
This process will let you easily push out updates to your blog theme or any custom Ghost server edits you might implement. We'll use the post-receive Git hook to automatically deploy changes to the Ghost app and restart the Ghost service any time we push our an update.
Log in as git
user so we create the git repository with permissions that the git user will be able to use.
su git
First, add a bare git repository on the server for your blog:
cd /home/git
mkdir yourblog.git
cd yourblog.git
git --bare init
Next, create the post-receive git hook file that will deploy updates to the /home/git/yourblog directory:
cd /home/git/yourblog.git/hooks
cat > post-receive
Then paste in this file content modified for your blog path:
#!/bin/sh
GIT_WORK_TREE=/home/git/yourblog git checkout -f
(ctrl-d
to exit the cat process)
Change permissions on the file to allow it to be executed:
chmod +x post-receive
Now go back to your local development mac and add the remote git repository:
cd ~/yourblog
git remote add yourblogrepo_label git@yourproductionserver.com:yourblog.git
You will be typing yourblogrepo_label
a lot, so use something short ;)
Test the deployment process on your mac. (Add the files to git and make an initial commit if you did not do so when you first set up the local git repo for this project: git add .
and git commit -m "init commit"
).
cd ~/yourblog
git push yourblogrepo_label master
The git push command will prompt you for the password for the git user you created on the production server (unless you're using an ssh key to connect.)
This should push your tracked and committed code changes, and the git hook on the server should automatically deploy the updated files to the ghost app. Log into the production server and verify that your code change has now indeed been deployed to /home/git/yourblog.
Troubleshooting: Be sure you can log in to your server as git: ssh git@yourproductionserver.com
. Once you log in as git user, you should start at /home/git and your yourblog.git repository should be right in that directory.
There is a limitation to our deployment process at this point. If we make any updates to the Ghost node server or its code dependancies, the node app needs to be restarted in order to use the updates. We will address this next when we set up our node app as an Upstart system service.
Running Ghost as a Service
In order to keep your Ghost blog apps running at all times, you can create an Ubuntu Upstart script and set up each node Ghost app as a system service which will do the following:
- Start the Ghost Node.js app whenever the server restarts
- Restart the app if it should fail for some reason (a crash from a bug, memory leak, etc.)
- Allow us to conveniently start/stop the app via system commands
To set up a service for the Ghost node app, you need to be root user:
su -
cd /etc/init
cat > yourblog.conf
Now copy in this file content modified for your blog specifics:
description "yourblog Ghost Node Service"
author "Your info ifya want"
start on started mountall
stop on shutdown
respawn
respawn limit 99 5
script
cd /home/git/yourblog
npm start --production >> /var/log/yourblog.log 2>&1
end script
post-start script
end script
(ctrl-d
to finish cat process)
This service script tells the server to cd to your ghost app root, start it, and log the stdout and stderr from the app to the log file you specified.
Test starting, stopping and restarting your service:
start yourblog
stop yourblog
restart yourblog
Once started, it will keep running now. Note: if you have a major error in the app that causes a crash on startup, this script will start it over and over again 99 times (or for 5 seconds, whichever comes first) before giving up.
Enable App Restart on Deployment
We can leverage the service commands to enhance our git deployment process to also restart the node app.
First we need to change the sudoers file to allow the git user to sudo as root to start and stop your blog service. We also need to stop the sudo process from prompting for a password when using sudo for this task, otherwise the automated git hook script will fail due to the prompting.
Add one line to the end of the sudoers file. I like to use vi, but you can use the editor of your choice. To use vi: export EDITOR="vi"
Now to edit the sudoers file:
visudo
It is important to add this line to the end of the file:
git ALL = (root) NOPASSWD: /sbin/stop yourblog, /sbin/start yourblog
This tells the system to allow git user to sudo as root with no password prompt, but only when running the start and stop commands. Note: yourblog
refers the service name you created- the same name of your Upstart .conf file. Also note that you need to use the full path for the start/stop commands. To double check your path, use which start
to see the full path.
Note: You need to logout and log into the server again for the sudoers file change to take effect in your ssh session.
To test, ssh as git@yourserver.com
You should be able to stop/start the service as git user as follows:
sudo /sbin/stop yourblog
sudo /sbin/start yourblog
Now update the git post-receive hook to stop and start the service:
cd /home/git/yourblog.git/hooks
cat > post-receive
Then paste this file contents in modified for your blog:
#!/bin/sh
sudo /sbin/stop yourblog
GIT_WORK_TREE=/home/git/yourblog git checkout -f
sudo /sbin/start yourblog
(ctrl-d
to finish)
Now when you deploy updates via git, the ghost service will restart. Make a small change to a ghost site file on your mac, commit the change, and deploy:
cd ~/yourblog
git add .
git commit -m "test deployment process"
git push yourblogrepo_label master
You should see the new process id # to confirm the restart of the service - something like this:
git push yourblogrepo_label master
git@yourblogdomain.coms password: xxxxx
Counting objects: 11, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 499 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: yourblogrepo_label stop/waiting
remote: yourblogrepo_label start/running, process 3469
To git@yourblogdomain.com:yourblog.git
60f6037..ece0ca8 master -> master
Seeing the process # gives us nice comfort that the service has indeed been restarted.
Conclusion
Now you can develop your Ghost theme files in ~/yourblog/content/themes/yourtheme, stage and commit the changes via Git, and deploy the updates to production with one command :)
When you want to add an additional blog to your server:
- Create a new Ghost app in development
- Create a new project directory
- Install Ghost in the directory
- Configure a unique port number in the config.js file
- Create the local git repo for the project:
git init
- Create a new Ghost app in production as above
- The app directory goes under the /home/git directory
- Create a bare git repo for production:
git --bare init
- Configure Nginx
- Create a .conf file with the new port number for this blog
- Restart Nginx:
service nginx start
- Create Upstart service and enable deployment process
- Create the Upstart .conf script
- Add a new line to the sudoers file so git user can start/stop the new service
- Create the post-receive file and grant execute permissions to it
- Add a remote git repository reference to your development project
Now you can get back to development knowing you have a lightning fast and reliable way to deploy your changes to production.
Cheers,
-Sv