Building a blog site with OCI, Docker, Ghost and Caddy
After building my kids a Minecraft server on Oracle Cloud Infrastructure using this article by Todd Sharp: How To Set Up and Run a (Really Powerful) Free Minecraft Server in the Cloud. I saw that he had another article Stand Up A Free Blog In 15 Minutes With Ghost In The Oracle Cloud. Let me tell you before you follow a HowTo guide make sure you review the date posted and check if the software has been updated. It did not take 15 minutes!
After following the basic instructions and set up. I found out that the latest Ghost version had changed and in order to debug Docker you need more than the basics from this article. In addition, Caddy with Docker network was also a challenge that 15 minutes did not cover. I spent a quite a few hours all in all over a weekend getting this site up and running, yes the site you are viewing right now!
First of all the instructions are really good but Ghost now tries to force you to use MySQL instead of SQLite. In addition, Docker Networks need to be set to allow Docker to utilize the open ports for 80/443. These specifics in addition to adding Docker Compose to the recipe really caused issues.
Docker/Docker Compose, Ghost and Caddy
Oracle Cloud "Always Free"
Feel free to follow all of Todd's instruction through installing Docker. However one difference that is important is if you use the "always free" account you will probably run into an issue when trying to provision the server, something like "out of host capacity". The solution to this, I found via Reddit, was to make a PAYG (Pay as you go) account. So give them a credit card, but keep the specs at the "always free" tier. The limits according to Todd are: 4 core and 24gb of RAM. For my server, which is a Minecraft and web server its is configured as a 2core 8gb instance. If you are only doing a web server you can probably just do 1core 1gb and use the rest for other instances. You can also create a budget that you can set to $1 or whatever to alert you if your instance starts to cost $. So far I have had no issues running a Minecraft server and this web server and its been "free".
Install the Docker Compose Plugin
We will also install Docker compose as this will allow us to use a single YAML file to hold all our configurations. As I was "debugging" I found continually writing a single line of Docker "run" commands became tedious and Docker Compose allowed me to use nano to edit the "docker_compose.yml" file and add all my configurations for both Ghost and Caddy.
Installing Docker Compose is super simple on Linux. It's a simple yum install or apt-get whatever you have installed.
$ sudo yum update
$ sudo yum install docker-compose-plugin
Now you can create a "docker_compose.yml" file. I put my file in a manually created "caddy" directory. For my set up, I manually create a "ghost" folder and a sub-folder for "caddy" then using nano I created an empty "Caddyfile"(no extention) in the caddy folder and an empty "docker_compose.yml" in the "ghost" directory. You may need to run chmod on the file or folder if you have any permission issues.
$ sudo mkdir /home/opc/ghost
$ mkdir /home/opc/ghost/caddy
$ cd /home/opc/ghost/caddy
$ nano Caddyfile
$ cd /home/opc/ghost
$ nano docker_compose.yml
Using nano again I add the following to the "docker_compose.yml" file. The network configs are necessary for Docker containers to communicate with the servers network.
networks:
web:
external: true
internal:
external: false
driver: bridge
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /home/opc/ghost/caddy/Caddyfile:/etc/caddy/Caddyfile
networks:
- web
- internal
I used this article, it really helped me understand Docker compose and how it works with Ghost and Caddy. However when it has you create the Docker network, do not create the internal one as this network will automatically get created when you run the compose file. If you do create it, it may cause issues as it did for me and I had to delete the network to get anything to work.
#create the web network
$ docker network create web
#to see the newly created network, use list networks
$ docker network ls
#use this to inspect the network you created
$ docker network inspect <network id or name>
Ghost Blog
The article has you add Ghost configs but its not clear where or if you need another docker compose file. You should add the directives to the same compose file, so to be clear we will only be using a single compose file for Ghost and Caddy. I did not use the Caddy data or config options. In addition, I used Ghost:alpine which is a minimal package version of Ghost, you may have different needs but check out the Ghost documentation to read more. In the below code, I added an email SMTP config otherwise you will not be able to use the subscribe and sign up features. I modified the config to use GMAIL, if you also want to use GMAIL you must get an App Specific Password from Google. You cannot just add your gmail password for obvious security reasons. You can read more about that at Google Help.
#add to the Docker compose yml
ghost:
image: ghost:alpine
restart: unless-stopped
environment:
- url=https://www.myghostdomain.com # Change to your domain
- mail__transport=SMTP
- mail__options__host=smtp.google.com # Find out from provider
- mail__options__port=587 # Find out from provider
- mail__options__auth__user=youremail@gmail.com
- mail__options__auth__pass=YOUR_GMAIL_APP_Pass
- mail__from=Admin <youremail@gmail.com>
# database config
- database__client=sqlite3
- database__connection__filename=/var/lib/ghost/content/data/ghost.db
volumes:
- /home/opc/ghost/content:/var/lib/ghost/content
networks:
- internal
Addition details about Ghost install with Docker can be found in the Ghost Developer Documentation. A note on the database config. Ghost no longer recommend SQLite for a production environment. In a dev environment you can leave out the database line and add "NODE_ENV=development" and it will automatically use SQLite. That said MySQL is overkill for my little blog and I didnt want yet another complication for a simple site. So I remove the Node environment config and it will default to "production" but then I tell it to use SQLite3 and give it the destination. This destination is virtual as the volume maps your local ghost files to that virtual location. If you look in your ghost/data folder after build you will see the ghost.db file. Feel free to set up MySql if that fits your needs better.
Configuring Caddy
Open the Caddyfile you created earlier with nano and paste or type in the following paying attention to use the specifics for your site.
{
# Global options block. Entirely optional, https is on by default
# Optional email key for lets encrypt
email youremail@domain.com
# Optional staging lets encrypt for testing. Comment out for production.
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
www.myghostdomain.com {
reverse_proxy ghost:2368
}
mydomain.com {
redir https://www.myghostdomain.com{uri}
}
Your Domain
If you are using OCI you will have a server IP. If you will be using a domain name, make sure to point your domain to the server IP. You will also need to open the ports 80 and 443 in both the OCI admin tool and in the server firewall. Please use the directions in Todds article. Its fairly straight forward, using OCI add and ingress rule for 80,443 and then use the below commands on the server.
$ sudo firewall-cmd --permanent --zone=public --add-port=80/tcp
$ sudo firewall-cmd --permanent --zone=public --add-port=443/tcp
$ sudo firewall-cmd --reload
Verifying files
You should now have a /home/opc/ghost directory with a docker_compose.yml, see example below. In YAML spaces and tabs matter so if there are issues run your file through a yaml validator.
#docker_compose.yml
networks:
web:
external: true
internal:
external: false
driver: bridge
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /home/opc/ghost/caddy/Caddyfile:/etc/caddy/Caddyfile
# - /data/caddy/data:/data # Optional
# - /data/caddy/config:/config # Optional
networks:
- web
- internal
ghost:
image: ghost:alpine
restart: unless-stopped
environment:
- url=https://www.myghostdomain.com # Change to your domain
- mail__transport=SMTP
- mail__options__host=smtp.google.com # Find out from provider
- mail__options__port=587 # Find out from provider
- mail__options__auth__user=yourlogin@yourprovider.com
- mail__options__auth__pass=YOUR_GMAIL_APP_Pass
- mail__from=Admin <youremail@gmail.com>
volumes:
- /data/myghostapp:/var/lib/ghost/content
networks:
- internal
You should also have a /home/opc/ghost/caddy directory with a Caddyfile.
{
# Global options block. Entirely optional, https is on by default
# Optional email key for lets encrypt
email youremail@domain.com
# Optional staging lets encrypt for testing. Comment out for production.
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
www.myghostdomain.com {
reverse_proxy ghost:2368
}
mydomain.com {
redir https://www.myghostdomain.com{uri}
}
Starting the web server/docker container
Navigate to the ghost directory and run your compose file. Please note that the version of docker compose matters as older versions used "docker-compose up -d" and the new version uses the below, so just check yours.
$ docker compose up -d
Congratulations!? If everything goes well you should be able to put your ip or domain in a browser and see your new Ghost blog. It will also show a valid SSL cert, if it doesnt immediately just give it a few minutes to populate. You can fine themes at Ghost.org use the Ghost console at "yourghostsite.com/ghost" to set up your admin account etc. You can upload themes there. If you want to customize your theme. Make a copy of the base theme then edit the handlebars and CSS (screen.css) files. After making changes you have to reload the theme, I just switch themes in the admin console to get it to reload. In addition, there is an area for CODE insertion in the admin console where you can add simple CSS/JS additions globally.
Good Luck, if you're like me you will need it! http://www.xsmithx.com