Fly.io’s Hobby Plan provides a free allowance that lets you deploy up to three small machines (each with 1 shared CPU and 256 MB of RAM) and 3 GB of storage. So, you can try to create a shared-cpu-1x machine with 256 MB of RAM and 1 GB of storage for the ghost v5, and another shared-cpu-1x machine with 256 MB of RAM and 2 GB of storage for the MySQL v8. Deploying both Ghost v5 and MySQL v8 on such limited resources can be challenging, but it’s entirely achievable. This post walks you through the process step-by-step.
# Create a working directory for the Fly.io MySQL app
mkdir /home/sumar/Projects/fly.io-mysql
cd /home/sumar/Projects/fly.io-mysql
# Launch a new Fly.io app for the MySQL database
# --name specifies a unique app name (you can use any name or generate a random one)
# --region specifies the nearest region to deploy the app; here, we choose Singapore
# --org specifies the Fly.io organization
# This command generates a fly.toml file that we will edit later
flyctl launch --name kube-my-id-db --image=mysql:8.4.3 --vm-memory 256 --region sin --no-deploy --org personal
# Create a 2GB volume for MySQL storage
# The volume will be used later as "db_data"
fly volumes create db_data --size 2 --region sin --auto-confirm
# Set MySQL user and root passwords
# The passwords can be generated using tools like pwgen
fly secrets set MYSQL_PASSWORD=aij1aiL0eim1Raqu
fly secrets set MYSQL_ROOT_PASSWORD=oath0Ci1Pievae7e
fly.toml file. Edit it as shown below:app = "kube-my-id-db"
primary_region = "sin"
# Adding swap memory to help manage the low RAM environment
swap_size_mb = 1_024
[build]
image = "mysql:8.4.3"
[env]
MYSQL_DATABASE = "ghost"
MYSQL_USER = "ghost"
[http_service]
auto_start_machines = true
auto_stop_machines = false
force_https = true
internal_port = 8_080
min_machines_running = 1
processes = ["app"]
[[mounts]]
# The source volume refers to the previously created "db_data"
destination = "/data"
source = "db_data"
[processes]
# This configuration ensures MySQL can run with only 256 MB of RAM
app = "--datadir /data/mysql --mysql-native-password=ON --performance-schema=OFF --innodb-buffer-pool-size 64M"
[[vm]]
size = "shared-cpu-1x"
memory_mb = 256
# Deploy the MySQL app
flyctl deploy
Monitor the logs during deployment to catch and address any errors.
# Create a working directory for the Fly.io Ghost app
mkdir /home/sumar/Projects/fly.io-ghost
cd /home/sumar/Projects/fly.io-ghost
# Launch a new Fly.io app for Ghost v5
# --name specifies a unique app name (you can use any name or generate a random one)
# --region specifies the nearest region to deploy the app; here, we choose Singapore
# --org specifies the Fly.io organization
# This command generates a fly.toml file that we will edit later
flyctl launch --name kube-my-id --image=ghost:5-alpine --vm-memory 256 --region sin --no-deploy --org personal
# Create a 1GB volume for Ghost storage
flyctl volumes create ghost_data --region sin --size 1 --auto-confirm
# Set the database connection password for Ghost
# Ensure this password matches the MySQL password set earlier
fly secrets set database__connection__password=aij1aiL0eim1Raqu
Edit the generated fly.toml file. Here’s an example configuration:
app = "kube-my-id"
primary_region = "sin"
# Adding swap memory to assist with low RAM usage
swap_size_mb = 512
[build]
image = "ghost:5-alpine"
[env]
NODE_ENV = "production"
database__client = "mysql"
database__connection__database = "ghost"
# The database host is the Fly.io MySQL app name with ".internal" as the TLD
# Adjust this value to match your MySQL app name
database__connection__host = "kube-my-id-db.internal"
database__connection__port = "3306"
database__connection__user = "ghost"
database__debug = "false"
# Custom domain for the blog
# Refer to <https://fly.io/docs/networking/custom-domain/> for details
url = "https://blog.kube.my.id"
# Optional email configuration
# mail__from = ""
# mail__options__auth__user = ""
# mail__options__host = ""
# mail__options__port = "587"
# mail__transport = "SMTP"
[http_service]
auto_start_machines = true
auto_stop_machines = false
force_https = true
internal_port = 2_368
min_machines_running = 1
processes = ["app"]
[[mounts]]
# The source volume refers to the previously created "ghost_data"
destination = "/var/lib/ghost/content"
source = "ghost_data"
[[vm]]
memory_mb = 256
size = "shared-cpu-1x"
# Deploy the Ghost app
flyctl deploy
Monitor the logs during deployment to catch and address any errors. Once the deployment is successful, you can access your Ghost blog at the specified URL if you have set up a custom domain and SSL certificates. Otherwise, you can access it at the Fly.io app URL.
With this setup, your Ghost v5 blog and MySQL v8 database are now deployed on Fly.io’s free tier shared-cpu-1x with 256MB of RAM. By optimizing resource usage (e.g., enabling swap and adjusting MySQL configurations), you can run both applications on minimal resources. Of course, this setup is not suitable for production environments with high load, but it’s a great way to experiment with deploying Ghost and MySQL on Fly.io. For production environments, consider upgrading to a higher-tier plan with more resources.

My blog is running on this setup, no visitor except myself and some botnet scanning wordpress caugh. You can check it out at blog.kube.my.id.