In my first post I said I installed Ghost with ghost-cli
, the classic way. I did also say that I wanted to run it in Docker but that I didn’t know Docker enough to do it.
In fact, I tried to set up Ghost in Docker a few times while being bored at school, but I didn’t succeed, so it ended up like it is now.
For the past week though, I’ve been learning and using Docker a lot, and finally moved a dozen services into containers.
And I succeeded! It was really simple when I understood what I was doing.
ghost-cli
, the software to manage your Ghost installation, is great and it’s way simpler to install Ghost that it was a few years ago. However, it’s still not completely straightforward and I has some issues with permissions.
Also, managing Node.js versions and NPM modules on your server can be a little messy, whereas right now everything is in my Docker container and I can trash it and rebuild it whenever I want while keeping my server clean (that’s the point of Docker).
Another advantage is that it is way easier to use a SQlite database, as ghost-cli
requires MySQL
Backup
Save your /ghost/content
folder and /ghost/config.production.json
file.
If you’re using a MySQL database, make a dump.
You can now stop the Ghost service to free the port.
Docker Compose
We’ll use docker-compose to manager our Ghost container using a simple Yaml file.
Here is the one I use:
version: "3.1"
services:
ghost:
container_name: ghost
image: ghost:1.21.3-alpine
restart: always
ports:
- 127.0.0.1:2368:2368
volumes:
- ./content:/var/lib/ghost/content
- ./config.production.json:/var/lib/ghost/config.production.json
- We use the offcial Alpine image (change the version)
- We bind the port 2368 of the container to 127.0.0.1:2368 on our host
- We mount our content folder that we previously backed up
- We mount our configuration file that we previsouly backed up
Example if you use MySQL:
version: "3.1"
services:
mysql:
container_name: ghost_mysql
image: mariadb:10.3
restart: always
environment:
MYSQL_ROOT_PASSWORD: example
ghost:
container_name: ghost
image: ghost:1.21.3-alpine
restart: always
depends_on: mysql
ports:
- 127.0.0.1:2368:2368
volumes:
- ./content:/var/lib/ghost/content
- ./config.production.json:/var/lib/ghost/config.production.json
environment:
database__client: mysql
database__connection__host: mysql
database__connection__user: root
database__connection__password: example
database__connection__database: ghost
Make sure the content
folder and config.production.json
are in your present directory.
Then apply the correct permissions:
chown -R 1000:1000 content/ config.production.json
1000
being the UID and GID of the ghost user inside the container.
Now you can run:
docker-compose up -d
And your container will come to life.
If you want to import your MySQL dump:
docker exec -i ghost_mysql mysql -u root -p ghost < dump.sql
Updating Ghost
It won’t be a pain anymore!
Change your version number in your docker-compose.yml
.
Fetch the latest images:
docker-compose pull
And restart the containers if needed:
docker-compose up -d
… that’s all.
Reverse proxy
You can use a reverse proxy the exact same way as you were before, without modifying a single file.
For you information, this is ~what I use:
server {
listen 80;
listen [::]:80;
server_name stanislas.blog www.stanislas.blog;
return 301 https://stanislas.blog$request_uri;
access_log /dev/null;
error_log /dev/null;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name stanislas.blog www.stanislas.blog;
if ($host = www.stanislas.blog) {
return 301 https://stanislas.blog$request_uri;
}
access_log /var/log/nginx/ghost-access.log;
error_log /var/log/nginx/ghost-error.log;
ssl_certificate /etc/nginx/https/fullchain.pem;
ssl_certificate_key /etc/nginx/https/key.pem;
ssl_protocols TLSv1.2;
ssl_ecdh_curve X25519:P-521:P-384:P-256;
ssl_ciphers EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
resolver_timeout 5s;
ssl_session_cache shared:SSL:10m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
location / {
proxy_set_header Host $http_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;
proxy_pass http://127.0.0.1:2368;
}
}
The best setup being having Nginx in a container and adding it to your docker-compose stack.
Enjoy
I’m really enjoying my new Ghost Docker container! It’s still early to give you feedback on how it runs in the long-term, but so far it has been incredibly easy to move my Ghost website to Docker, even more so because I use SQLite! I’m glad I don’t have to deal with NPM anymore.