Table of Contents
This article introduce how to install, backup and restore ghost on fly.io for free. Backing up and restoring data is a reference for use cases on other servers. With the use of cron, the full site data is regularly backed up to github, so to speak, you can really feel at ease with ghost and give up static blogs.
- To register for a fly.io account,install flyctl command line, you need a credit card. (required)
- be familiar with basic git commands. (not required)
- Understand fly.io's pricing. (not required)
- Understand Mailgun. (not required)
Install the command line
curl -L https://fly.io/install.sh | sh
iwr https://fly.io/install.ps1 -useb | iex
The following command is common on Linux and Mac.
Open a terminal and execute.
mkdir blog #Create a directory cd blog #Enter this directory flyctl auth login #Log in, then the browser will pop up the login session flyctl launch --image=ghost:5 -r hkg --name=<AppName> --no-deploy
An explanation of the last command:
The docker image used is
ghost:5, indicating that the 5.x major version will be updated automatically on every deployment, and if you intend to check a specific version, such as 5.36.0, then replace 5 with it. The official image is here.
- r hkgmeans that the node of the selected server is Hong Kong (the node closest to us). The region code of fly.io is here.
--name=<AppName>means the name of the created app, replace
<AppName>with your own, and note that the default URL generated in the future is
<AppName>.fly.dev, so please take a unique name.
--no-deploymeans don't deploy for now.
This will create a fly.toml file in the blog directory. Open it with a text editor and overwrite it with the following code.
app = "<AppName>" kill_signal = "SIGINT" kill_timeout = 5 processes =  [build] image = "ghost:5" [env] url = "https://<AppName>.fly.dev" database__client = "sqlite3" database__connection__filename = "content/data/ghost.db" database__debug = "false" database__useNullAsDefault = "true" mail__from = "firstname.lastname@example.org" mail__options__auth__pass = "<YourMailgunPassword>" mail__options__auth__user = "email@example.com" mail__options__host = "smtp.mailgun.org" mail__options__port = "465" mail__transport = "SMTP" [experimental] auto_rollback = true [mounts] destination = "/var/lib/ghost/content" source = "data" [[services]] http_checks =  internal_port = 2368 processes = ["app"] protocol = "tcp" script_checks =  [services.concurrency] hard_limit = 25 soft_limit = 20 type = "connections" [[services.ports]] force_https = true handlers = ["http"] port = 80 [[services.ports]] handlers = ["tls", "http"] port = 443 [services.ports.http_options.response.headers] Referrer-Policy = "strict-origin" Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload" X-Content-Type-Options = "nosniff" X-Frame-Options = "SAMEORIGIN" x-xss-protection = "1; mode=block" Permissions-Policy = "camera=(), microphone=(), geolocation=(), browsing-topics=()" [[services.tcp_checks]] grace_period = "1s" interval = "15s" restart_limit = 0 timeout = "2s"
<AppName> with your own, and, if you don't use Mailgun, please remove all code starting with
[services.ports.http_options.response.headers] section is optional and is used to set the http header. You can search item by item,
Strict-Transport-Security, for example, to determine if you want to add them. After the deployment, you may go to the http header test site.
When you are done checking, save and close the fly.toml file. Then go back to the terminal and continue executing.
flyctl volumes create data -r hkg --no-encryption --size 1 # Create a volume of size 1Gb with region hkg and no encryption (helps improve performance). The free amount is 3GB in total. flyctl deploy #deploy
Wait a few moments, the deployment is successful! It will show
to initialize ghost. After this, you are free to use ghost to publish content. Binding domain names, backing up and restoring data are optional operations.
flyctl ips list
to see the IP address of your app. Then create A records for ipv4 and AAAA records for ipv6 on your DNS server, pointing to your domain name
example.com (required). Create CNAME,
<AppName>.fly.dev, pointing to your
www.example.com (not required). Also known as
Note: Before binding the domain name, make sure to create the DNS records correctly.
flyctl certs create example.com flyctl certs create www.example.com
wait a moment and run
flyctl certs check example.com flyctl certs check www.example.com
to check the certificate enactment, which usually takes about a minute or two as long as the DNS advance configuration is correct. Run
flyctl ips list
to check the certificate issuance status.
Note: Don't install certificates repeatedly because there is a limit, only 5 certificates can be issued every 7 days. It is recommended to bind them after debugging the app.
Notice that in the fly.toml,
[env] url = "https://<AppName>.fly.dev"
is the address assigned by fly.io, and if you click on the home page title of the ghost site, it will redirect to this address, so when you bind your own domain, change it to
[env] url = "https://example.com"
Save and close, then run
Every time in the future you need to go into your local configuration directory to use flyctl correctly.
The advantage is that no complicated configuration is needed, but the copy speed is slow.
First login (assuming you need to back up later).
cd blog flyctl auth login
flyctl ssh sftp shell -r -a <AppName>
When it succeeds, it will show
>> (red), then run
This will backup the whole site data, including themes, images, videos, articles, etc. It's very slow, please be patient.
You can also backup only images and videos by running
get /var/lib/ghost/content/images/ get /var/lib/ghost/content/media/
This will generate a
media.zip in the blog directory. back them up elsewhere, or name them
Now suppose you reinstall a new ghost, assuming it is still on fly.io, then the installation directory will remain as
/var/lib/ghost/content/. If you install ghost somewhere else in the future, like on AWS, make sure you create this directory instead of the usual tutorial
/var/www/ghost/content/, otherwise you will have trouble recovering your data by first method.
cd blog flyctl auth login flyctl ssh sftp shell -r -a <AppName>
put content.zip /var/lib/ghost/content.zip
After success, create a new tab in the terminal and run
flyctl ssh console
again, in ssh, run
apt-get update && apt-get upgrade && apt-get install unzip
unzip. Then run
cd /var/lib/ghost/ unzip content.zip
It will overwrite the content folder directly.
To verify that the unzip was successful, run
cd /var/lib/ghost/content/images/ ls #List all the files in the current folder
to see if there is any new data.
To fix permissions, run
chown -R node:node /var/lib/ghost/content/
Note: Possible scenario, can't overwrite the content folder correctly, then try to backup only
/var/lib/ghost/content/media/ instead. At this point run
put images.zip /var/lib/ghost/content/images.zip put media.zip /var/lib/ghost/content/media.zip rm -rf /var/lib/ghost/content/images/ rm -rf var/lib/ghost/content/media/ unzip /var/lib/ghost/content/images.zip unzip /var/lib/ghost/content/media.zip
this may break the permissions of the content folder, so run
ls -l /var/lib/ghost/content/
to check the user and permissions of the images and media folders, if their permissions belong to root, then you can't upload images and videos anymore, then run
chown -R node:node /var/lib/ghost/content/ chown -R node:node /var/lib/ghost/content/images/ chown -R node:node /var/lib/ghost/content/media/
to fix it, note that
node is the ghost user in the fly.io case, depending on the result of the
ls -l /var/lib/ghost/content/ command,, in any case make sure that the user of the images and media folders is the same as the user of the other files under
The advantage is that it is fast to replicate data, but the disadvantage is that the installation and configuration is relatively complicated.
- Login and enter ssh
cd blog flyctl auth login flyctl ssh console
- Install git
apt-get update && apt-get upgrade && apt-get install git
Create a private (recommended) repository on the github web side , assuming it is called
Generate the key
ssh-keygen -t rsa -C "<GithubEmail>" # Press the Enter key continuously
- Terminal new tab, run
flyctl ssh sftp shell -r -a <AppName> get /root/.ssh/id_rsa.pub
Go back to the blog folder and find the
id_rsa.pub file, open it with a text editor, and copy the key from it. Add it to
setting→SSH and GPG keys→SSH keys in github and save it.
Test the connection with github
Pay attention to the word
- Initialize the content git
cd /var/lib/ghost/content/ git init git config --global user.name "<GithubUsername>" git config --global user.email <GithubEmail> git config --global --add safe.directory /var/lib/ghost/content git remote add origin firstname.lastname@example.org:<GithubUsername>/<YourRepository>.git git add . git commit -m "auto backup" git push -u origin master --force
Make sure you successfully push to github.
- Create a new script
apt-get update && apt-get upgrade && apt-get install nano cd /var/lib/ghost/content/ nano auto_run.sh
Copy the following code into
! /bin/sh cd /var/lib/ghost/content # Switch to the directory git config --global init.defaultBranch master git config --global --add safe.directory /var/lib/ghost/content git pull # Pull the repository git add . # Add a staging git commit -m "auto backup" # Commit git push --force
O followed by
enter to write; press
x to exit nano.
Give the file permission to run
chmod a+x auto_run.sh
Later, after logging into ssh, you can directly run
to execute a new push.
Log in to shh, reinstall git and add the key without further ado.
Empty the content folder
cd /var/lib/ghost/ rm -rf content
It will return that the folder can't be deleted because its
busy, it doesn't matter, it has been emptied, but not deleted.
- Initialize the git in the content file and force a pull
cd /var/lib/ghost/content/ git init git config --global user.name "<GithubUsername>" git config --global user.email <GithubEmail> git pull --force email@example.com:<GithubUsername>/<YourRepository>.git
- Verify that the pull was successful
- Make sure the data is successfully pulled, then restart the app
fly apps restart <AppName>
As of now, restarting or redeploying will clear the installed environment and software, but will not delete the ghost data.
- It is really difficult to run cron in docker, so don't try it. In a normal virtual machine, you can set up cron to run at regular intervals