3 min read
Ghost blog mail configuration in Docker and Kubernetes

This post documents my experiments on configuring something as simple as SMTP mail for the ghost blogging platform using a non cloud email server.

I believed this would be a trivial thing to do, and ultimately it was. However, as always there were very fine details that needed to be configured very precisely for all of this to work.


I have a public blog running ghost that uses a public email service which was working fine. Information about doing this is a simple google search away as this is the most common use case.

I wanted to implement this same functionality in a private network that uses MS Exchange. This was running in a Kubernetes pod via a deployment so I believed it was a simple matter of taking the environment variable configuration from docker-compose and translate it into the correct format for supplying environment variables to the container in kubernetes.

Key takeaways

The following is the configuration that finally worked

        - name: url
          value: https://blog.domain.com
        - name: mail__transport
          value: SMTP
        - name: mail__options__host
        - name: mail__options__port
          value: '25'
Relevant code snippet for configuring ghost mail options in Kubernetes Deployment

However these were the key points you should be very aware of:

  • The configuration option name must be specificed exactly. For the longest time I had missed the mail_transport option entirely while mail__options_host and mail__options_port and varioues other mail__options_* were configured. When I did realize the mail_transport option was missing I has initially made the mistake of naming it mail_options_transport.
  • Kubernetes may not have a way to resolve DNS names you will specific for the mail_options_host parameter and that's why I ended up configuring the IP. This will probably break if the mail service hosted on a different IP in the future.

Debug process I went through

At first I couldn't get this to work in Kubernetes and instead of inspecting the ghost config options and logs within Kubernetes which I haven't done before, I decided to debug the mail configuation from within an ephemeral docker container that I ran using docker run --rm -p 5000:2368 --name ghost ghost. I docker exec -ti ghost bash into the container and attempted to use the ghost shell command under the var/lib/ghostfolder. However, I got feedback that I can't run ghost using the root user. I created a new user with elevated privileges as was pointed out through a link in the error message. However, this led to challenges with permissions on the config.production.json file. Then I attempted to mount a host volume to the ephemeral container via docker run --rm -v $(pwd):/var/lib/ghost -p 5000:2368 --name ghost ghost, but the container would always exit. I didn't figure out why. Finally, I just created a docker-compose file and experimented with providing the relevant environment variabe parameters. The configuration looked like this:

            - 'mail__transport:SMTP'
            - 'mail__options__host:mail.domain.com'
            - 'mail__options__port:25'
            - 'url=https://test.domain.com'
Non-working configuration on private network. Works in public network setting.

After multiple configuration changes and attempts to send test mails via the Ghost > Labs > Send Test Mail functionality, I noticed that the url parameter had an = between the key and the value. I then did the same for mail parameter like so:

            - 'mail__transport=SMTP'
            - 'mail__options__host=mail.domain.com'
            - 'mail__options__port=25'
            - 'url=https://test.domain.com'

And suddenly the mail started going through. So it was just a matter of using = rather than :. Funny enough, the configuration with : works fine when using a public mail service but not for when using a private email service in a private network.


The intent of this post was to help others who might experience the same issue and also to give an insight in to the debug process that I followed. Hopefully this can lead to improvements in how I debug such problems in future this saving time.