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:
- Creating the local user
ghostadmin
- Installing Docker from official repositories
- Launching Ghost and MySQL in Docker containers using Docker Compose
- Configuring nginx as web server and reverse proxy
- Enabling HTTPS / SSL using Certbot in a Snap package
- 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 inghost
anddb
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!
Step 6 - Post-installation security
-
Secure your SSH server - disabling root logins, etc.
-
Enable UFW Firewall
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.