š From Zero to Newsletter Hero: Building and Hosting a Full-Stack Publishing Platform with Ghost, Docker, Kubernetes, and ngrok


Submission for the Newsletter Challenge ā Built with š», powered by curiosity, and deployed like production.
āØWhy You Should Read This
If youāve ever wondered:
How can I run my own newsletter without paying a monthly fee?
Can I customize it like a pro?
Can I deploy it using modern DevOps tools like Docker and Kubernetes?
...then youāre exactly where you need to be. In this challenge, I took a raw idea and turned it into a fully functional, self-hosted newsletter system, built from scratch using:
š° Ghost CMS
š³ Docker & Docker Compose
āøļø Kubernetes with Kind
š ngrok for instant HTTPS tunneling
Letās dive into the story.
š Challenge Breakdown: What Was Required
Here's what the challenge asked for:
ā Use a popular newsletter platform (Substack, Beehiiv, or MailerLite), post content, and export data
ā Set up a self-hosted Ghost CMS site (locally or on cloud), import data from step 1
ā Customize Ghostās theme and settings
ā š§ Bonus: Run with Docker, then with Kubernetes (Minikube/Kind), and document everything
I didn't just complete the challenge. I engineered it like a real production system.
āļø Step 1: Exploring Substack
I chose substack because I used to write on it a while ago.
I then added a few email ids of mine, and then exported the data.
šļø Step 2: Running Ghost CMS with Docker
Here's the docker-compose.yaml
file I used to run Ghost CMS and MySQL locally:
services:
ghost:
image: ghost:5
container_name: ghost
ports:
- "2368:2368"
volumes:
- ghost_data:/var/lib/ghost/content
environment:
url: http://localhost:2368
database__client: mysql
database__connection__host: db
database__connection__user: ghost
database__connection__password: ghostpass
database__connection__database: ghost
depends_on:
db:
condition: service_healthy
restart: always
db:
image: mysql:8
container_name: ghost-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: -/-/-/
MYSQL_DATABASE: ghost
MYSQL_USER: ghost
MYSQL_PASSWORD: ghostpass
volumes:
- ghost_mysql:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
ghost_data:
ghost_mysql:
Now run this command in the same directory:
docker-compose up -d
Once all the containers are healthy and running, we can visit http://localhost:2368 and we should be able to see something like this:
Now go to http://localhost:2368/ghost to create the newsletter:
š Theme Customization
I chose the Edition theme because it resembles Substackās layout, and picked the Manrope fontābecause itās clean and modern (and honestly, my favorite).
Now, I just have to import my blogs and email list and then I can finally see how will my page will look like.
A common issue I faced was that Ghost requires the site to be hosted publicly to access certain features like newsletter imports. Since I didnāt want to pay to host my website, so I had to figure out a way to host my site for free(coz thatās what Indians are expert inš ). So, after searching the internet, I found a way that I can host the site using ngrok or cloudflare tunnel temporarily.
After downloading ngrok and setting it up with my auth token, I ran the below command to forward the port 2368. But this alone wouldnāt solve the problem, because I also had to update the Docker Compose file and recreate the containers which will ensure that ghost generate internal links correctly with ngrokās domain.
ngrok http 2368
something like this will be shown, now I just have to edit the url in the compose file and then restart container.
docker-compose down
docker-compose up -d
Even after doing this, Ghost was redirecting incorrectly. I expected to be prompted for setup, but it redirected me back to the sign-in pageāturns out I needed to wipe the previous volume data. After a while, I got the issue. I then started everything fresh and removed all the old volumes.
Now, finally we are allowed the import from substack.
Finally, after setting up website for a while, I got this result, containing all my blogs from substack.
And thatās how you host your newsletter using Ghost CMS and Dockerāfor free! š.
āļø Step 3: Ghost + Kubernetes Using Kind
Now, I wanted to run the ghost cms using kubernetes and kind (Since Iāve used Minikube in the past, I decided to try out Kind for fun).
I organized my project into several kubernetes manifest files:
ghost-deployment.yaml
: defines the Ghost CMS deploymentghost-pvc.yaml
: Persistent Volume Claim for Ghost contentmysql-deployment.yaml
: MySQL database deploymentmysql-service.yaml
: MySQL service configurationghost-service.yaml:
Ghost service configuration
After writing the code for all the files, run these commands:
kubectl apply -f ghost-pvc.yaml && kubectl apply -f mysql-deployment.yaml && kubectl apply -f mysql-service.yaml && kubectl apply -f ghost-deployment.yaml && kubectl apply -f ghost-service.yaml
Generate the ngrok url and edit the url in ghost deployment file and reapply it.
But got the ngrok 8012 error:
struggled a lot with the error and came to know that this command was missing.
Basically request was forwarding from ngrok to my localhost, but still after that, I needed to forward it to my ghost service.
After running the commandāboomāit started working on the ngrok URL!
š§ Learnings
How to organise your thoughts to present them in a structured way bcoz there is a lot I can write ā how to hit the perfect spot so that reader doesnāt get bored and still gain some value.
Learning backwards ā It's not necessary these days to learn everything upfront. You can learn on the goāand thatās how I approach most projects nowadays.
When starting out, I wonderedāwhy go through all this if I could just keep writing on Substack?( turns out ghost cms is a lot customizable)
I was curious how ngrok allows access to a site running locally. So, as any curious person would do, I asked ChatGPT and got this. Maybe this can also be useful to you.
š¬ Letās Connect
If you enjoyed reading this blog and gained some value out of it, letās connect on LinkedIn.
Here is the link to github repo!
Subscribe to my newsletter
Read articles from Kartik Yadav directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
