I have been using Dokku for years to host personal sites and pet projects on a cheap web-server. Dokku allows you to deploy your projects through git-ops. The experience is very similar to deploying to other git-ops platforms like Heroku. This had been working smoothly for my previous personal blog which was a Wordpress app. When I decided to switch my personal site to Ghost I wasn't willing to sacrifice my Dokku setup. This made me figure out how to deploy a Ghost application to Dokku.
The main difference between my Wordpress setup and Ghost, is that there is no code repository managed by Git for my Ghost setup. Since Dokku is based on git-ops there is no clear way to deploy Ghost to a Dokku environment.
Fortunately Dokku allows you to directly deploy Docker images directly, be it a bit of an obscure feature, it works remarkably well. Directly deploying a Docker image gives us the advantage of not having to create a custom code repository for our Ghost deployment, saving us time spent on maintenance.
Prerequisites: I assume you have a server running Dokku, if you don't it is quite easy to spin one up on Digitalocean. This guide is tested against Dokku v0.14.6
Start by creating a new Dokku application
$ dokku apps:create my-ghost-app
Once you have an application setup you can pull in the latest Ghost docker image
$ docker pull ghost:latest
For Dokku to recognize a docker image from your local repository it should be name
dokku/name-of-app:tag. So let's tag our Docker image with the correct name:
$ docker tag ghost:latest dokku/my-ghost-app:latest
Now that we have tagged the Docker image we can deploy it:
dokku tags:deploy my-ghost-app latest
We should now have a docker container that is running Ghost (you can check by issuing the command
docker ps). But before we can reach our Ghost application from the outside world we need to expose it's port:
$ dokku proxy:ports-add my-ghost-app http:80:2368
This command tells Dokku to route all incoming traffic on port 80 to port 2368 inside our Ghost Docker container.
Since Docker containers are ephemeral and do not persist across restarts we should mount a storage volume to make sure we do not lose any content when that happens.
$ dokku storage:mount my-ghost-app /opt/my-ghost-app/ghost/content:/var/lib/ghost/content
And create the directory.
$ mkdir -p /opt/my-ghost-app/ghost/content
Now configure one of Ghost's environment variables to instruct it for which Host it can receive traffic:
$ dokku config:set my-ghost-app url=http://my-ghost-app.com
Since this domain is not known by the Nginx server operated by Dokku we need to add it through the Dokku command-line:
$ dokku domains:add my-ghost-app my-ghost-app.com
This should be enough for your blog to be running. Now you can start using you Ghost deployment. Or you could leverage any of the other Dokku features to for instance improve your deployment's security.
Bonus: uploading large files
By the default the Nginx proxy created by Dokku will not allow you to upload 'large files'. We can update the Nginx configuration to support these files. To do this we need to access the server running Dokku with ssh, and create the following file:
$ echo 'client_max_body_size 50M;' > /home/dokku/APP_NAME/nginx.conf.d/upload.conf
Where you replace
APP_NAME with the name of your Ghost Dokku app. After this we need to reload the Nginx service and we should be good to go:
$ service nginx reload
Troubleshooting In the case something is wrong you can troubleshoot by looking at the Docker container logs, through
$ dokku logs my-ghost-app. If this gives you zero output please check that the Ghost docker image is actually running
$ docker ps.
By default Ghost will persist all content to a sqllite database. This setup requires very little effort to get up and running but will not outperform a persistence layer backed by Mysql.
Start by creating a new database:
Now link this database to your app:
Finally you need to pass the
database__connection environment variable to Ghost. (replace
mysql://... with the url printed out by the previous command)
$ dokku config:set my-ghost-app\ database__client=mysql\ database__connection=mysql://...
That's it. After restarting, your Ghost is running on a Mysql datastore instead of Sqlite.
If you are running Ghost for your personal blog, chances are most posts wont update that frequently. So it is probably a good idea to let nginx cache the output generated by Ghost, this will reduce load on the NodeJS instance(s) in charge of rendering. Unfortunately Ghost sends the following
public, max-age: 0 which disallows Nginx to cache html output (by default).
To solve this we will need to deploy Ghost with a custom nginx configuration, instructing Ghost to cache any public content.
Add the following
Dockerfile to this new repository:
Now get the base nginx config template from the Dokku repository. Be sure to pick the configuration template for the version of Dokku that is running on your server.
We first need to instruct NGINX where it can store cached content on the file system. To do this add the following line above the first
Now we can activate the cache for the
This will enable caching for all locations, we probably do not want to cache the admin pages, these can be disabled by adding the following block nested inside the root location block:
If you support both HTTP and HTTPS (which you should), be sure to duplicate the process for the HTTPS part of the configuration.
Add all the files to your git repository and create a commit. Now add
my-ghost-app as a remote to your git repository:
$ git remote add dokku firstname.lastname@example.org:my-ghost-app
Finally push up your changes to the remote. Dokku's git-ops should take care of rolling out your application.
As a reference I have uploaded my setup to Github (notice that this NGINX config template is written for Dokku 0.14): https://github.com/matthisk/matthisk.com