When running SQLite in production, you need to have a solid backup mechanism setup. And I say this as someone who has accidentally deleted the production SQLite database. Trust me, resilience is something you should have setup from day one.
Posted this on the twits at x.com before I saw the link to this post.
When I run fly console, it spins up a Fly Machine that doesn’t have access to the filesystem where the SQLite database resides. It’s surprising when I run something like User.all and I don’t see users or it tells me the database doesn’t exist. Eventually I remember that fly console runs a different machine and I can run fly ssh console -C "bin/rails" as a workaround.
Could fly console be modified for SQLite installations so that it works in a non-surprising way?
In the past I’ve proposed a fly task runner. For example, it would be useful if I could run fly exec rake db:migrate and have it do what I expect and fly exec console load up the machine I expect to run Rails tasks. Idealy I could customize this to boot a new machine or shell into an existing machine.
I tried this again today and it’s still broken; however I was able to get some logs:
Desktop/demo [main] → fly logs
WARN The running flyctl agent (v0.3.72) is older than the current flyctl (v0.3.74).
WARN The out-of-date agent will be shut down along with existing wireguard connections. The new agent will start automatically as needed.
2025-02-05T00:01:47Z runner[7811093f59e148] sjc [info]Pulling container image registry.fly.io/demo-bitter-butterfly-361:deployment-01JK9PENZE2W8F2410KETNQW2W
2025-02-05T00:01:57Z runner[7811093f59e148] sjc [info]Successfully prepared image registry.fly.io/demo-bitter-butterfly-361:deployment-01JK9PENZE2W8F2410KETNQW2W (9.662814673s)
2025-02-05T00:01:57Z runner[7811093f59e148] sjc [info]Setting up volume 'data'
2025-02-05T00:01:57Z runner[7811093f59e148] sjc [info]Uninitialized volume 'data', initializing...
2025-02-05T00:01:57Z runner[7811093f59e148] sjc [info]Encrypting volume
2025-02-05T00:02:02Z runner[7811093f59e148] sjc [info]Opening encrypted volume
2025-02-05T00:02:04Z runner[7811093f59e148] sjc [info]Formatting volume
2025-02-05T00:02:06Z runner[7811093f59e148] sjc [info]Configuring firecracker
2025-02-05T00:02:06Z health[7811093f59e148] sjc [warn]Health check on port 8080 is in a 'warning' state. Your app may not be responding properly. Services exposed on ports [80, 443] may have intermittent failures until the health check passes.
2025-02-05T00:02:06Z app[7811093f59e148] sjc [info]2025-02-05T00:02:06.569380628 [01JK9PKD4CJXKJ9899J7A9FZKE:main] Running Firecracker v1.7.0
2025-02-05T00:02:07Z app[7811093f59e148] sjc [info] INFO Starting init (commit: 676c82a4)...
2025-02-05T00:02:07Z app[7811093f59e148] sjc [info] INFO Checking filesystem on /data
2025-02-05T00:02:07Z app[7811093f59e148] sjc [info]/dev/vdc: clean, 11/64512 files, 8785/258048 blocks
2025-02-05T00:02:07Z app[7811093f59e148] sjc [info] INFO Mounting /dev/vdc at /data w/ uid: 1000, gid: 1000 and chmod 0755
2025-02-05T00:02:07Z app[7811093f59e148] sjc [info] INFO Resized /data to 1056964608 bytes
2025-02-05T00:02:07Z app[7811093f59e148] sjc [info] INFO Preparing to run: `/rails/bin/docker-entrypoint ./bin/rake litestream:run ./bin/rails server` as 1000
2025-02-05T00:02:07Z app[7811093f59e148] sjc [info] INFO [fly api proxy] listening at /.fly/api
2025-02-05T00:02:07Z runner[7811093f59e148] sjc [info]Machine created and started in 19.975s
2025-02-05T00:02:08Z app[7811093f59e148] sjc [info]2025/02/05 00:02:08 INFO SSH listening listen_address=[fdaa:0:793f:a7b:16d:309d:1001:2]:22 dns_server=[fdaa::3]:53
2025-02-05T00:02:10Z app[7811093f59e148] sjc [info]2025/02/05 00:02:10 ERROR failed to run error="config file not found: /rails/tmp/litestream.yml"
2025-02-05T00:02:10Z app[7811093f59e148] sjc [info] INFO Main child exited normally with code: 1
2025-02-05T00:02:10Z app[7811093f59e148] sjc [info] INFO Starting clean up.
2025-02-05T00:02:10Z app[7811093f59e148] sjc [info] INFO Umounting /dev/vdc from /data
2025-02-05T00:02:10Z app[7811093f59e148] sjc [info] WARN could not unmount /rootfs: EINVAL: Invalid argument
2025-02-05T00:02:10Z app[7811093f59e148] sjc [info][ 4.087932] reboot: Restarting system
2025-02-05T00:02:13Z app[7811093f59e148] sjc [info]2025-02-05T00:02:13.542643652 [01JK9PKD4CJXKJ9899J7A9FZKE:main] Running Firecracker v1.7.0
2025-02-05T00:02:14Z app[7811093f59e148] sjc [info] INFO Starting init (commit: 676c82a4)...
2025-02-05T00:02:14Z app[7811093f59e148] sjc [info] INFO Checking filesystem on /data
2025-02-05T00:02:14Z app[7811093f59e148] sjc [info]/dev/vdc: clean, 11/64512 files, 8785/258048 blocks
2025-02-05T00:02:14Z app[7811093f59e148] sjc [info] INFO Mounting /dev/vdc at /data w/ uid: 1000, gid: 1000 and chmod 0755
2025-02-05T00:02:14Z app[7811093f59e148] sjc [info] INFO Resized /data to 1056964608 bytes
2025-02-05T00:02:14Z app[7811093f59e148] sjc [info] INFO Preparing to run: `/rails/bin/docker-entrypoint ./bin/rake litestream:run ./bin/rails server` as 1000
2025-02-05T00:02:14Z app[7811093f59e148] sjc [info] INFO [fly api proxy] listening at /.fly/api
2025-02-05T00:02:14Z runner[7811093f59e148] sjc [info]Machine started in 1.25s
2025-02-05T00:02:15Z app[7811093f59e148] sjc [info]2025/02/05 00:02:15 INFO SSH listening listen_address=[fdaa:0:793f:a7b:16d:309d:1001:2]:22 dns_server=[fdaa::3]:53
2025-02-05T00:02:16Z health[7811093f59e148] sjc [error]Health check on port 8080 has failed. Your app is not responding properly. Services exposed on ports [80, 443] will have intermittent failures until the health check passes.
2025-02-05T00:02:17Z app[7811093f59e148] sjc [info]2025/02/05 00:02:17 ERROR failed to run error="config file not found: /rails/tmp/litestream.yml"
2025-02-05T00:02:17Z app[7811093f59e148] sjc [info] INFO Main child exited normally with code: 1
2025-02-05T00:02:17Z app[7811093f59e148] sjc [info] INFO Starting clean up.
2025-02-05T00:02:17Z app[7811093f59e148] sjc [info] INFO Umounting /dev/vdc from /data
2025-02-05T00:02:17Z app[7811093f59e148] sjc [info] WARN could not unmount /rootfs: EINVAL: Invalid argument
2025-02-05T00:02:17Z app[7811093f59e148] sjc [info][ 4.123091] reboot: Restarting system
2025-02-05T00:02:20Z app[7811093f59e148] sjc [info]2025-02-05T00:02:20.472114649 [01JK9PKD4CJXKJ9899J7A9FZKE:main] Running Firecracker v1.7.0
2025-02-05T00:02:21Z app[7811093f59e148] sjc [info] INFO Starting init (commit: 676c82a4)...
2025-02-05T00:02:21Z app[7811093f59e148] sjc [info] INFO Checking filesystem on /data
2025-02-05T00:02:21Z app[7811093f59e148] sjc [info]/dev/vdc: clean, 11/64512 files, 8785/258048 blocks
2025-02-05T00:02:21Z app[7811093f59e148] sjc [info] INFO Mounting /dev/vdc at /data w/ uid: 1000, gid: 1000 and chmod 0755
2025-02-05T00:02:21Z app[7811093f59e148] sjc [info] INFO Resized /data to 1056964608 bytes
2025-02-05T00:02:21Z app[7811093f59e148] sjc [info] INFO Preparing to run: `/rails/bin/docker-entrypoint ./bin/rake litestream:run ./bin/rails server` as 1000
2025-02-05T00:02:21Z app[7811093f59e148] sjc [info] INFO [fly api proxy] listening at /.fly/api
2025-02-05T00:02:21Z runner[7811093f59e148] sjc [info]Machine started in 1.235s
--- a/lib/tasks/litestream.rake
+++ b/lib/tasks/litestream.rake
@@ -42,7 +42,7 @@ namespace :litestream do
end
end
- task :run do
+ task :run => :prepare do
require "shellwords"
exec(*%w[bundle exec litestream replicate -config],
Note: at this point you may have a zero length sqlite3 database on your volume, and this will prevent db:prepare from being run. I’ll investigate tomorrow why the => :prepare is missing - it should be present in sqlite3 is detected and omitted if postgres is omitted as the prepare would be done on a deploy release machine.
This should now be fixed. As an added bonus, the change to the Ruby docker images removing libyaml has been reverted so once again fly launch will just work with the Rails provided Dockerfiles.
Quick synopsis on the libyaml issue… Rails generated Dockerfiles didn’t include it, but depend on it. Ruby dockerhub images included it, but shouldn’t have, and stopped. This broke Rails Dockerfiles from being deployed (not just to fly.io, but everywhere). For now, the Ruby folks have reverted their change, and the Rails folks have updated their Dockerfile on main, but haven’t released the fix.
As to fly console, I’m not clear on what the issue is. Yes, fly console will create a new machine, and no, new machines won’t have access to volumes in production. What they will have, however, is the ability to restore a current snapshot from Tigris to play with. The rake task is currently named litestream:prepare, but I’m interested in your thoughts. In any case, the following will get you up and running:
bin/rails litestream:prepare
If you want to access the database in production, fly ssh console will do that for you.
Yeah, my point is the DX is “broken” from the PoV of a person deploying a SQLite database to production. The expectation is that fly console brings up bin/rails c in production and there’s a database there. As we know, in practice, that doesn’t happen.
I’m still confused. What I’m hearing is that a screwdriver is not a good tool for hammering in a nail. I’m suggesting a hammer instead.
Fly.io does not have an innate concept of an SQLite application.
I can describe the commands you can use to ssh into an existing machine and run rails console there. Or I can describe the commands to create a new machine, restore a snapshot of a database, and then run rails console against that snapshot.
What I can’t do is change the command that was explicitly designed to create a new machine to not do that.
I get it, it’s just super hacky and will surprise people who don’t know about running fly ssh console -C "/bin/rails c" to bring up a sane console when they deploy to Fly.
I proposed the fly exec $TASK in flyctl as a way to make a SQLite deployment sane and more customizable (e.g. fly exec rake db:migrate, fly exec rails console), but I’ve never seen that go anywhere either. Said tasks would be configured in fly.toml, including if they’re run on another machine or the same machine.