UPDATE 1 - May 11 2021: I published the second part, about deploying this to a production server for your personal website. Click here.
I'm currently writing this article from my phone. I'm using the Ghost admin of my website that I installed like a Progressive Web App. It's not a PWA yet, but that will do for now.
This is impeccable. I couldn't do this with Hexo, the previous platform I was using.
Hexo was pretty cool when I only wanted to write 2-3 articles a month. Now that I'm more comfortable writing in public, I want to reach another level.
Since Ghost provides an admin interface with a back-end that are ready for production, you don't actually need to run it in development. I did this to first test the tool out locally.
Therefore, if you simply want to get a blog started as soon as possible, I'll write a subsequent article titled "How I deployed this blog: Running Ghost in production with Docker and Caddy". I will link it here when it's ready.
Requirements
When I use Docker, no matter what the OS I'm on, I use it on Linux, for speed. I believe Docker was meant to improve Developer Experience (DX), and we need fast command execution in the terminal.
So first thing first, get a Linux distro (Ubuntu's great). You can dual boot, use a virtual machine or even replace your current OS.
Just make sure you backup your stuff somewhere else before taking any risks.
For anything else, you need to install those two:
- Docker - at least version 20.10.6
- Docker Compose - at least version 1.29.0
This is a high level tutorial so I'm sorry if you're a beginner, I will just fly over stuff. So I recommend you get to understand the concept of containers, Docker and Docker Compose.
I like to add links to official (or interesting) articles about things I discuss about and I highly recommend you click on them so you may benefit from them as well ;)
Note that you don't install Ghost yourself... not even Nodejs and NPM.
Running the platform locally
With all the requirements in mind. Let's see how easy it is to run Ghost locally.
We simply need two files to get started:
- docker-compose.yml - to easily run it all with one command
- .env - to setup private information about your data and content
The "docker-compose.yml" File
Create a new directory and use it as your base:
mkdir ~/ghost-blogcd ~/ghost-blog
Create a new file - docker-compose.yml:
touch docker-compose.yml
Use your preferred text editor to fill it up with the following content:
version: '3.8' services: ghost: image: ghost:4.2.1-alpine container_name: ghost-dev restart: unless-stopped depends_on: - db ports: - 2368:2368 environment: # see https://ghost.org/docs/config/#configuration-options database__client: mysql database__connection__host: db database__connection__user: ${MYSQL_USER} database__connection__password: ${MYSQL_PASSWORD} database__connection__database: ${MYSQL_DATABASE} url: http://localhost:2368 NODE_ENV: development volumes: - ghost_content:/var/lib/ghost/content db: image: mysql:8 container_name: ghost-dev-db command: mysqld --default-authentication-plugin=mysql_native_password restart: unless-stopped ports: - 3307:3306 environment: # see https://hub.docker.com/_/mysql MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} volumes: - mysql_ghost_data:/var/lib/mysql volumes: mysql_ghost_data: ghost_content:
Explanation
- As you can see, I used the compose file version 3.8.
version: '3.8'
- Then I defined two different services:
ghost- this service will run the official Ghost image on DockerHub as a basedb- this service will run the official MySQL image on DockerHub as a base
services: ghost: ... db: ...
- Finally, I defined two volumes that will be used by those two services:
mysql_ghost_data- which will point to the MySQL filesghost_content- which will be point to theghost/contentdirectory
volumes: mysql_ghost_data: ghost_content:
The "ghost" service
- Within this service, I'm using the alpine variation (creates a lighter image) of Ghost version 4.2.1. Feel free to update it to a more up to date and stable one:
image: ghost:4.2.1-alpine
- I like to be explicit about my container names, so I call this one
ghost-dev:
container_name: ghost-dev
- I want it to only restart if it were explicitly stopped. So in case of failure, it will attempt a restart:
restart: unless-stopped
- I make sure the
dbservice runs successfully first because Ghost needs a storage location for your dynamic data:
depends_on: - db
- This Ghost image runs on port
2368by default, so I open this container port to my computer:
ports: - 2368:2368
- The official Ghost image accepts specific environment variables so it can automatically create the Ghost configuration file for you. They do a good enough job at explaining the necessary variables on the image documentation.
I say "good enough" because it takes two different pages/websites to know which environment variables to use.
I only configure the database when in development:
environment: # see https://ghost.org/docs/config/#configuration-options database__client: mysql database__connection__host: db database__connection__user: ${MYSQL_USER} database__connection__password: ${MYSQL_PASSWORD} database__connection__database: ${MYSQL_DATABASE} url: http://localhost:2368 # contrary to the default mentioned in the linked documentation, this image defaults to NODE_ENV=production (so development mode needs to be explicitly specified if desired) NODE_ENV: development
Note that three of those configuration properties are using environment variables, not direct input. We will be setting those variables in the .env file.
Also note that we're using the name of the db service as the database host so that it can easily access the MySQL default port running under this service.
- Finally, I set a volume so I can easily have access to my Ghost content and so that Ghost can be able to reuse them when the container restarts
volumes: - ghost_content:/var/lib/ghost/content
The "db" service
- Within this service, I'm using the default image of MySQL version 8. Feel free to update it to a more up to date and stable one:
image: mysql:8
- I set my own name for the resulting container:
container_name: ghost-dev-db
- I set a custom command for this image so I can use a different authentication plugin for MySQL. Because this is how Ghost connects to MySQL.
command: mysqld --default-authentication-plugin=mysql_native_password
- I make sure it only stops when it's explicitly stopped:
restart: unless-stopped
- I open the default MySQL port (3306) to my laptop through the port 3307 because on this computer I was already running another MySQL engine on port 3306. This step was not necessary at all, I only wanted to connect with the container database directly so I could understand how Ghost stores data:
ports: - 3307:3306
- I define the required environment variables so MySQL can configure itself:
environment: # see https://hub.docker.com/_/mysql MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD}
Note that here again, we will be using the variables from a .env file to setup a container.
- I setup a volume that points to the MySQL files so that I don't lose my data on container restart
volumes: - mysql_ghost_data:/var/lib/mysql
The ".env" File
I hope that you understand how the Compose file is structured by now. It's expecting a few environment variables. We can pass them to Compose through a .env file that's located in the same directory as the Compose file.
Within the same ~/ghost-blog directory, create a new file:
touch .env
Then fill it up with the following content:
MYSQL_ROOT_PASSWORD=the-password-for-root-userMYSQL_DATABASE=ghostMYSQL_USER=ghostMYSQL_PASSWORD=the-password-for-ghost-user
Make sure to update the values to your own needs.
Running it all
Now that we're all setup, here's how to run Ghost locally:
docker-compose up -d
That's it. It will install MySQL, configure it, then run it, then it will install Ghost, configure it, and run it.
Once it says "done" for both services, you're ready to check out your Ghost blog at http://localhost:2368, and your Ghost admin at http://localhost:2368/ghost so you can configure it.
To stop all services, run
docker-compose stop
Conclusion
I hope that was detailed enough. Here are some ideas of things you can do from there:
- Configure Mailgun SMTP to test out the transactional mail service (user registration and login... etc)
- Make your Compose file more configurable with the .env file