key lock

After having many challenges with my previous hosting provider and spending much time coding the entire blog engine from scratch, I decided to give Ghost a try. This time I wanted to host everything on AWS, use Docker... you know, everything latest and greatest :)

Running Ghost within Docker container

I followed this article on how to install Ghost: https://aws.amazon.com/blogs/compute/building-a-photo-diary-ghost-on-amazon-lightsail/

As mentioned in that article, you can choose between Ghost Blueprint or running the Ghost within Docker container.  Since the Docker alternative offers painless upgrades to new Ghost versions, the choice was pretty obvious.

I installed Docker, Ghost, set up a custom domain...

The next thing was to execute the following command and have my blog up and running:

sudo docker run -d --name blog -e url=http://dejan.blog -p 80:2368 --restart unless-stopped -v /var/lib/ghost/content:/var/lib/ghost/content ghost
NOTE: the article doesn't mention the --restart unless-stopped parameter. I noticed my blog was unavailable the day after. So I had to remove the container first by executing sudo docker rm blog and specifying the restart parameter when running a new container.

Ordering an SSL certificate from Let's Encrypt

My blog was running fine on http://dejan.blog

The next step was to order an SSL certificate from https://letsencrypt.org/ and set it up on my server.

I followed steps 1-6 from this article: https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-using-lets-encrypt-certificates-with-nginx

Since my Linux machine didn't come with Bitnami tools and Nginx, I had to set it up myself.

Setting up Nginx

Nginx will serve as a reverse-proxy and forward all traffic from http://dejan.blog and https://dejan.blog to Docker. It will also be responsible for HTTP to HTTPS redirection.

There are several ways to install Nginx. I chose to install it directly on my virtual machine and not use a Docker image.

sudo apt-get install nginx

Nginx runs on port 80 by default, and you'll get an error when you run the Nginx service because that port is already in use. To see who's using port 80, you can run the following command:

sudo lsof -i:80

The output may look like this:

COMMAND     PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 21681 root    4u  IPv6 481234      0t0  TCP *:http-alt (LISTEN)

Since port 80 is used by Docker, we have to reconfigure our Docker container to use another port so that Docker and Nginx service can run side-by-side. I chose port 8080.

Docker containers cannot be reconfigured on the fly; you have to remove the existing one first:

sudo docker rm blog

And create a new one like this:

sudo docker run -d --name blog -e url=https://dejan.blog -p 8080:2368 --restart unless-stopped -v /var/lib/ghost/content:/var/lib/ghost/content ghost
NOTE: I've changed the URL and port number.

Configuring Nginx

Open the config file by executing the following command:

sudo nano /etc/nginx/nginx.conf

And add the following lines to ngingx.conf:

server {
  listen 443 ssl;
  server_name dejan.blog;
  ssl_certificate "/etc/letsencrypt/live/dejan.blog/fullchain.pem";
  ssl_certificate_key "/etc/letsencrypt/live/dejan.blog/privkey.pem";
  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    proxy_pass http://localhost:8080;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

server {
  listen 80;
  server_name dejan.blog;
  return 301 https://dejan.blog$request_uri;
}
NOTE: Make sure to replace dejan.blog with your domain name

The first server block tells Nginx to listen for incoming traffic for dejan.blog on port 443 and pass everything to http://localhost:8080 (Docker container takes it from there and re-routes everything to port 2368).

The second server block is a simple redirect from HTTP to HTTPS.

Save changes and restart the Nginx service.

Renewing the SSL certificate

The SSL certificate will expire after 90 days. To renew it, you can use the following command:

DOMAIN=dejan.blog
WILDCARD=*.$DOMAIN

sudo certbot -d $DOMAIN -d $WILDCARD --manual --preferred-challenges dns certonly


## sudo certbot certonly --standalone -d $DOMAIN -d $WILDCARD
## sudo certbot renew

And simply restart the Nginx service after that.

Some useful commands

Restart Nginx service:

sudo systemctl restart nginx.service

Status of Nginx service:

sudo systemctl status nginx.service

Verify that website is reachable from outside:

curl https://dejan.blog

Very that redirect works as well:

curl http://dejan.blog

Verify that Ghost is running:

curl http://localhost:8080