Develop & Deploy Ghost with PM2
Andrew Lechowicz
Setting Up
If you already have a git repository setup and a package.json
you can skip to Dependencies.
Project
Create a git repository for this project:
git init <project name>
cd <project name>
Then create your package.json the easy way:
npm init
Dependencies
Install global dependencies like Grunt and PM2 and package dependencies like Ghost and Shrinkwrap:
npm install -g grunt pm2
npm install ghost shrinkwrap --save
Next, create the project’s index file at the root of the project. This is the entry point PM2/Node.js will use to start your app This can be whatever you like and I’ll use index.js
for the rest of the post.
Add the following to index.js
and save it.
// Start Ghost
var ghost = require('ghost');
ghost().then(function (ghostServer) {
ghostServer.start();
});
With that you can now run Ghost:
pm2 start index.js --name "<process name>"
Upon visiting http://localhost:2368 you should see the standard blog with the standard Casper theme and the default “Welcome to Ghost” blog post.
Visit http://localhost:2368/ghost/ to setup your development blog. Be sure to remember your username and password for the future. It doesn’t need to be a real email address or even a secure password at thispoint because you’ll only be accessing the backend from your local computer. If the page hangs quite some time, just refresh and you should be prompted to log in.
That’s it. You’re all setup.
Development
If your Ghost server is still running, stop it with:
pm2 stop <app name>
Up until now, Ghost has been using the content
directory in node_modules/ghost/
to store application data, themes, images, and Ghost apps. We’ll need to define a new place to store all of these things so npm doesn’t overwrite on the next npm install
or npm update
command.
Create a config.js
for Ghost
Before we can adjust and configuration values to tell Ghost where we expect the content
directory to be, as well as a host of other things, such as which port to listen on and how to send emails, we need a configuration file and we need to tell Ghost where it is.
Grab the example config from Github or copy it from /node_modules/ghost/config.example.js
. Run cp node_modules/ghost/config.example.js config.js
to copy the one npm grabbed for you. Edit the new config.js
to remove any testing related configs for the Ghost core. This will prevent you from getting confused later on. You should be left with a “production” object and a “development” object in the “config” object in config.js
.
Ghost expects the file and folder stucture from before to be in place wherever we tell it the content
directory is. Run cp -R node_modules/ghost/content/ content/
to copy the entire content directory from node_modules
to the root of the project.
Pass Configuration into Ghost
In index.js
we’re going to tell ghost to use the config.js
file as it’s configuration file. We can pass the ghost()
call an object to tell it which configuration file to load:
var path = require('path'),
ghost = require('ghost');
ghost({
config: path.join(__dirname, 'config.js')
}).then(function (ghostServer) {
ghostServer.start();
});
Don’t forget to create and instance of the Node.js path
object on line 1.
You can now create themes and apps as you normally would with Ghost. Just be sure to edit files in the content/
directory at the root of the project, Not the one in node_modules/ghost/
.
Before You Commit
Before you commit changes it’s probably a good idea to stop the PM2 process and add content/data/*.db
to your .gitignore
since it’s the folder SQLite uses to store databases and they have the .db
extention.
If you already commited the SQLite database you can still edit your .gitignore
and then run git m -r --cached content/data/*db
to remove it from git’s index. Finally, commit the .gitignore
and the “deleted” files from git’s index as usual.
Setting up PM2 for Deployment
Since version 0.9.0, PM2 has been able to deploy code for you. To see the commands availble for deployment run pm2 deploy help
.
Create an ecosystem.json
To get started setting up PM2 for deployment let’s have PM2 do some of the work for us. While in your project root, run pm2 ecosystem
.
For me, this generated an ecosystem.json5
file in the project root. JSON5 is an extention to JSON that adds some additional features to JSON like comments and unquoted keys. I’m not a fan of JSON5 for a couple of reasons, this little warning doesn’t help:
JSON5 is not an official successor to JSON, and JSON5 content may not work with existing JSON parsers. For this reason, JSON5 files use a new .json5 extension. - json5.org
Later, when I tried to use thie file, PM2 was still looking for ecosystem.json
not ecosystem.json5
so I decided to rename it to the former. I’ll suggest you do the same until I know differently.
Once the file has been generated, you’ll want to edit it with your projects configuration. Sine we rename the extention, we should make sure it’s valid JSON first.
Here’s what my ecosystem.json
looks like:
{
"apps": [{
"name": "<project name>",
"script": "index.js"
}],
"deploy": {
"production" : {
"user": "<user to run as on production>",
"host": "<production IP or domain>",
"ref": "origin/<production branch>",
"repo": "git@github.com:<Github username>/<project name>.git",
"path": "/var/www/<production directory>",
"post-deploy": "npm install --production && pm2 startOrRestart ecosystem.json --env production",
"env": {
"NODE_ENV": "production"
}
},
"staging": {
"user": "<user to run as on stage>",
"host": "<staging IP or domain>",
"ref": "origin/<staging branch>",
"repo": "git@github.com:<github username>/<project name>.git",
"path": "/var/www/<staging directory>",
"post-deploy": "npm install --production && pm2 startOrRestart ecosystem.json --env staging",
"env": {
"NODE_ENV": "staging"
}
}
}
}
In the above config, “script” under “apps” array refers to the entry-point script you made above. I’ve been calling it index.js
in this post. I for my staging
config I tell PM2 to set my NODE_ENV
variable to be set to “staging” because in my config.js I have a “staging” key. Ghost will load the config key of the same name as the value of NODE_ENV
.
Deploy Key
If your server is new you may not have setup public/private keypair authentication. Follow the instructions Github provides. You’ll then want to add this key as a deploy key for your project on Github. This will allow PM2 to authenticate with Github to clone the repo.
While you are also connected to the server you should install PM2 using npm, just as you did before locally (npm install -g pm2
).
Deploying
Be sure that Github is in the server’s known_hosts
file or you’ll get cloning errors. This can be done by running ssh git@github.com
on the server and accepting the prompt to add it to the file.
We need to have PM2 setup the server for deployment. Run pm2 deploy <ecosystem.json config key> setup
from your local machine. For example, to setup my “staging” config above I would run pm2 deploy staging setup
. I would expect to see something like this:
$ pm2 deploy staging setup
--> Deploying to staging environment
--> on host <staging IP or domain>
○ running setup
○ cloning git@github.com:alecho/<project name>.git
Cloning into '/var/www/<staging directory>/source'...
Warning: Permanently added the RSA host key for IP address '**Github's IP here**' to the list of known hosts.
○ setup complete
--> Success
Once PM2 is done setting up the server run pm2 deploy <ecosystem.json config key>
. Just like above, this would be pm2 deploy staging
for my staging environment and should output something like:
$ pm2 deploy staging
--> Deploying to staging environment
--> on host <staging IP or >
○ deploying
○ hook pre-deploy
○ fetching updates
Fetching origin
Warning: Permanently added the RSA host key for IP address '**Github's IP here**' to the list of known hosts.
○ resetting HEAD to origin/staging
HEAD is now at <git hash for latest commit on origin/staging> <commit message here>
○ executing post-deploy `npm install && pm2 startOrRestart ecosystem.json --env staging`
** npm install output here **
You shoudl then see the typical output of pm2 list
with the Ghost blog process running followed by:
Use `pm2 show <id|name>` to get more details about an app
○ hook test
○ successfully deployed origin/staging
--> Success
That’s It!
You’re now able to deploy your latest changes to staging and production environments with ease. There’s no reason you couldn’t setup a deveopment enviroment with PM2 as well but I find that Ghost is easy enough to just “clone”, “install”, and “start” that I haven’t yet.
If you run into issues or know of a better way of doing things let me know in the comments.