Running Ghost in a WSL2 Docker container on Windows Server 2022

This will set up Ubuntu on Windows server 2022 with Docker running in WSL2, configured to host Linux apps with IIS acting as a reverse proxy and using a SSL. Then the Ghost docker container can be set up. In particular this shows how to fix the Too Many Redirects error that comes from using a https domain in Ghost from behind a reverse proxy.

Server Configuration

To use IIS as a reverse proxy to the WSL2 containers install Application Request Routing 3.0 using the Web Platform Installer. The URL Rewrite feature should also be installed if it isn't already. See:

Reverse-Proxying Node.js Apps on Windows with IIS
You can run Node.js apps on Windows with the added layer of a reverse-proxy with the built-in web service manager IIS. Together with a process manager like PM2, it’s a viable strategy to run apps for production. Why use a reverse proxy?Having a ded...
Reverse Proxy with URL Rewrite v2 and Application Request Routing
This walkthrough will guide you through how to use URL Rewrite Module and Application Request Routing (ARR) to implement a reverse proxy server for multiple...

Enable ARR under the server's IIS Home by selecting Application Request Routing Cache, then Server Proxy Settings... Check "Enable proxy", HTTP version: "Pass through", "Reverse rewrite host in response header" and the header "X-Forwarded-For" should be listed for the Custom Headers.

WSL2 and Docker need to be installed. Follow the guide and related links here: https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-containers

Ghost Installation

The instructions below use the official Docker image for Ghost: https://hub.docker.com/_/ghost/

Create a folder in Ubuntu home for the docker compose YAML file. I named it scripts. In the new folder create a file called ghost-stack.yml.

version: '3.1'

services:

  ghost:
    image: ghost:latest
    restart: always
    ports:
      - 3001:2368
    environment:
      # see https://ghost.org/docs/config/#configuration-options
      database__client: mysql
      database__connection__host: db
      database__connection__user: root
      database__connection__password: ABCDEF123456
      database__connection__database: ghost
      # this url value is just an example, and is likely wrong for your environment!
      url: https://example.com
      # contrary to the default mentioned in the linked documentation, this image defaults to NODE_ENV=production (so development mode needs to be explicitly specified if desired)
      #NODE_ENV: development
    volumes:
      - /home/ghost/content:/var/lib/ghost/content

  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ABCDEF123456
    volumes:
      - /home/ghost/mysql:/var/lib/mysql

You can change the port number to something other than 3001, change the url to you domain, and use a unique password for mysql.

From within the scripts folder run:
docker-compose -f ghost-stack.yml up

IIS Configuration

Create a folder in your IIS Sites for the Ghost site, for example examplecom, this will only have one file.

In IIS create a new website, pointing to examplecom. Bind your domain name, for example example.com bound to port 80 of an available IP address.

Go into the site's settings and select "URL Rewrite", then on the right menu select "View Server Variables". Select "Add..." and enter HTTP_X_FORWARDED_PROTO. Without sending this header variable you will get a "Too Many Redirects" error when using https for the domain name.

In the examplecom folder for the IIS site add a web.config file:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <clear />
                <rule name="HTTP Redirect" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTPS}" pattern="^OFF$" />
                    </conditions>
                    <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" appendQueryString="false" />
                </rule>
                <rule name="JSGhost" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
                    <action type="Rewrite" url="http://localhost:3001/{R:1}" />
                    <serverVariables>
                        <set name="HTTP_X_FORWARDED_PROTO" value="https" />
                    </serverVariables>
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

This forwards any non-https traffic to https, and it configures the reverse proxy that sends all of the traffic to the IIS site to the Ghost docker image, while setting the header that tells Ghost that the HTTPS has already been handled. Change the port number if you used something other than 3001.

Set up a SSL certificate for the IIS site with the method of your choice, like win-acme: https://www.win-acme.com/

You should now be able to go to example.com and see the Ghost site. The admin URL will be example.com/ghost where you can configure your password and other site settings.

If you need to change any of the docker compose settings make sure to stop the containers first, then edit the yaml file, then restart the containers.