Use a MySQL Database
If you just want to run a quick, self-managed MySQL instance on Fly.io, here’s how to do it. It’s pretty basic, with one caveat around using a Fly Volume to persist data.
Most Fly apps use a Dockerfile
to define an application and its dependencies. However, in this case we can use MySQL’s official container directly - no need for a custom Dockerfile
!
Here’s how to run MySQL.
Create the App
We’ll run MySQL as a new app:
# Make a directory for the mysql app
mkdir my-mysql
cd my-mysql
# Run `fly launch` to create an app
fly launch --no-deploy
You can name the app whatever you’d like. The name will become a hostname our application uses to connect to the database, such as my-mysql.internal
.
We used the --no-deploy
option because we have some work to do before we deploy the app.
Configure the App
Let’s create a volume straight-away. If you don’t create a volume, you’ll lose all of your data on each deployment.
# Create a volume named "mysqldata" within our app "my-mysql"
fly volumes create mysqldata --size 10 # gb
We also need to set some secrets required by the MySQL container:
# Set secrets:
# MYSQL_PASSWORD - password set for user $MYSQL_USER
# MYSQL_ROOT_PASSWORD - password set for user "root"
fly secrets set MYSQL_PASSWORD=password MYSQL_ROOT_PASSWORD=password
Save these secrets somewhere, because they’re not accessible after you set them.
Finally, edit the fly.toml
file that fly launch
generated.
For apps using MySQL 8+:
app = "my-mysql"
kill_signal = "SIGINT"
kill_timeout = 5
# If copy/paste'ing, adjust this
# to the region you're deploying to
primary_region = "bos"
[processes]
app = """--datadir /data/mysql \
--default-authentication-plugin mysql_native_password \
--performance-schema=OFF \
--innodb-buffer-pool-size 64M"""
[mounts]
source="mysqldata"
destination="/data"
[env]
MYSQL_DATABASE = "some_db"
MYSQL_USER = "non_root_user"
# As of 04/25/2023:
# MySQL 8.0.33 has a bug in it
# so avoid that specific version
[build]
image = "mysql:8.0.32"
There’s a few important things to note:
- We deleted the
[[services]]
or the[[http_service]]
block and everything under it. We don’t need it! - We added the
[build]
section to specify an existing Docker image. We don’t need to create aDockerfile
of our own. - In the
[env]
section, we added two not-so-secret environment variables that MySQL will need to initialize itself.- The
MYSQL_USER
here should be any user butroot
, which already exists.
- The
- We added the
[processes]
section for the defaultapp
process, which lets us pass custom commands (overriding Docker’sCMD
).- For MySQL 8+, you’ll want to use the
mysql_native_password
password plugin. - Also for MySQL 8+, to reduce memory usage, add the performance schema and buffer pool size flags. Note that
--performance-schema=OFF
is all one string. - Important: For MySQL 5.7 and MySQL 8+, we set MySQL’s data directory to a subdirectory of our mounted volume.
- For MySQL 8+, you’ll want to use the
If you’re using MySQL 5.7, your app
process can be simplified:
[processes]
app = "--datadir /data/mysql"
⚠️ Mounting a disk in Linux usually results in a lost+found
directory being created. However, MySQL won’t initialize into a data directory unless it’s completely empty. Therefore, we use a subdirectory of the mounted location: /data/mysql
.
Scale the App
There’s one more detail. MySQL 8+ has higher baseline resource demands than MySQL 5.7.
If you’re using MySQL 8, it’s best to add some additional RAM to the VM:
# Give the vm 2GB of ram
fly scale memory 2048
This isn’t necessary with MySQL 5.7.
Deploy the App
We’re now ready to deploy the MySQL app! Go ahead and run:
fly deploy
After a minute of MySQL initializing itself (on its first run), you should now have an app running MySQL!
Your other apps can access the MySQL service by its name. In my case, I would use my-mysql.internal
as the hostname. Any app that needs to access the database should set the hostname and username as environment variables, and create a secret for the database password.
Access the database from outside
To connect to your MySQL database from outside of your Fly organization, you need a WireGuard connection. However, fly
on your local machine can connect using user-mode WireGuard magic, without you having to set up your own WireGuard tunnel.
You can forward the MySQL server port to your local machine using fly proxy
:
flyctl proxy 3306 -a my-mysql
You can also set a different local port, if your 3306
port is already in use:
flyctl proxy 13306:3306 -a my-mysql
Then connect to your MySQL server at localhost:3306
and the username and password credentials from above:
mysql -h localhost -P 3306 -u non_root_user -ppassword some_db
Backups
We’ll take a snapshot of the created volume every day. We retain 5 days of snapshots.
To restore a snapshot, make sure you have the latest version of the fly
command, and then create a new volume using the --snapshot-id
flag.
# Get a volume ID
fly volumes list -a my-mysql
# List snapshots for a volume
fly volumes snapshots list vol_xxx
# Create a new volume from a snapshot
fly volumes create --snapshot-id vs_xxx --size 10