Ghost in A shell - Part I : localhost
Tags: ghost, productivity, docker, tutorial
Published: 2018-05-24 21:02:50

---
title: Ghost in A shell - Part I : localhost
published: true
tags: ghost,productivity,docker,tutorial
canonical_url: https://manu.breton.ch/blog/ghost-in-a-shell-part1-your-blog-on-localhost/
---

 ![Ghost in A shell - Part I : localhost](https://manu.breton.ch/blog/content/images/2018/05/featured-image-4.png)


The last blog from the Foundation Ghost, "[After 5 years and $3M, here's everything we've learned from building Ghost](https://blog.ghost.org/5/)", was an eye-opener for me.

Not only the company model: a Foundation whose owners will **not** be able to sell, on purpose; but especially the actual love behind the product:

> when we do anything you can be absolutely certain it's in the sole interest of building the best product we can for the right reasons.

Coming from WordPress, having played with Jekyll and Hugo, I could only take the bait to try Ghost. Therefore, I happily threw myself into the doc and the installation process to get a taste of the product.

# Objective

The overall objective of this (short) series of blogs is to be able to spawn easily multiple (independent) ghost blogs on one's beloved server.

It's intended for developers/ops that wish to put their hands into the mechanics of Ghost, and need to easily spawn/kill blogs here and there.

I will not go through the code of the blog itself, neither on the topics of writing blogs, nor creating a theme. I will limit my sharing to the installation-side of Ghost, and how to facilitate/automate the process.

Would you **not** feel like a devops at the end of the day, you might find better value in the [Ghost(Pro)](https://ghost.org/) service, which will do a much better job of hosting your blogs, and keeping them running and up to date.

In this first (of a few) blogs, I will focus on the installation of instances on localhost. The purpose is to set up locally a sandbox, as similar as possible as what we will ultimately have on production (hopefully).

# Following the path

There I went on my way, clicking on the [Developer \> Documentation](https://ghost.org/developers/) link of the Ghost website, scrolling a bit down, and finding the expected entry point for setting up things:

![Ghost in A shell - Part I : localhost](https://manu.breton.ch/blog/content/images/2018/05/setup.png)

After clicking on the Setup box, I had the choice between "_I want to install Ghost on my own server_" and "_I want to try Ghost out quickly_". I thought I was very lucky and I clicked on the second option: [local install guide](https://docs.ghost.org/v1.0.0/docs/install-local).

> The TL;DR version of local installs, is you want to make use of Ghost CLI's super handy command: `ghost install local`

From there I stumbled on

- the current configuration of my environment
- its Node version
- and my lack of knowledge of the JavaScript stack.

I quickly switched to one of my server running Ubuntu, where it was also quite painful to get all the versions aligned... At the end of my 10mins timebox, I could not make the ghost-cli execute itself properly.

I (quickly) switched again for a Docker image, and there you go...

# Thank you Docker!

There is an official image (i.e. maintained by the DockerHub staff) on the docker hub, [ghost](https://hub.docker.com/_/ghost/), with a few examples of the commands available.

Back on my Mac I created a simple Makefile:

```
NAME?=ghost-local
DOMAIN?=localhost
PORT?=3001

dev: 
	# Simply start a ghost container making it directly available through $$PORT
	docker run --rm -d --name ${NAME} \
		-v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content \
		-p ${PORT}:2368 \
		-e url=http://${DOMAIN}:${PORT} \
		ghost:1-alpine
```

> if/when copy-pasting, make sure to use tabs for the indentation (not spaces)

Running `make` was the only thing left to get my blog running on [http://localhost:3001](http://localhost:3001)

Running `NAME=another PORT=3002 make` runs a second blog available on [http://localhost:3002](http://localhost:3002)

## Docker parameters

A quick explanation on the arguments passed above:

- `--rm` will remove the container when it stops, thus preventing conflicts when spawning a new container with the same ${NAME}
- `-d` will run the container in daemon mode
- `--name ${NAME}` will name your container. This one is optional, but it is nice to have the same name for the container and the data volume defined on the following line...
- `-v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content` will mount the Ghost files locally on your file system into subfolder _./instances_. That will persist data (and allow you to sneak into the code later on).
- `-p ${PORT}:2368` exposes the blog on the local ${PORT}
- `-e url=http://${DOMAIN}:${PORT}` tells ghost what URL will be used to access it.

## A bit of cleaning up

Before next step, you can stop the containers with `docker stop ghost-local another`. However, the data will not be lost thanks to the volumes.

Moreover, they will be used again the next time you start a blog with the same name.

# Thank you Traefik !

My initial objective achieved, I went for the bonus...

## The bonus

The first one was to be able to serve on port 80, and to have the blogs sit on different paths. Something like

- [http://localhost/ghost-local](http://localhost/ghost-local)
- [http://localhost/another-ghost](http://localhost/another-ghost)

## The "prod-stack" companion

I have my so humbly called ["prod-stack" repo](https://github.com/ebreton/prod-stack) that provides me with this kind of routing functionalities (among others). It is based on Traefik (and NGinx), and allows me to quickly set up a proxy, a DB and a memcached.

It's running on my Mac, therefore making this solution free for me. I could only buy it :)

For you dear reader, I have taken advantage of this blog to open my prod-stack on github, and I also have done a bit of curating with the Readme (even though it is still more a draft than a real documentation).

Here is the fast-track setup to pull and run all docker containers:

```
$ git clone git@github.com:ebreton/prod-stack.git
Cloning into 'prod-stack'...
...
$ cd prod-stack
$ make
cp etc/traefik.toml.sample etc/traefik.toml
cp etc/db.sample.env etc/db.env
sed -i s/password/auMr9jsMrPnX0Oatb9j4yX2AYBN2jTQV/g etc/db.env
Generated etc/db.env
docker-compose pull
Pulling nginx-entrypoint ...
...
```

Once the logs stabilize, you can `Ctrl-C` to get back on the command line, and `docker ps` to check that the stack is running.

The Traefik dashboard will be available on [http://localhost:8081](http://localhost:8081) with the user _test/test_

![Ghost in A shell - Part I : localhost](https://manu.breton.ch/blog/content/images/2018/05/traefik.png)

Please, do not hesitate to feedback thanks to [issues](https://github.com/ebreton/prod-stack/issues) if you felt the curiosity to try it.

## Adding `make traefik`

After making sure that Traefik is running, I added this command to the Makefile:

```
traefik:
	# Start a ghost container behind traefik on path $$NAME
	# Beware of --network used, which is the same one traefik should be using
	docker run --rm -d --name ${NAME} \
		-v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content \
		-e url=http://${DOMAIN}/${NAME} \
		--network=proxy \
		--label "traefik.enable=true" \
		--label "traefik.backend=${NAME}" \
		--label "traefik.frontend.entryPoints=http" \
		--label "traefik.frontend.rule=Host:${DOMAIN};PathPrefix:/${NAME}" \
		ghost:1-alpine
```

The parameters are similar to the ones of the previous command with the following changes:

- The ${PORT} is not exposed anymore since Traefik will handle the routing
- `--network=proxy` launches the container in the same network as the one where Traefik is running (the values `proxy` is arbitrary and comes from the default configuration of the "prod-stack")
- `--label key=value` are used by Traefik to create the appropriate route

Running `make traefik` will now run a blog on [http://localhost/ghost-local](http://localhost/ghost-local)

Running `NAME=another-ghost make traefik` runs a second blog available on [http://localhost/another-ghost](http://localhost/another-ghost)

# Conclusion

**First of all** , I wanted to easily spawn Ghost blogs on my local machine,

- [http://localhost:3001](http://localhost:3001)
- [http://localhost:3002](http://localhost:3002)

The native documentation did not survive to my impatience, mainly because I knew it would be fast and smooth with Docker.

**Secondly** , I wanted to use the standard port (80 to start with), and route the blogs according to their path,

- [http://localhost/ghost-local](http://localhost/ghost-local)
- [http://localhost/another-ghost](http://localhost/another-ghost)

It was given for free with Traefik (and my "prod-stack") and I must admit I had a lot of fun playing with make. However, the result is still one for a developer, surely not ready for production (despite the name of the stack).

Nevertheless, the stack can provide HTTPs thanks to Let's Encrypt, a DB and some easy way to manage all this... I will dive into this in the [next part](https://dev.to/ebreton/ghost-in-a-shell---part-ii---https-is-the-norm-1jj4) to get something more robust... maybe

- [https://manu.breton.ch/blog](https://manu.breton.ch/blog) ?