Ghost is an open-source blogging platform to help you create a professional-looking blog. It was launched in 2013 as an alternative to WordPress because it was getting overly complex. Ghost is written in JavaScript and is powered by Node.js.
In this tutorial, we will explore how to install Ghost CMS using Docker on a server powered by Ubuntu 20.04. We will also use Nginx as a proxy and Let's Encrypt SSL certificate to secure our installation.
Prerequisites
-
A server running Ubuntu 20.04.
-
A non-root sudo user.
-
Make sure everything is updated.
$ sudo apt update $ sudo apt upgrade
Step 1 - Configure UFW Firewall
The first step is to configure the firewall. Ubuntu comes with ufw (Uncomplicated Firewall) by default.
Check if the firewall is running.
$ sudo ufw status
You should get the following output.
Status: inactive
Allow SSH port so that the firewall doesn't break the current connection on enabling it.
$ sudo ufw allow OpenSSH
Allow HTTP and HTTPS ports as well.
$ sudo ufw allow 80
$ sudo ufw allow 443
Enable the Firewall
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
Check the status of the firewall again.
$ sudo ufw status
You should see a similar output.
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
80 ALLOW Anywhere
443 ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
80 (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)
Step 2 - Install Certbot and obtain the SSL certificate
Before we proceed, we need to install the Certbot tool and install an SSL certificate for our domain.
To install Certbot, we will use the Snapd package installer. Certbot's official repository has been deprecated and Ubuntu's Certbot package is more than a year old. Snapd always carries the latest stable version of Certbot and you should use that. Fortunately, Ubuntu 20.04 comes with Snapd pre-installed.
Ensure that your version of Snapd is up to date.
$ sudo snap install core
$ sudo snap refresh core
Remove any old versions of Certbot.
$ sudo apt remove certbot
Install Certbot.
$ sudo snap install --classic certbot
Use the following command to ensure that the Certbot command can be run by creating a symbolic link to the /usr/bin
directory.
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
Generate an SSL certificate.
$ sudo certbot certonly --standalone -d example.com
The above command will download a certificate to the /etc/letsencrypt/live/example.com
directory on your server.
Step 3 - Install Docker and Docker Compose
The first step is to install the Docker Engine and Docker Compose. First, uninstall any old versions of Docker.
$ sudo apt remove docker docker-engine docker.io containerd runc
Install some packages required for Docker to run.
$ sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release
Add the Docker's official GPG key.
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
Add Docker's official repository.
$ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the system repositories.
$ sudo apt update
Install the latest version of Docker Engine.
$ sudo apt install docker-ce docker-ce-cli containerd.io
Verify that Docker Engine is running and installed correctly.
$ sudo docker run hello-world
By default, Docker requires sudo to run. To get around that, we can add the current user account to docker
user group.
$ sudo usermod -aG docker ${USER}
To apply the new group membership, log out and log back in or use the following command.
$ su - ${USER}
Now, you can run docker commands without using sudo.
Next, download the current stable release of Docker compose.
sudo curl -L "https://github.com/docker/compose/releases/download/1.28.6/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
At the time of writing this tutorial, 1.28.6 is the latest version of Docker compose. You can always change or choose a different version in the command by checking from the Github releases page of Docker compose.
Apply executable permission to the installed version of Docker compose.
$ sudo chmod +x /usr/local/bin/docker-compose
Test the installation.
$ docker-compose --version
docker-compose version 1.28.6, build 5db8d86f
Step 4 - Install Ghost
The Ghost installation will comprise of three components - Ghost package, a database server such as MySQL and a web server (Nginx). All these services can be installed using a single Docker compose file.
Create Docker Compose File
First, create a directory to store and launch your Docker compose file from.
$ mkdir ghost && cd ghost
Create a file named docker-compose.yml
and open it with Nano editor.
$ nano docker-compose.yml
Paste the following code in the file. Replace example.com
with your domain and insert a database password in place of your_password
value. Keep the values for database__connection__password
and MYSQL_ROOT_PASSWORD
the same. Replace <username>
with your server's username.
version: '3.3'
services:
ghost:
image: ghost:latest
restart: always
depends_on:
- db
environment:
url: https://example.com
database__client: mysql
database__connection__host: db
database__connection__user: ghost
database__connection__password: ghostdbpass
database__connection__database: ghostdb
mail__transport: SMTP
mail__options__host: {Your Mail Service host}
mail__options__port: {Your Mail Service port}
mail__options__secureConnection: {true/false}
mail__options__service: {Your Mail Service}
mail__options__auth__user: {Your User Name}
mail__options__auth__pass: {Your Password}
volumes:
- /home/<username>/ghost/content:/var/lib/ghost/content
db:
image: mariadb:latest
restart: always
environment:
MYSQL_ROOT_PASSWORD: your_mysql_root_password
MYSQL_USER: ghost
MYSQL_PASSWORD: ghostdbpass
MYSQL_DATABASE: ghostdb
volumes:
- /home/<username>/ghost/mysql:/var/lib/mysql
nginx:
build:
context: ./nginx
dockerfile: Dockerfile
restart: always
depends_on:
- ghost
ports:
- "80:80"
- "443:443"
volumes:
- /etc/letsencrypt/:/etc/letsencrypt/
- /usr/share/nginx/html:/usr/share/nginx/html
The Docker compose file creates a few mount points, i.e. it maps certain directories on the server to directories inside the container.
- The
/var/lib/ghost/content
and/var/lib/mysql
directories inside your containers are mapped to/home/<username>/ghost/content
and/home/<username>/ghost/mysql
on the server. - Nginx uses the
/etc/letsencrypt/
bind to access Let's Encrypt certificates from the server. - Nginx also needs access to the user directory
/usr/share/nginx/html
so that it can access Let's Encrypt Challenge files for the certificate.
In the above file, we have also included options for setting up mail. If you are using a popular SMTP mail service like Mailgun, Mailjet, Mandrill, Postmark, Sendgrid, SendCloud, SES, Zoho or Gmail, you can just add the name of the service and your SMTP username and password and do away with the rest of the fields. Otherwise, fill all the other options and remove the service name and it should still work. You can check more mail options on Ghost's documentation about Mail options.
Create directories for all the bind mounts described above (except for /etc/letsencrypt
, which was already created when we created the certificate before)
$ cd ~/ghost
$ mkdir content
$ mkdir mysql
$ sudo mkdir -p /usr/share/nginx/html
Create the Nginx Docker Image
The Docker compose file that we created relies on the Nginx Docker image. To make it work, we need to include a customized configuration file for Nginx which will work with Ghost.
Create a directory for this image in the current directory.
$ mkdir nginx
Create a file named Dockerfile
in this directory.
$ touch nginx/Dockerfile
Paste the following code in the Dockerfile
.
FROM nginx:latest
RUN rm /etc/nginx/conf.d/default.conf
COPY ghost.conf /etc/nginx/conf.d
The above code instructs Docker to use the latest Nginx image. It also deletes the default Nginx configuration file and copies the custom configuration file that we have created for our Ghost CMS.
Create a file named ghost.conf
in the nginx
directory.
$ touch nginx/ghost.conf
Paste the following code in the ghost.conf
file. Replace all instances of example.com
with your domain.
server {
listen 80;
listen [::]:80;
server_name example.com;
# Useful for Let's Encrypt
location /.well-known/acme-challenge/ { root /usr/share/nginx/html; allow all; }
location / { return 301 https://$server_name$request_uri; }
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
access_log /var/log/nginx/ghost.access.log;
error_log /var/log/nginx/ghost.error.log;
client_max_body_size 20m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://ghost:2368;
}
}
The above configuration will redirect all HTTP requests to HTTPS and will serve as a proxy for Ghost service to serve it via your domain.
Step 5 - Run the Site
Run the following command from the ghost
directory to start the Ghost service.
$ docker-compose up -d
Now, you can verify your installation by opening https://example.com
in your web browser. It may take several minutes for the Docker to power up all the services, so you may need to refresh if you don't see your blog right away.
If your site doesn't show up in the browser, you need to review the Docker logs. To that do that, shut your container first.
$ cd ghost
$ docker-compose down
Run Docker compose in an attached state to view the logs generated by each container.
$ docker-compose up
To shut down the container, and return back to the bash prompt, press CTRL+C
. Once you have finished troubleshooting, you can run the container again and your blog should be visible this time.
Step 6 - Complete Setup
To finish setting up your Ghost blog, visit https://example.com/ghost
in your browser. The extra /ghost
at the end of your blog's domain redirects you to Ghost's Admin Panel or in this case the setup since you are accessing it for the first time.
Here, you will be required to create your Administrator account and choose a blog title.
You can also invite additional staff members or collaborators for your blog which you can do it later as well if you choose to skip now.
At the end of the setup, you will be greeted with the Ghost's Administration panel.
If you want to switch to dark mode, you can do so by clicking on the toggle switch next to the settings gear button at the bottom of the settings page.
You will see already some default posts which are basically guides to help you navigate and use Ghost. You can unpublish or delete them and start posting.
Step 7 - Update Ghost
In our Docker compose file, we are pulling the latest version of Ghost available at the time of installation which makes it easy to update your Ghost blog.
To update, you need to shut down your container, pull up the latest images and then run the container again.
$ docker-compose down
$ docker-compose pull && docker-compose up -d
Step 8 - Renew your Let's Encrypt SSL Certificate
Let's Encrypt certificates are valid only for 90 days. Therefore, we need to set up a cronjob that will renew the certificate automatically.
Open Crontab in your editor.
$ sudo crontab -e
Paste the following line at the end which will run Certbot at 11 PM every day. Replace example.com
with your domain.
0 23 * * * certbot certonly -n --webroot -w /usr/share/nginx/html -d example.com --deploy-hook='docker exec ghost_nginx_1 nginx -s reload'
Running at 11 PM every day doesn't mean your certificate will be renewed every day since Certbot will renew your certificate only if its expiration date is within 30 days. Running this every night gives the script a number of chances to try before the expiration.
The above command will also restart the Nginx server inside the Docker container post successful renewal.
You can test the cronjob using the --dry-run
option of Certbot.
$ sudo bash -c "certbot certonly -n --webroot --dry-run -w /usr/share/nginx/html -d example.com --deploy-hook='docker exec ghost_nginx_1 nginx -s reload'"
Conclusion
This concludes our tutorial on how to set up Ghost CMS on your Ubuntu 20.04-based server using Docker. If you have any questions or any feedback, share them in the comments below.