Deploy Ghost blog with Dokku

Step-by-step guide on how to deploy production-ready & scalable Ghost.io blog with Dokku PaaS (free & open-source Heroku alternative) on any server. This is how I run the blog you are reading right now.

Deploy Ghost blog with Dokku

Step 1: Rent a server

I prefer using DigitalOcean because it is really easy to create, monitor, and scale your droplets. I'd always suggest having a server with at least 2CPU and 2Gb RAM. My referral link with $100 bonus: https://m.do.co/c/260555f64021

Step 2: Install Dokku

Unlike Heroku, Dokku doesn't have a handy web interface where you can just press buttons. If you are not familiar with Heroku CLI (which is similar to the Dokku interface), don't worry: I'll give you all the necessary commands you'll need below.

I'd strongly recommend installing the latest Dokku from their website, here is the current latest version (July 2021):

wget https://raw.githubusercontent.com/dokku/dokku/v0.24.10/bootstrap.sh
sudo DOKKU_TAG=v0.24.10 bash bootstrap.sh

⚠️ Don't forget to enter your server IP address in the browser to finish Dokku setup: it will require confirming some stuff through its web interface (which still has only one page).

Step 3: Create & Setup Dokku app

Imagine that you have bought a domain okhlopkov.com for your blog. I'll use this URL for demo purposes.

Create Dokku app

dokku apps:create ghost

Install MySQL addon and link it to the app:

dokku plugin:install https://github.com/dokku/dokku-mysql.git mysql
dokku mysql:create ghost
dokku mysql:link ghost ghost

Now connect MySQL the proper way

Let's connect MySQL database the way that Ghost will understand. Let's find out our DATABASE_URL in the config:

dokku config:show ghost

My DATABASE_URL looks like this:

mysql://mysql:b35543396688d777c@dokku-mysql-ghost:3306/ghost

According to the official docs, we need to set connection params separately. Check how I split this URL into different environment variables:

dokku config:set ghost \
  database__connection__host=dokku-mysql-ghost \
  database__connection__user=mysql \
  database__connection__password=b35543396688d777 \
  database__connection__database=ghost

You will also require these environment variables:

dokku config:set ghost NODE_ENV=production url=https://okhlopkov.com database__client=mysql

Now the hardest part. You also need to update MySQL from inside its CLI (read comments):

# remember root password
cat /var/lib/dokku/services/mysql/ghost/ROOTPASSWORD

# Enter MySQL container
dokku mysql:enter ghost

# login as root
mysql -u root -p
# then copy-paste the root password you gathered before

# and then in mysql> console:
# don't forget to replace password from your DATABASE_URL 
ALTER USER 'mysql' IDENTIFIED WITH mysql_native_password by 'b35543396688d777';
FLUSH PRIVILEGES;

Some more Dokku tweaks

Allow Nginx to receive uploads up to 50Mb:

dokku nginx:set ghost client-max-body-size 50m
dokku proxy:build-config ghost

Specify docker persistent storage for the images you uploaded through Ghost Admin:

mkdir -p /root/ghost/content
dokku storage:mount ghost /root/ghost/content:/var/lib/ghost/content

Finally, deploy from the official Docker image and proxy its port to HTTP(80) :

dokku git:from-image ghost ghost:latest
dokku proxy:ports-set ghost http:80:2368

Attach domain and create Letsencrypt SSL certificates for HTTPS (don't forget to create 'A record' to point your domain to your server IP in your DNS provider). If it is the first time you setup Letsencrypt with Dokku, you may be asked to enter your email:

dokku config:set --global [email protected]

dokku domains:set ghost okhlopkov.com
dokku letsencrypt:enable ghost

🎉 That's all folks! Now your blog is deployed in production! 🎉

P.S. Helpful snippets

If you want to check Ghost logs, use this Dokku command:

dokku logs ghost -t

If you want to update the ghost docker image, just deploy again:

dokku git:from-image ghost ghost:latest