/ Docker

How to host a multilingual Ghost blog with Docker and nginx

Ghost is a great blogging platform. It offers me everything I need for blogging and it is simple and easy to use. Unfortunately, there is currently no possibility to use the blog multilingual from scratch. Especially if you want to address a wider audience.

Update: Since Ghost 2.0 was published you can use an alternative approach for managing multiple languages. This example shows you how you can divide your articles into different languages using tags: https://help.ghost.org/article/112-multi-language-content

Update: The original article was about multilingual Ghost blogs with docker and Rancher. But since I don't use Rancher anymore this article is now about native Docker and nginx.

In general, you also have the opportunity to write a post in another language. Unfortunately, the result is a page with mixed language content. The post text will then have another language than the menu and the footer. This will cause confusion for some users. Maybe even search engines then have problems to classify the content language correctly.

On this blog I've set up a few static content pages. I think it would be practically if I could translate all contents of my page into a second language. So I decided to start an experiment with this page. As you can see I link to the German blog in my menu and this post is hosted under /en.

Since I run my Ghostblog with Docker and nginx I've got the idea to just start a second instance of Ghost to host all the English contents. Since Ghost is very lightweight and has only a few configuration options, nothing speaks against this approach.

However, I would not do that with many other systems, since maintenance and configuration of such a setup could then become confusing and time-consuming. Even if additional languages will be added, this setup is no longer recommended.

1. Configure the nginx proxy container

For my setup I will use this nginx-proxy container maintained by jwilder in combination with the docker-letsencrypt-nginx-proxy-companion.

On your server create a new folder containing all the nginx configuration and the following subfolders:

  • nginx
    • certs
    • html
    • vhosts

Inside vhosts create a new configuration file. Use your domain name (yourdomain.com) as filename:


## Start of configuration add by letsencrypt container
location ^~ /.well-known/acme-challenge/ {
    auth_basic off;
    allow all;
    root /usr/share/nginx/html;
    try_files $uri =404;
    break;
}
## End of configuration add by letsencrypt container

location ^~ /en {
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $host;
    proxy_pass http://ghost-en$1;
}

The first part of the file will be auto generated by the letsencrypt container later. The second part is important. It tells nginx to route all incoming traffic from /en to the ghost-en host which will be created later.

2. Start the nginx proxy container with Letsencrypt

Inside your nginx folder create a docker-run.sh file with the following contents:

#!/bin/sh

# Run nginx proxy
docker run -d -p 80:80 -p 443:443 \
    --name nginx-proxy \
    -v /home/user/nginx/certs:/etc/nginx/certs:ro \
    -v /home/user/nginx/vhosts:/etc/nginx/vhost.d \
    -v /home/user/nginx/html:/usr/share/nginx/html \
    -v /var/run/docker.sock:/tmp/docker.sock:ro \
    --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy \
    --restart unless-stopped \
    jwilder/nginx-proxy

# Run letsencrypt companion
docker run -d \
    --name letsencrypt \
    -v /home/user/nginx/certs:/etc/nginx/certs:rw \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    --volumes-from nginx-proxy \
    --restart unless-stopped \
    jrcs/letsencrypt-nginx-proxy-companion

Do not forget to alter the volume paths. Then add executive permissions to that file chmod +X docker-run.sh and run it using ./docker-run.sh

You should now see both containers running if you type docker ps

3. Start two ghost containers using docker run

Create a new folder on your server that will contain all the configuration and data for your ghost instances. For example place it beside the nginx folder. Also create a folder called content and a second folder called content-en inside the new folder. Also create a new file called docker-run.sh and place the following contents in it:

#!/bin/sh

# start the first container
docker run -d --name ghost \
    -v /home/user/ghost/content:/var/lib/ghost/content \
    -e "url=https://yourdomain.com" \
    -e "VIRTUAL_HOST=yourdomain.com,www.yourdomain.com" \
    -e "LETSENCRYPT_HOST=YOURDOMAIN.de,www.yourdomain.com" \
    -e "LETSENCRYPT_EMAIL=info@yourdomain.com" \
    --restart unless-stopped \
    ghost:2-alpine

# start the second container
docker run -d --name ghost-en \
    -v /home/USER/ghost/content-en:/var/lib/ghost/content \
    -e "VIRTUAL_HOST=ghost-en" \
    -e "url=http://yourdomain.com/en" \
    --restart unless-stopped \
    ghost:2-alpine

Do not forget to alter the yourdomain.com strings, your email and the volume paths. Run this file like in the step before and start the two new ghost instances. Do you notices the VIRTUAL_HOST environment variable inside the second run command? This is the virtual host that was placed inside the nginx configuration. This is the way the second container will receive every /en request.

You should now have two independent ghost instances. One running at https://yourdomain.com and one running at https://yourdomain.com/en.

4. Configure your new instance

  • Open the backends of both instances. In each instance go to the general settings and change the publication languages as you like.
  • Apply all ghost settings from the first instance and copy your theme
  • Link the menus of each instance to each other
  • Translate the static content and the posts you want to have in another language

The Hreflang attribute

You can inject custom code into your posts header. We can use this function to add the Hreflang attribute. This will allow search engines to index the alternative content for the new language. Take a look at the source code of this page. Example:

<link rel="alternate" href="https://steampixel.de/wie-du-einen-mehrsprachigen-ghost-blog-mit-docker-und-nginx-aufsetzt/" hreflang="de-de" />

This article is also available in german

How to host a multilingual Ghost blog with Docker and nginx
Share this