Ghost blog mail configuration in Docker and Kubernetes

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.

Context

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

        env:
        - name: url
          value: https://blog.domain.com
        - name: mail__transport
          value: SMTP
        - name: mail__options__host
          value: 99.99.99.99
        - 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:

        environment:
            - '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:

        environment:
            - '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.

Conclusion

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.