Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

How to install and configure Ghost CMS on Debian

profile picture
Author
Magnus Helander
Published
2023-06-13
Time to read
8 minutes reading time

Introduction

In this tutorial we will set up a Ghost CMS on a server with Ampere Altra processors. Running Ghost on Hetzner's CAX11 with Arm64 architecture, for example, will give you two vCPUs and 4GB RAM - a very high-performance Ghost installation, with capacity for additional applications.

We will cover:

  1. Creating the local user ghostadmin
  2. Installing Docker from official repositories
  3. Launching Ghost and MySQL in Docker containers using Docker Compose
  4. Configuring nginx as web server and reverse proxy
  5. Enabling HTTPS / SSL using Certbot in a Snap package
  6. Activating the UFW Uncomplicated Firewall

Prerequisites

  • A server with Arm64 architecture and Debian 11 (e.g. Hetzner Cloud CAX11).

  • A connection to the server over SSH as root.

  • A domain name with a DNS A-record pointing to the IP address of your server.

  • Updated Debian 11 Linux with packages install, ca-certificates, curl, gnupg, ufw, joe, snapd and nginx

    apt update ; apt upgrade
    apt-get install ca-certificates curl gnupg ufw joe snapd nginx
    install -m 0755 -d /etc/apt/keyrings

Example terminology

  • Domain name: example.com

Step 1 - Create local user ghostadmin

In this step we create the local user ghostadmin and assign it to the group sudo so that we can execute commands as root with the sudo command.

adduser ghostadmin
usermod -aG sudo ghostadmin

Logout root and log back in over SSH as user ghostadmin.

We can also use su - ghostadmin to switch to the new user without having to log out and back in again.

Step 2 - Install Docker

We will be running Ghost and MySQL in Docker containers, with a standard install of nginx as reverse proxy and web server. In this step we install the Community Edition (CE) of Docker from official repositories.

curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 

sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

To verify that everything is OK with our Docker installation use:

sudo docker run hello-world

You should see this:

ghostadmin@debian-arm64:~# sudo docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

Now we need to add our user ghostadmin to the docker group so that we can run the docker command without sudo:

sudo usermod -aG docker ghostadmin

Update group membership for user ghostadmin by logging out and logging back in again.

Step 3 - Launch Ghost and MySQL in Docker

In this step we create a docker-compose.yml file which launches Ghost and MySQL 8 in Docker containers.

Create docker-compose.yml in a new directory ghostcms in our home directory - /home/ghostadmin/ghostcms:

mkdir ghostcms ; cd ghostcms
joe docker-compose.yml  #or your favourite editor

In the docker-compose.yml file below note:

  • the volumes: section where /var/lib/ghost/content maps to a folder in your $HOME directory, ex /home/ghostadmin/ghostcms/content.

This is where Ghost will store media, configuration files and themes. You must create this empty directory.

  • the LongAndComplexRootPassword must be the same in ghost and db sections.
  • edit url: https://example.com to match your domain name.

Here's the Docker Compose file docker-compose.yml:

version: "3.7"
 
services:
  ghost:
    image: ghost:5-alpine
    restart: always
    ports:
      -  2368:2368
    expose:
      - 2368
    environment:
      database__connection__host: db
      database__connection__user: root
      database__connection__password: LongAndComplexRootPassword
      database__connection__database: ghost
      url: https://example.com
    links:
      -  db
    volumes:
      -  /home/ghostadmin/ghostcms/content:/var/lib/ghost/content
    depends_on:
      -  db
  db:
    image: arm64v8/mysql:8
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: LongAndComplexRootPassword
      MYSQL_DATABASE: ghost
      MYSQL_USER: ghost
      MYSQL_PASSWORD: GhostUserPassword
    volumes:
      - mysql-data:/var/lib/mysql
 
volumes:
  mysql-data:
  ghost-data:

Now we can launch Ghost and MySQL in Docker containers and daemonize ( the -d option, run in background ) with:

docker compose up -d

Check that everything is OK with docker ps. You should see something like this:

CONTAINER ID   IMAGE             COMMAND                  CREATED        STATUS        PORTS                                       NAMES
4ae328d5281a   ghost:5-alpine    "docker-entrypoint.s…"   1 minute ago   Up 1 minute   0.0.0.0:2368->2368/tcp, :::2368->2368/tcp   ghost-docker-ghost-1
c47c72ae2adb   arm64v8/mysql:8   "docker-entrypoint.s…"   1 minute ago     Up 1 minute   3306/tcp, 33060/tcp                         ghost-docker-db-1

Step 4 - Configure nginx as reverse proxy

In this step we set up a Nginx web server which will communicate with Ghost internally on port 2368 and make Ghost available over an encrypted HTTPS connection on port 443.

cd /etc/nginx
sudo rm sites-enabled/default
sudo rm sites-available/default
sudo joe sites-available/example.conf #use any editor...

Edit server_name to match your domain name. Certbot will add certificates for the HTTPS connection in the next step.

server {

 # Port 80 initially, SSL will be enabled by Certbot

    listen 80;
    listen [::]:80;
    server_name example.com;

# Housekeeping

    server_tokens off;
    client_max_body_size 1g;

# Certbot stuff

   location ~ /.well-known {
        allow all;
    }
 
# Block evildoers

    location ~* \.(aspx|php|jsp|cgi)$
    {
      deny all;
    }

# Enable gzip compression

    gzip on;
    gzip_min_length 1000;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript image/svg+xml;
    gzip_buffers 16 4k; 
    gzip_disable "msie6";
 
 # Reverse proxy to Ghost in docker running on port 2368
  
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:2368;
  
    }
 
}

This configuration is stored in the directory sites-available but must also be available in the directory sites-enabled. So we create a symbolic link to /etc/nginx/sites-enabled using this command:

sudo ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/

Check that everything is OK and reload nginx with these two commands:

sudo nginx -t
sudo systemctl reload nginx

Ghost should now be available at http://example.com on port 80. Your browser may not allow non-HTTPS access on port 80, so we will generate an SSL certificate using Certbot.

Step 5 - Enable SSL with Certbot

Certbot can provide wildcard certificates for all domains under *.example.com, this involves creating two TXT records in your DNS domain admin. In this tutorial we will only create a certificate for a single domain.

Install Certbot as a Snap package with:

sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Run Certbot to create a certificate for example.com:

sudo certbot --nginx

If everything works out fine, you will find these lines added to the nginx configuration file example.conf and a new server section for port 80:

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

You should now be able to access Ghost securely over HTTPS at https://example.com. Create an admin user at https://example.com/ghost and you're done!

Ghost welcome screen

Step 6 - Post-installation security

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow SSH  # Important, or you will be locked out of your server...
sudo ufw allow 'Nginx Full'
sudo ufw enable

Conclusion

Congratulations! You now have a self-hosted Ghost blog up and running on a server with Arm64 architecture. If you want to send emails from your Ghost installation, add SMTP information in the environment section, and restart your Ghost Docker container.

    environment:
      - mail__options__auth__pass=your_password
      - mail__options__auth__user=your_email
      - mail__options__host=smtp_host_address
      - mail__options__port=smtp_port_587
      - mail__options__secure=false
      - mail__options__service=name_for_service
      - mail__transport=SMTP

You can find additional information at the official Ghost documentation.

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2024 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more