Migrate from Beehive: Using Ghost-CMS


Hello 👋
This is the documentation of the challenge that Sandeep Das announced → see here
🎯 Agenda: I am documenting the whole process that I gone through from setting up Ghost-CMS to importing content from a Beehive to it -
🧐 Why I chose beehive?
It is not like I chose beehive for any particular reason I chose it because it is very popular, I am subscribed to some newsletter of some creators who uses these platform and Sandeep Das sir also migrated from this platform to their own who gave this challenge
(spoiler alert)🚨: I should have chosen substack because, this created a problem and wasted the 4 hours of mine for me you will come to know later in this blog but because of this it will be easier for you guys to migrate from beehiiv
anyway, lets continue ⏩
Now let’s Begin 🚀
Part - 1 → Beehive setup 🐝
(if you are already a user of beehive or any other newsletter platform then skip this part)
go to beehiiv site and create an account and after creating the account you will see a dashboard like this
Now, you have a post section in the sidebar just under the plus button go there and create some posts.
Just under the post section in the sidebar you also have audience section from where you can import subscriber click on import subscribers and you will see screen like this
Now, choose the import method as in this screen and you can add subscribers
Now, you need to export the data from the beehiiv this is pretty straight forward,
so you can see that export data is written just under the subscribers import so click on that and you will see
Remember to import the subscribers data and Posts data separately.
Now, you can explore according to your wants and needs this was all for the part - 1 😁
Part - 1 Mission Passed ✅
Part - II Begins 🎬
Now this is will be interesting -
You have to set up the Ghost CMS locally there are more than one way to do it but the one which I found simple is documented here
First, lets see see what all are the requirements we need 📋
a. Node.js
b. Database like mysql, postgresql etc
c. Package manager like yarn or other
MacOS (as this tutorial all commands are according to MacOS)
Now let’s start with installation ⚙️
go to terminal and run the command to install the ghost cli
🧐Why? → it makes easier to install, configure and manage the ghost
npm install -g ghost-cli
Now create a folder manually or by command in your system
🧐Why? → to provide a dedicated folder to install ghost locally and prevent any sort of conflicts and to organise the project correctly
mkdir ghost-local cd ghost-local
Now install the ghost locally
🤨What will this command do?
Installs Ghost in development mode using SQLite (no MySQL needed we will configure it later)
Automatically handles dependencies
Starts the local Ghost server
Opens the admin panel in your browser
ghost install local
Access Ghost
Blog frontend: http://localhost:2368
Admin panel: http://localhost:2368/ghost
Now, these are some useful command of ghost 📋
| Command | Why it's needed | | --- | --- | |
ghost start
| Start the local Ghost instance | |ghost stop
| Stop the Ghost server | |ghost restart
| Restart after making changes | |ghost log
| Debug by viewing logs | |ghost update
| Keep Ghost CMS up to date |
Now, when you will go to the localhost url you will see a screen
You have to set it up by providing your site title, name, email and password, it is pretty straight forward like any other signing up process and also remember the password 😁 as you will need it later
Now, you have signed up and you will be seeing a dashboard like
Now, we will be importing the data which we exported from the beehiiv
There is a catch in this, remember I was telling I should have used substack now you will get to know why this beehiiv migration thing is not that straight forward
Now, when you click on the setting icon this will be showed
Now, when you scroll down in the sidebar in the advanced section of sidebar there is written import and export
There are direct options for other platforms like substack, mailchimp etc. which is pretty simple but when you click on them they will only run if you are available online but there is a quick solution for that go to
ngrok
which will make your localhost available online and then you can use these optionsbut there is no direct option for the beehiiv 😔
So, I used Universal import but again it was a problem only, because the format in which it accepts and the format in which beehiiv exports are different so we need to convert the beehiiv files to right format and even when you do that a new problem will be waiting for you
when you upload the json or zip file of the content and subscribers it will show
“Import in Progress“ ⏸️ but this will never progress as in the logs you will see
This error is coming because I haven’t set up any mail service but there is no option to bypass the mail service I was stuck in this problem for 3 hours 😔 but now, you don’t have to because this is not the correct way to import from beehiiv in the first place 😜, the correct way is below
But, First let’s change our database to mysql there is no need but I prefer mysql because I am familiar with its workbench for doing this you need to first stop the ghost and then
→ open the ghost which you installed locally → open
config.development.json
file→ then in that edit to make database
{ "database": { "client": "mysql", "connection": { "host": "localhost", "user": "ghost_user", "password": "your_secure_password", "database": "ghost_db" } } }
now, you can start the ghost again ⏩
but this is when you don’t already have any posts in the previous database if you have that then you need to migrate it from that database to this for that there are a couple of extra steps
‼️ this method is for importing the free subscribers and local set up if you have paid users and custom domain then you will require some more configurations for avoiding problems for that you can refer to this ghost-cms article for more details → here
Importing Subscribers
for this go to members section on the dashboard and you will see
Click on settings icon and then on the import members and you will see a popup which will ask for csv, upload the csv which you have exported from the beehiive
upload that csv and you will see some configurations you can edit them and after clicking on import button your subscribers will get imported
Importing Posts
now for migrating the posts we need to install a dependency to do that → run this command
npm install --global @tryghost/migrate # Install CLI # Verify it's installed migrate
Now, run this command to make the zip file of the posts that you exported from the beehiiv
# Basic migration migrate beehiiv --posts /path/to/posts.csv --url https://example.com
in the —url you can use you localhost url
‼️ Now, this will create a zip file, now go back to that import/export in advanced section of the setting’s sidebar of ghost cms and upload this zip file and your posts will be visible in the ghost cms
you will see a screen like this in when the command run is completed
Now, you have imported the content now it is the time to tweak the design from default to give it personal touch to do so go to the site section in the setting
there are good options to do that in the ghost cms but better ones are paid at least you have many option in choosing theme in theme section anyway, I briefly explored this section because design is subjective and we can keep changing further
After this your site will be looking like this according to your theme you posts should be visible on http://localhost:2368/
Part - II → Concludes Here ✅
here, you have local set up of ghost cms with imported data
Part - III → Docker Compose File 🐳
I am not a pro in Docker but I studied docker through tutorials and I created it by viewing some of that tutorials and also A.I. for specific doubts. This may be not perfect or very optimised but
This is my docker compose file -
# Docker Compose version - tells Docker which features are available
version: '3.8'
# Services section - defines all the containers we want to run
services:
# Ghost CMS container
ghost:
# Which Docker image to use - Ghost version 5 with Alpine Linux (lightweight)
image: ghost:5-alpine
# Container name for easier identification
container_name: ghost-cms
# Always restart if container stops/crashes
restart: always
# Port mapping: host_port:container_port
# Maps port 2368 on your computer to port 2368 inside container
ports:
- "2368:2368"
# Environment variables - configuration settings for Ghost
environment:
# The URL where your blog will be accessible
url: http://localhost:2368
# Production mode for better performance and security
NODE_ENV: production
# Database type - telling Ghost to use MySQL instead of default SQLite
database__client: mysql
# MySQL connection settings
database__connection__host: mysql-db # hostname of MySQL container
database__connection__user: ghost_user # MySQL username
database__connection__password: ghost_password # MySQL password
database__connection__database: ghost_db # MySQL database name
database__connection__port: 3306 # MySQL port (default)
# Volume mapping - persists Ghost data between container restarts
# Maps named volume to Ghost's content directory inside container
volumes:
- ghost_content:/var/lib/ghost/content
# Wait for MySQL to be ready before starting Ghost
depends_on:
- mysql-db
# Put Ghost on custom network so it can talk to MySQL
networks:
- ghost-network
# MySQL database container
mysql-db:
# MySQL version 8.0 Docker image
image: mysql:8.0
# Container name for easier identification
container_name: ghost-mysql
# Always restart if container stops/crashes
restart: always
# MySQL configuration through environment variables
environment:
# Root user password (administrative access)
MYSQL_ROOT_PASSWORD: root_secure_password
# Create a database specifically for Ghost
MYSQL_DATABASE: ghost_db
# Create a user specifically for Ghost (not root)
MYSQL_USER: ghost_user
# Password for the Ghost user
MYSQL_PASSWORD: ghost_password
# Volume mapping - persists MySQL data between container restarts
# Maps named volume to MySQL's data directory inside container
volumes:
- mysql_data:/var/lib/mysql
# Put MySQL on custom network so Ghost can connect to it
networks:
- ghost-network
# Named volumes - Docker manages these for persistent data storage
volumes:
# Stores Ghost content (themes, images, uploads, etc.)
ghost_content:
# Docker creates and manages this volume
driver: local
# Stores MySQL database files
mysql_data:
# Docker creates and manages this volume
driver: local
# Custom network - allows containers to communicate using container names
networks:
ghost-network:
# Bridge network type - containers can talk to each other
driver: bridge
Now, I have used localhost in url but for making it available online I can use ngrok or custom domain but I was learning dockerization techniques and how to write dockerfile for a big project so I used localhost
We can change url later
there are only 2 services we are using Ghost and MySql
We have volumes to persist data storage
And we have networks to allow containers to communicate
Now, for running it you can follow the process
Save the file as
docker-compose.yml
Start everything:
docker-compose up -d
View logs:
docker-compose logs -f ghost
Access Ghost: Go to
http://localhost:2368
Stop everything:
docker-compose down
Migrating Existing Data:
Since I already have Ghost with imported Beehiiv data, I can import my data with two options:
Stop the docker and
Option 1: Export/Import
Export from your current Ghost (Settings > Labs > Export)
Start this Docker setup
Import the JSON file into new Ghost
Option 2: Copy existing content
Find your current Ghost content folder
Copy it to Docker volume after first run
Then, you can recompose it
Part - III: Ends here → Mission Complete ✅
We have reached to the finish line for now 🏁
I would have hosted this and included that also but the thing is, it requires credit/debit card and my debit card of Rupay is not accepted so I didn’t explored this part I will add the kubernetes and hosted site blog later.
This is my first blog. So, “Pardon my mistakes”. Till then -
Subscribe to my newsletter
Read articles from Agrapujya Lashkari directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
