To start with this blog, I think it might be worth talking about this specific project; the website you are currently on.
To start with the basics, the server is hosted behind Cloudflare which I rate highly even just for it's API controlled DNS functionality. The server itself is a small virtual machine in some datacenter halfway across the world. It's quite cheap and as projects go on, I might find some way to take the whole thing "serverless".
I find myself planning on what a page on my new site might contain. I imagine a "Code Snippet" might be pretty cool. A highlight of all of the Docker Compose iac or something? How do I even write a Code Block on Ghost?!
I find myself trying to figure out how to write a code block. This one looks somewhat plain.
Not to worry, it seems as though there's a tutorial I can follow:
https://ghost.org/docs/tutorials/code-syntax-highlighting/
^ This one is just some HTML. It's not even that intuitive.
The guide above says to use 3 backticks. Similar to markdown I guess:
Docker Compose
With that out of the way, lets get into some Docker Compose. I generally will launch most of my projects using Docker. The reason? It's easier to clean up when I don't want it anymore and i change my mind a lot.
Lets say one day, I want to switch a service I'm running. In my example, I don't want to use Keycloak anymore because I find it too overkill for my purposes. I want to swap it out for Authentik (more on this in a later post), can this even be done easily without Docker? Apt/Yum remove? What about left over files...
For this reason, I like to put all of my code into Docker and I use Docker Compose for the deployment. For this site, here is the code:
---
version: '3.1'
services:
ghost:
container_name: ghost
image: ghost:4-alpine
restart: unless-stopped
ports:
- 3008:2368
volumes:
- /opt/docker/ghost:/var/lib/ghost/content
environment:
database__client: mysql
database__connection__host: mariadb
database__connection__user: DATABASE_USERNAME
database__connection__password: DATABASE_PASSWORD
database__connection__database: DATABASE_NAME
mail__transport: SMTP
mail__options__host: MAILSERVER_IP
mail__options__port: 25
mail__options__auth__user: EMAIL_USERNAME
mail__options__auth__pass: EMAIL_PASSWORD
mail__from: Dan Jackson Ghost <EMAIL_ADDRESS>
url: https://danjackson.me
networks:
- MySQL_Connect
- frontend
networks:
MySQL_Connect:
external: true
frontend:
external: true
You might notice above that I've created a bridge network called frontend
and you might wonder why. The answers to that question are found here
I've got another network called MySQL_Connect
. This is specifically to hold the connection to the MySQL database. The network is specified as internal only and is used to allow traffic to transmit between a single database instance on the Docker host. The MySQL container is not connected to any externally facing Docker network by design and therefore can only be accessed by other containers for additional security.
Reverse Proxy
Infront of every good HTTP based Docker system, there is some sort of reverse proxy to direct the traffic. In my case, despite years of using Nginx, I've now switched to Caddy 2 for all of my traffic.
Why?
Painlessly easy for automated SSL.
Don't get me wrong, you can use both Certbot and Acme.sh for SSL certificates on Nginx. Both solutions support DNS validation too. These are both fairly easy but they both lack a certain instant automation. On Caddy 2 however, it couldn't be more easy. You simply add your Cloudflare API key to the Caddyfile and then define a domain
Here is that config:
...I probably don't even need the headers bit.
That's the basis of this Ghost installation. Following the above, I simply point ensure that Cloudflare is pointing at the right server.
Improvements
If you stumble across this and think you want to try this yourself, there are a couple of things with this that can be fixed. For example:
reverse_proxy http://127.0.0.1:3008
There's nothing wrong with this Caddy code, however since both Caddy and Ghost are both in the frontend
network, I don't need to open the port forwarding on the host AND I don't need to specify an IP address here, the container name would simply be enough.
Until next time.