In this post I will share my experiences with upgrading our d&b blog to Ghost 1.8.7 in one of our testing environments - based on Ghost's latest official Docker image:
- Upgrade Scenario
- Key Takeaways
- Sample Files
- Further Information
1. Upgrade Scenario
- This blog always was (before and after) based on the official Docker image for Ghost:
- So the upgrade is from 0.11.11 to 1.8.7
- For the lack of better knowledge when starting to use Ghost, our first steps started (unknowingly) in
- So I had (as probably many others) the confusion when switching to
productionwith missing/switching paths (
- Thus the container used to have two
- So I had (as probably many others) the confusion when switching to
- Our blog used the
USER userinstruction in its Dockerfile.
- Keep using SQLite as database also for version 1.x which still is the default for the docker-based setup and does not require additional steps.
I used the official Migration guide. It is well explained and also mostly applicable when setting it up in a container-based environment.
The most important things to note before starting it I will summarize in the next chapter. After that are basic sample files to illustrate the chosen configs.
3. Key Takeaways:
Here are the most important things to keep in mind:
Export/Import ... No backup restore
Besides your image directory you cannot use restored backups of the
docker volumes ... you need to export your blog in the Ghost web UI to a JSON file and import it to the new container.
Node_ENV now is
production ... not
In contrast to the previous versions 0.x which started in
development mode (
NODE_ENV), the new versions of Ghost 1.0.0 and above do start in
production mode by default. So in our case we now need only one volume for the Ghost
content (depending on how you handle config files ... see below).
IMPORTANT: The path in the container has changed from
/var/lib/ghost to now
/var/lib/ghost/content ... you might be tempted to leave it the old way (e.g. in order to have the
config.production.json within the volume) ... DON'T do that:
- In my experiences Ghost did not find the paths to your images in your blog posts
- More importantly Ghost re-initiated the initial setup (each time) after a removal and restart of the docker container/docker stack. I'm not sure how this is related but it happened to me always with the old path and reliably did not happen when using the new path.
- The new path explicitly is marked as Breaking Change in Ghost's Docker Hub repo description.
How to persist the config file
Despite being no longer in the
docker volume in order to maintain your individual config across restarts (e.g. a URL with sub-domains or paths after the host part) you can come up with something rather unconventional like
# Dockerfile <...> RUN sed -i "s|localhost:2368\/|yourDomain.com\/yourPath\/|g" config.production.json <...>
or use something more appropriate such as the newly introduced
docker configs (Docker version 17.06+):
# docker-compose.yml (full sample below) <...> configs: - source: config_production target: /var/lib/ghost/config.production.json uid: "1000" gid: "1000" mode: 0440 <...>
docker config work pretty much as
docker secrests do. Full files can be found at the end of this post.
You might as well just stick with "old school plane jane"
docker and use instead a simple
COPY instruction in your Dockerfile.
The previous versions' user called
user has been dropped. But you can now use the user
node from the
NodeJS base image by adding a simple
USER node in your
This (repeatedly) worked out of the box and no (more) need for crazy
RUN chown -R ... statements.
The actual import
The actual import of the previously exported JSON worked pretty reliable every time I did it:
Users, posts and settings
When you take identical credentials in the new Ghost's setup process Ghost does not import that user (and gives a warning) but the re-imported posts are automatically assigned to you. This at least worked if I left the initial default Ghost blog posts, imported the JSON and then only after that deleted the default ghost stuff (stories, user, tags, etc.).
Otherwise (all users imported and all posts assigned as before) imported users are locked on import and have to reclaim access via the email-based password reset mechanism.
code injection and other
genereal settings were nicely imported and directly working after the import.
After the JSON import you still need to
<your_whatever_old_base_path>/content/images to now
/var/lib/ghost/content/images in your new
docker volume (so copy from where your images really are ... this is one of the parts with Ghost's 0.x versions where initially starting in
development and then switching to
production used to be also a switch from
But now everything works as expected: Even a
docker cp /path/on/host <container_id>:/path/in/container/ worked automatically with correct file permissions ... even for the user
Default theme Casper
As expected the old default theme Casper was not imported (this gave another warning). You can stick with the default new Casper theme or download version 1.4 which is the latest version optimized for Ghost 1 and above:
- Importing and successfully activating it reliably worked every time.
- You may need to (re-)upload your cover, logo and the new publication icon.
- This publication icon e.g. sets the favicon for your blog.
After that your blog should be up and running again.
Worth a note: The official Docker image still uses SQlite for data persistence. I did not change it.
Editing and Handling
New editor is nice (but on the other hand did not blow me away either) and tidied up. You can now toggle between full-screen and side-by-side mode. And you do have a built-in spell-checker (which works great and is a major add-on but which ironically did not recognize the word "blog" ;-> ).
Some shortcuts and markdown have changed:
- On Mac OS I could not use the shortcut for
inline codeas it is already occupied by Safari
- Headings now need to be
##Heading... the old markdown
##Heading##in the new version is still rendered as heading but also reveals the hash tags on the right side of the heading## content.
- The are some others mentioned as "Markdown breaking changes" in the migration guide
4. Sample Files
In the following some sample files for how you could do it ... they should be working but should also be considered only as a basic starting point of your container configuration:
Dockerfile Example (basic)
FROM ghost:latest RUN apt-get update RUN apt-get autoremove -y \ && apt-get autoclean -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* # COPY or RUN sed your config if needed # or just take the docker configs as below USER node # NODE_ENV already set to "production" by default
Docker-Compose.yml Example (basic)
version: "3.3" services: <ghost_service>: <...> volumes: - <docker_volume_name>:/var/lib/ghost/content <...> configs: - source: config_production target: /var/lib/ghost/config.production.json uid: "1000" gid: "1000" mode: 0440 environment: - NODE_ENV=production # already set ... but just to be sure. <...> volumes: <docker_volume_name>: external: true configs: config_production: file: ./local/path/to/file/config.production.json <...>
Go ahead ... give it a try ... the beauty about Docker is that you can easily test it (several times) in one of your testing or development environments and then optimize the steps necessary and move to production.
I will update this post as soon as I have finally moved also within the
production environment if there have been other effects not mentioned here yet.
6. Further Information
Ghost Docs & Docker Hub:
Ghost Markdown Guide
Casper Theme 1.4:
Ghost & NodeJS official Dockerfiles:
Docker Configs Example: