Pre-requisites
- Root access to your server or have access to an account that has sudo powers on your servers – Instructions here to see how to give a linux user sudo powers.
nodejs
andnpm
installed – Instructions here.
Installing
Where to install
First let’s decide where to put the ghost directory. I chose /var/www/
, because that’s where websites are usually on CentOS. You can choose whichever directory you like. However note that we will be running ghost using a specialized user so make sure you can give another user permission to access that directory. That means don’t put it in /root/
. Go to your chosen directory and get the latest ghost file.
[ahmed@amayem ~]$ cd /var/www
[ahmed@amayem www]$ curl -L https://ghost.org/zip/ghost-latest.zip -o ghost.zip
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
102 307 0 307 0 0 212 0 --:--:-- 0:00:01 --:--:-- 0Warning: Failed to create the file ghost.zip
0 1823k 0 4096 0 0 2097 0 0:14:50 0:00:01 0:14:49 2097
curl: (23) Failed writing body (0 != 4096)
Hmm that’s not good. Looks like a permissions issue. Let’s check if that is the case:
[ahmed@amayem www]$ ls -l /var | grep www
drwxr-xr-x 6 root root 4096 Oct 5 2012 www
Yup. The folder is owner by root and only root can write to it. We need to sudo
.
[ahmed@amayem www]$ sudo curl -L https://ghost.org/zip/ghost-latest.zip -o ghost.zip
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1823k 100 1823k 0 0 969k 0 0:00:01 0:00:01 --:--:-- 2178k
Ah looks much better. Let’s check the new file is there.
[ahmed@amayem www]$ ls
cgi-bin error ghost.zip html icons
Good! Now we unzip it into a directory called ghost. We will once again need to use sudo
.
[ahmed@amayem www]$ sudo unzip -uo ghost.zip -d ghost
Archive: ghost.zip
inflating: ghost/Gruntfile.js
inflating: ghost/LICENSE
...
A huge load of lines should appear. I truncated the output to preserve space. Let’s check the directory has been made.
[ahmed@amayem www]$ ls
cgi-bin error ghost ghost.zip html icons
Looks good. We don’t need the ghost.zip
file so let’s remove it. If we ever need it again we can always download the latest version.
[ahmed@amayem www]$ sudo rm ghost.zip
[ahmed@amayem www]$ ls
cgi-bin error ghost html icons
Now let’s enter the ghost directory and see what we have.
[ahmed@amayem ghost]$ ls
bower.json config.example.js content core Gruntfile.js index.js LICENSE package.json README.md
Looks good! Let’s install the production version of ghost.
[ahmed@amayem ghost]$ npm install --production
In the beginning things will look good with a lot of green. Then things will not look so good with a lot of red, with an output that looks like this.
Error: EACCES, mkdir '/var/www/ghost/node_modules'
npm ERR! { [Error: EACCES, mkdir '/var/www/ghost/node_modules']
npm ERR! errno: 3,
npm ERR! code: 'EACCES',
npm ERR! path: '/var/www/ghost/node_modules',
npm ERR! fstream_type: 'Directory',
npm ERR! fstream_path: '/var/www/ghost/node_modules/connect-slashes',
npm ERR! fstream_class: 'DirWriter',
npm ERR! fstream_stack:
npm ERR! [ '/usr/lib/node_modules/fstream/lib/writer.js:171:23',
npm ERR! '/usr/lib/node_modules/mkdirp/index.js:37:53',
npm ERR! 'Object.oncomplete (fs.js:107:15)' ] }
npm ERR!
npm ERR! Please try running this command again as root/Administrator.
Argh we need another sudo
.
[ahmed@amayem ghost]$ sudo npm install --production
This time you should only see green. After a lot of output, ghost should be ready to go. Let’s see what we have in the directory first.
[ahmed@amayem ghost]$ ls
bower.json config.example.js content core Gruntfile.js index.js LICENSE node_modules package.json README.md
Looks like the directory node_modules
was made. Let’s give ghost a run then.
[ahmed@amayem ghost]$ npm start
> ghost@0.4.2 start /var/www/ghost
> node index
ERROR: Could not open config.js for write.
/var/www/ghost
Please check your deployment for config.js or config.example.js.
Error: Could not open config.js for write.
at WriteStream.<anonymous> (/var/www/ghost/core/bootstrap.js:46:36)
at WriteStream.EventEmitter.emit (events.js:117:20)
at WriteStream.<anonymous> (fs.js:1657:12)
at OpenReq.Req.done (/var/www/ghost/node_modules/express-hbs/node_modules/readdirp/node_modules/graceful-fs/graceful-fs.js:143:5)
at OpenReq.done (/var/www/ghost/node_modules/express-hbs/node_modules/readdirp/node_modules/graceful-fs/graceful-fs.js:63:22)
at Object.oncomplete (fs.js:107:15)
Argh! Again with the sudo
.
[ahmed@amayem ghost]$ sudo npm start
> ghost@0.4.2 start /var/www/ghost
> node index
Ghost is running in development...
Listening on 127.0.0.1:2368
Url configured as: http://my-ghost-blog.com
Ctrl+C to shut down
That’s what we wanted. I can also run it using index.js
.
[ahmed@amayem ghost]$ node index.js
ERROR: SQLITE_READONLY: attempt to write a readonly database, sql: update "settings" set "created_at" = ?, "created_by" = ?, "id" = ?, "key" = ?, "type" = ?, "updated_at" = ?, "updated_by" = ?, "uuid" = ?, "value" = ? where "id" = ?, bindings: Thu Apr 10 2014 18:57:56 GMT+0400 (MSD),1,16,installedApps,app,Thu Apr 10 2014 19:00:12 GMT+0400 (MSD),1,0f92d9a8-3531-481c-889d-c75023848aa9,[],16
Error: SQLITE_READONLY: attempt to write a readonly database, sql: update "settings" set "created_at" = ?, "created_by" = ?, "id" = ?, "key" = ?, "type" = ?, "updated_at" = ?, "updated_by" = ?, "uuid" = ?, "value" = ? where "id" = ?, bindings: Thu Apr 10 2014 18:57:56 GMT+0400 (MSD),1,16,installedApps,app,Thu Apr 10 2014 19:00:12 GMT+0400 (MSD),1,0f92d9a8-3531-481c-889d-c75023848aa9,[],16
at /var/www/ghost/node_modules/knex/clients/server/base.js:73:22
at process._tickDomainCallback (node.js:459:13)
From previous event:
at ClientBase.extend.getConnection (/var/www/ghost/node_modules/knex/clients/server/base.js:90:59)
at ClientBase.extend.query (/var/www/ghost/node_modules/knex/clients/server/base.js:36:22)
at null.<anonymous> (/var/www/ghost/node_modules/knex/lib/common.js:52:28)
From previous event:
at exports.Common.then (/var/www/ghost/node_modules/knex/lib/common.js:50:31)
at Promise.all.bind.then.then.attributes.(anonymous function).(anonymous function) (/var/www/ghost/node_modules/bookshelf/dialects/sql/model.js:185:38)
From previous event:
ERROR: SQLITE_READONLY: attempt to write a readonly database, sql: update "settings" set "created_at" = ?, "created_by" = ?, "id" = ?, "key" = ?, "type" = ?, "updated_at" = ?, "updated_by" = ?, "uuid" = ?, "value" = ? where "id" = ?, bindings: Thu Apr 10 2014 18:57:56 GMT+0400 (MSD),1,16,installedApps,app,Thu Apr 10 2014 19:00:12 GMT+0400 (MSD),1,0f92d9a8-3531-481c-889d-c75023848aa9,[],16
The app will not be loaded
Check with the app creator, or read the app documentation for more details on app requirements
Ghost is running in development...
Listening on 127.0.0.1:2368
Url configured as: http://my-ghost-blog.com
Ctrl+C to shut down
NOOOO! It’s the sudo
problem again.
[ahmed@amayem ghost]$ sudo node index.js
Ghost is running in development...
Listening on 127.0.0.1:2368
Url configured as: http://my-ghost-blog.com
Ctrl+C to shut down
Solving the sudo problem
Let’s solve this sudo
issue. By using sudo
I am effectively running ghost as root. That means I am exposing an application to the outside world with root priveleges, which is not very good security-wise. We can check the truth of this by opening another terminal and running following command to see who is running node index.js
:
[ahmed@amayem ~]$ ps aux | grep index.js
root 9439 0.0 0.0 9180 1728 pts/0 S+ 20:19 0:00 sudo node index.js
root 9440 0.1 0.9 120900 40228 pts/0 Sl+ 20:19 0:00 node index.js
ahmed 9478 0.0 0.0 5508 732 pts/1 S+ 20:26 0:00 grep index.js
Instead let’s make a ghost user that has ownership over the ghost files, that way we can avoid all this sudo
stuff.
Adding the ghost user
First we add the user
[ahmed@amayem ghost]$ sudo useradd -rm ghost -U
The command above makes a service user account called ‘ghost’ with a home directory and also adds him to a group of the same name. If you omit the -m
flag you will face some problems if you want to run forever
afterwards as I outline here.
Let’s check the permissions of the files in the ghost
directory.
[ahmed@amayem ghost]$ ls -l /var/www/ghost/
total 92
-rw-r--r-- 1 root root 558 Mar 26 18:39 bower.json
-rw-r--r-- 1 root root 4108 Mar 26 18:39 config.example.js
-rw-r--r-- 1 root root 4108 Apr 10 18:57 config.js
drwxr-xr-x 6 root root 4096 Apr 10 18:39 content
drwxr-xr-x 6 root root 4096 Apr 10 18:39 core
-rw-r--r-- 1 root root 36979 Mar 26 18:39 Gruntfile.js
-rw-r--r-- 1 root root 270 Mar 26 18:39 index.js
-rw-r--r-- 1 root root 1099 Mar 26 18:39 LICENSE
drwxr-xr-x 27 ahmed ahmed 4096 Apr 10 18:54 node_modules
-rw-r--r-- 1 root root 2628 Mar 26 18:39 package.json
-rw-r--r-- 1 root root 5627 Mar 26 18:39 README.md
I notice it’s mostly root. We want to change that to say ‘ghost’ in both columns. Let’s change the ownership and check again.
[ahmed@amayem ghost]$ sudo chown -R ghost:ghost /var/www/ghost
[ahmed@amayem ghost]$ ls -l /var/www/ghost/
total 92
-rw-r--r-- 1 ghost ghost 558 Mar 26 18:39 bower.json
-rw-r--r-- 1 ghost ghost 4108 Mar 26 18:39 config.example.js
-rw-r--r-- 1 ghost ghost 4108 Apr 10 18:57 config.js
drwxr-xr-x 6 ghost ghost 4096 Apr 10 18:39 content
drwxr-xr-x 6 ghost ghost 4096 Apr 10 18:39 core
-rw-r--r-- 1 ghost ghost 36979 Mar 26 18:39 Gruntfile.js
-rw-r--r-- 1 ghost ghost 270 Mar 26 18:39 index.js
-rw-r--r-- 1 ghost ghost 1099 Mar 26 18:39 LICENSE
drwxr-xr-x 27 ghost ghost 4096 Apr 10 18:54 node_modules
-rw-r--r-- 1 ghost ghost 2628 Mar 26 18:39 package.json
-rw-r--r-- 1 ghost ghost 5627 Mar 26 18:39 README.md
Beautiful! Now let’s switch to the ghost user and run node again without sudo
.
[ahmed@amayem ghost]$ sudo su ghost
[ghost@amayem ghost]$ node index.js
Ghost is running in development...
Listening on 127.0.0.1:2368
Url configured as: http://my-ghost-blog.com
Ctrl+C to shut down
Thats what we want.
Let’s take a look at those lines.
Ghost is running in development…
– I want to run it inproduction
, since I am, afterall, installing it on my production server.Listening on 127.0.0.1:2368
– I want it to point to my server’s ip and my domain.
Url configured as: http://my-ghost-blog.com
Looks like we need to do some configuration.
Configuration
Development versus Production
Let’s take a look at config.js
to see what environments are already pre-configured. You should see a config
Javascript Object that looks something like the following.
config = {
development: {
…
}
production: {
…
}
testing: {
…
}
…
}
From this we learn that there is a pre-configured environment called production
. We need to tell node
to use the production environment by changing the NODE_ENV
enviornment variable. Run ghost again as follows:
[ghost@amayem ghost]$ NODE_ENV=production npm start
> ghost@0.4.2 start /var/www/ghost
> node index
Ghost is running...
Your blog is now available on http://my-ghost-blog.com
Ctrl+C to shut down
Now it’s saying Ghost is running...
looks like we succeeded in getting it to use the production environment.
Configuring the ip and web address.
Your web address is the domain you bought and any subdomains you made on it. In my case it is ahmed.amayem.com
(my domain is amayem.com and ahmed is a subdomain).
You can find your ip as follows:
[ahmed@amayem ~]$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: venet0: <BROADCAST,POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
link/void
inet 127.0.0.1/32 scope host venet0
inet 192.241.121.253/32 brd 192.241.121.253 scope global venet0:0
inet 192.241.121.254/32 brd 192.241.121.254 scope global venet0:1
My ips in this example are 192.241.121.253
and 192.241.121.254
. My server has two ips.
Let’s modify the production
part of the config object. Change the url
to your own url and the host
to the ip of your server. Issue the following command:
[ghost@amayem ghost]$ vi config.js
Move your cursor using the arrow keys to where you want it. Press i
to enter insert mode and make your changes. Exit insert mode using Esc
or ctrl+c
then save and exit using :x
.
This is what mine looked like.
// ### Production
// When running Ghost in the wild, use the production environment
// Configure your URL and mail settings here
production: {
url: 'http://ahmed.amayem.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: '192.241.121.253',
// Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
port: '2368'
}
}
Now that my blog is running in production and pointing to the correct ip I would look to see it. Point your browser at your address and port number and you should see it. In my case it was ahmed.amayem.com:2368
.
Next Steps
Making sure ghost runs forever
We have our blog running now, but you will notice that our terminal is preocccupied with ghost
. If we close our terminal then the blog will close. We want it to run even when I close my terminal. Let’s try the following.
[ghost@amayem ghost]$ npm start &
[1] 11652
[ghost@amayem ghost]$
Looks good, we got our prompt back that means that ghost is running and we don’t have to have our terminal open … but wait what’s this:
> ghost@0.4.2 start /var/www/ghost
> node index
Ghost is running in development...
Listening on 127.0.0.1:2368
Url configured as: http://my-ghost-blog.com
Ctrl+C to shut down
Argh! So close. Looks like that didn’t work. We are left with the following ways:
- Using
forever
withcrontab
– Instructions here - Using
supervisor
– Intsructions coming soon - Using an
init
script – Intsructions coming soon
Making the blog available without putting a port number
Now I would like to be able to have my blog show up without the port number, in my case ahmed.amayem.com
. If the blog is the only site you have running on your domain then you may just setup ghost to use port 80 (Intsructions coming soon). Otherwise, whether you want to run your blog on a subdomain (like blog.amayem.com) and serve another site on your base domain (e.g. amayem.com), or you just want to serve ghost alone you can always use one of the following servers as a proxy:
- Apache – Intsructions coming soon
- nginx – Intsructions coming soon
- haproxy – Intsructions coming soon
I plan to discuss the differences between these options in later posts.
References
- Installing ghost on linux ghost docs
- Cyberciti for the linux commands ps and ip addr show