Ghost Custom Themes with Docker

⚠️
This guide is out of date!

After deploying custom themes with custom docker containers for a few years, I recommend against it unless you are already maintaining and monitoring a robust automation and monitoring pipeline.

See this article for a simpler approach to custom themes.

This guide covers a simple approach to deploy ghost with docker and a custom theme. We will use the official ghost themes as a base, but any custom theme will do just fine.

Preparing a dev environment

Preresiquites

  • Get a Docker Hub account
  • Install Docker on your workstation
  • Authenticate your Docker Hub account to your workstation with docker login
  • Install the current LTS version of node.js, preferably using nvm
  • Install the ghost-cli@latest npm package
  • Create a directory for this project's work files. This guide presumes a project directory located at ~/Projects/ghost

Create a local ghost install

From a terminal in your project root, install a local ghost server

# Switch to your project directory
cd ~/Projects/ghost

# Create a folder for the ghost installation
mkdir ghost-local

# Install a local ghost server
ghost install local

Create an admin user

Visit your local ghost installation and create your admin user and password by visiting http://localhost:2369/ghost.

Clone the Ghost Themes repository

Clone the Ghost Themes github repository to your project directory.

# Switch to your project directory
cd ~/Projects/ghost

# Clone the themes repo
git clone git@github.com:TryGhost/Themes.git

Directory structure

Your project folder should now look like this.

For this guide, we'll customize the Journal theme.

Working dev git branch

If this section is gibberish to you, it can be safely ignored.

Create a branch for your customizations. You can rebase your branch with main in the future. By rebasing your branch, you can get all the updates to the official theme over time as Ghost is updated, while preserving your customizations in your own branch. Pull the main branch from the offiaial repo, push your custom branch to your own git repository.

Update the theme's code and see the changes relected live within your local ghost server at http://localhost:2369. This is done by creating symbolic link from your themes repository into the content directory of your local ghost server.

ln -s \
  ./Themes/packages/journal \
  ./ghost-local/content/themes/journal-custom

Once the link is created, activate the custom theme from the ghost admin panel, under Settings > Design > Brand > Themes

Compile the theme during development

While working with your theme files, run the themes package dev target to compile all your assets as you make changes.

# Change to the themes repository
cd ~/Projects/ghost-dev/Themes

# Automatically compile assets when changed
yarn dev

Customize Your Ghost Theme

Actually working with the theme files is outside the scope of this guide. The themes are very friendly and accessible to customize. If you need help, look to the Ghost Theme Documentation to get started.

Deploy with Docker

Build the Dockerfile

When you're ready to deploy your theme, create a dockerfile based on the official ghost container. This dockerfile will copy your custom theme to /opt, and symbolically link it into the ghost content directory. By using a link, instead of copying the theme directly into the content directory, deployments of your container can safely mount docker volumes for the content directory and still get updated theme files with container updates.

FROM ghost:latest

RUN mkdir -p /opt/journal-custom \
 && mkdir -p /var/lib/ghost/content/themes \
 && ln -s /opt/journal-custom \
    /var/lib/ghost/content/themes/journal-custom

COPY ./packages/journal /opt/journal-custom

CMD ["node", "current/index.js"]
~/Projects/ghost-dev/Themes/Dockerfile

Build and publish your container

# Use your own dockerhub user and repo below, of course

# Build the container, add latest and timestamp labels
docker build \
  -t mjac/ghost-custom:latest \
  -t mjac/ghost-custom:$(gdate +%Y-%m-%d_%H%M) \
  .

# Push to container registry
docker push mjac/ghost-custom

Deploy your container with a compose file

version: '3'

services:
  app:
    # image: ghost:latest
    image: mjac/ghost-custom:latest
    restart: always
    ports:
      - "80:2368"
    volumes:
      - content:/var/lib/ghost/content
    environment:
      url: https://mjac.dev

volumes:
  content:
docker-compose.yml

Next steps and thoughts

I would like to improve this workflow.

With this approach, when ghost pushes a new official image, I have to pull the new image, rebuild and republish this custom container. I want to, without needing a jenkins infrastructure, or proprietary github, get the custom theme container rebuilt automatically and published when ghost is updated.

Ghost offers tools for this involving github actions, through jenkins, connecting to the ghost api on your ghost website and uploading a new theme distribution bundle. Their approach is github proprietary, and doesn't please me. I want updates to the theme to be delivered as part of the container, instead of living as data injected into a data volume after the fact.