Set Up Ghost Blog In Docker

This blog is self-hosted Ghost blog in Docker container on my secondary home server. It is pretty easy to set up I will show you how in this post. You can set it up on any platform that has Docker support (Linux distro like Ubuntu is preferred). Repository is here.

One Bash Script To Run Them All

I have a simple bash script called run to run the blog in Docker (Docker should be installed):

set -e

docker pull ghost:2-alpine
docker stop blog || true
docker rm blog || true
docker run --name=blog -p 2368:2368 \
	-v ${PWD}/content:/var/lib/ghost/content \
	-e url= \
	-e mail__from="'Ghost Blog' <>" \
	-e mail__transport=SMTP \
	-e mail__options__host= \
	-e mail__options__port=25 \
	--restart=always \
	-d ghost:2-alpine

Changes to be made:

  • Variable url should be omitted when first setting up the blog, because you need to set up admin account first. When everything is set up change it from to your blog location.
  • Change in variable mail__from to your domain which have configured SMTP server (see section below) - so that email to admin mail account will not be marked as spam. This variable can be omitted if you don't want to use your own SMTP server.
  • Change variable mail__options__host from (Docker host from container perspective) to your configured SMTP server (see section below) location (if it is not running on the same machine). This variable can be omitted if you don't want to use your own SMTP server.
  • Change variable mail__options__port from 25 (default SMTP port) to your configured SMTP server listening port. This variable can be omitted if you don't want to use your own SMTP server.

Setting Up Send-Only Postfix In Docker (optional)

Use my Mail repository to set it up fast. See my other post about setting it up here. Be aware that you will need to add TXT record for DKIM in your DNS. In the end you should have DKIM and Postfix configured and running in Docker. This step is optional, it is needed to make sure your mail is not marked as spam and reaches you at all.

Setting Up nginx Reverse-Proxy

I have nginx reverse-proxy (in Docker if you wondered) on my main server that reverse-proxy to my secondary server IP:2368. Setting it up is pretty easy, maybe I will soon write full guide on how to use my Docker-Webserver repository, but here are the steps (I assume you cloned the repository and now in its root).

1. Generate dhparam for your blog more secure TLS:

openssl dhparam -out dhparams/example-com.pem 2048

2. Create empty folder in html for your blog:

mkdir html/

3. Create basic configuration for HTTP version of your blog in sites-enabled (nano sites-enabled/

server {
    listen 80;
    server_tokens off;
    sendfile on;
    location ^~ /.well-known/acme-challenge/ {
        alias /usr/share/nginx/html/;
    location / {
        return 301$request_uri;

4. Run webserver and get LetsEncrypt certificates for your domain:

sudo ./run

# In case you did not have it installed (Ubuntu)
sudo apt update
sudo apt install certbot

sudo certbot certonly --webroot -w /home/user/webserver/html/ -d

5. Create configuration for HTTPS version of your blog in sites-enabled (nano sites-enabled/ Change to location of the server with running Ghost in Docker (if it is running on the same host - use

server {
    access_log off;
    error_log /dev/null crit;
    sendfile on;

    listen 443 ssl http2;
    server_tokens off;

    ssl_certificate /etc/letsencrypt/live/;
    ssl_certificate_key /etc/letsencrypt/live/;

### Improved security of HTTPS
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_dhparam /dhparams/example-com.pem;

### Tweaks for performance and smooth experience
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

### Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Frame-Options "SAMEORIGIN";
    add_header Referrer-Policy "strict-origin-when-cross-origin";

### Timeouts to prevent abuse
    client_body_timeout 10s;
    client_header_timeout 10s;

    client_body_buffer_size 2M;
    client_max_body_size 2m;

    location / {
        proxy_redirect     off;
        proxy_set_header   Host                 $host;
        proxy_set_header   X-Real-IP            $remote_addr;
        proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto    $scheme;


6. Rerun webserver again:

sudo ./run

After that it should be accessible from outside. Make sure you have forwarding from router 80 and 443 ports to your server with Docker-Webserver.

Happy Experimenting!

comments powered by Disqus