Create your own blog!
This guide it meant for anybody to get their own blog up and running. I am trained in software development, if you have any questions please post in the comments and I will address accordingly! I’ve provided additional information to help guide you in creating the setup via links throughout the text. After completion of this guide, you can add all sorts of enhancements, or keep your site as is for development purposes. This should get you up and running with a new blog in less than a day!

Guide
Create Your Own Blog Using Caddy, Docker and Ghost
Purchase a VPS
For this part, you will need to spend some money unless you choose to partake in a free trial from a cloud provider. Another option, though far less likely, is to host your site on your own machine.
I went with Digital Ocean for their price point and ease of use. There are a multitude of offerings and I encourage you to do your own research to find out what works best for your needs. I am using Ubuntu, though this guide should still be helpful if you’re running on other Linux OS’s.
With this, I selected the cheapest Virtual Private Server (VPS) and setup SSH authentication using the Secure your server guide below. From there I took additional steps to secure my server adding fail2ban and installed docker under a non-root user the guide for this is also below.
Secure your server
Install Docker on Ubuntu
Setup Caddy
For all Docker volume information, I am using /data and for Docker containers I am working from /opt/docker-containers/ – this is my own preference. For more information on Docker volumes, review this documentation.
Adding our custom routing for caddy, based off Caddyfile documentation
/data/caddy/Caddyfile
Now we need to map this domain name to our VPS.
{
# Email for lets encrypt
email [email protected]
}
example.com {
reverse_proxy ghost:2368
}
www.example.com, blog.example.com, www.blog.example.com {
redir https://example.com{uri}
}
Map VPS domain name
Digital Ocean provides documentation on how to do this. Whatever VPS provider you choose should have guidance for this section though.
The steps I took are as follows:
- Map my Domain provider’s (e.g. – GoDaddy, Namecheap, etc.) DNS nameservers to ns1.digitalocean.com, ns2.digitalocean.com and ns3.digitalocean.com (Note: This may be different based on the services you’re using)
- A name mapping example.com to the server’s IPv4 address
- AAAA name mapping example.com to IPv6
- CNAME mapping www.example.com, www.blog.example.com, blog.example.com to example.com
Caddy will perform the routing once we reach our server
Setup Docker-Compose files
To get more insights into docker-compose and what we’re doing below, I highly encourage you to review the following documentation.
We need to setup a network for our components to run on, let’s call it “web”.
Do this via command line within your project’s directory
$> cd /opt/docker-containers/myblog
$> docker network create web
#verify network is created:
$> docker network ls
I want to give a big shout out to Brian Burroughs who provides great documentation on a development setup of Ghost and Caddy, here.
As you can see by the volume definitions in docker-compose.yml we will need the following directories:
/data/caddy/
/data/caddy/data
/data/caddy/config
/data/ghost/
/data/mysql/
In your file…
/opt/docker-containers/myblog/docker-compose.yml
Enter:
version: "3"
networks:
web:
external: true
services:
caddy:
image: caddy:2-alpine
container_name: atoz-caddy
restart: unless-stopped
depends_on:
- ghost
ports:
- "80:80"
- "443:443"
volumes:
- /data/caddy/Caddyfile:/etc/caddy/Caddyfile
- /data/caddy/data:/data
- /data/caddy/config:/config
networks:
- web
ghost:
image: ghost:4.11-alpine
container_name: atoz-blog
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: https://aytuzi.com
volumes:
- /data/ghost:/var/lib/ghost/content
networks:
- web
db:
image: mysql:8
container_name: atoz-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:
- /data/mysql:/var/lib/mysql
networks:
- web
To keep our passwords and other login information outside of the docker-compose file, I am creating a .env file. This information is injected to docker-compose using the ${} operator.
Example /opt/docker-containers/atoz/.env
MYSQL_ROOT_PASSWORD=somepassword
MYSQL_DATABASE=databasename
MYSQL_USER=username
MYSQL_PASSWORD=someotherpassword
Start your containers
$> docker-compose up -d
#if no errors, trail your logs
$> docker-compose logs -f
#verify the configuration from .env are present
$> docker-compose config
If no issues are present, go to your domain. With this setup, you can visit your site under all of the definitions you have set.
Additional Enhancement Opportunities
There are plenty of enhancements that can be made to this setup, including but not limited to:
- Enhanced logging, monitoring and regular system backups
- Security updates
- Creation of a pipeline to automate updates to our system
Thanks for your blog, nice to read. Do not stop.