Self-hosting Ghost CMS with NGINX using Docker

a note reminding myself how I host this blog

Self-hosting Ghost CMS with NGINX using Docker

NGINX and Let's Encrypt

Running NGINX is easy, especially when using this repo to setup NGINX and Let's Encrypt with docker-compose. It automates SSL certificate provisioning and renewal. I use it for enable https for this blog and all of my other web services.

The installation instructions are simple, and the documentation is good. However, the tricky part (for me at least) is getting the NGINX config (located at data/nginx/app.conf) right. Here's what mine looks like:

# This first server block takes any http requests for andrewbridges.org or 
# www.andrewbridges.org and redirects them to https
server {
    listen 80;
    server_name andrewbridges.org www.andrewbridges.org;
    server_tokens off;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name andrewbridges.org www.andrewbridges.org;
    server_tokens off;

    ssl_certificate /etc/letsencrypt/live/andrewbridges.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/andrewbridges.org/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    
    # NGINX limits uploads to 1MB by default. The following line increases
    # the limit to 2MB, which is helpful when you want to add larger
    # images to your blog.
    client_max_body_size 2M;

    location / {
        # The following block forwards requests to port 2368 of our Ghost
        # container, which will be named abridges_blog.
        proxy_pass    http://abridges_blog:2368;
        proxy_set_header    Host                $http_host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
    }
}

Ghost

I use a one-liner (split across several lines for readability) to run Ghost. It's pretty straightforward:

docker run \
    -d \
    --restart always \
    --name abridges_blog \
    --network nginx-certbot_default \
    -e url=https://andrewbridges.org \
    -v /home/andrew/abridges_blog:/var/lib/ghost/content \
    ghost:alpine

A couple things to note:

  • I use --network nginx-certbot_default to put the Ghost container on the same network as the NGINX & Let's Encrypt containers. (docker-copose creates this network by default.) This allows the two containers to communicate without exposing port 2368 (the default port for Ghost) on the host.
  • The volume mapping (-v /home/andrew/abridges_blog:/var/lib/ghost/content) is extremely important. It tells the container to use a folder on the host (/home/andrew/abridges_blog) for storing the blog's content. This ensures that your valuable blog posts survive when the container is stopped, restarted, or removed.