We will see in this article how to host a real Laravel application inexpensively at fly.io.
By “real application”, I mean, a one with a database, jobs / CRON and a way to store uploaded files in volumes. All of that for very cheap or even free in some cases. Stay tuned!
First of all, what is fly.io?
I quote their site Fly.io transforms containers into micro-VMs. The description seems very explicit. You can scale 0 to unlimited, you don’t have to manage a server and the underlying operating system. Just do your app and ship it! In my opinion, it also have the advantage to be very competitive in term of pricing versus similar alternatives like Google App Engine for example. It is not a general rule, but I have my laravel website creaboutique.dezeiraud.com currently on Fly. And in one month, it cost me $0.00 with the use of the shared-cpu-1x machine. Because of a sort of “free tier” of 3 free shared-cpu-1x VM. Not a lot of power, but sufficient for a small website. Another advantage, the machine can scale to 0 if no traffic inbound after a certain time. So you can save cost & be more ecological. The downside is that the user who wakes up the VM will have a slightly longer loading time than normal, around 300ms.
Cool but, where is my database?
You may have noticed, but there is no database at Fly. We need to look at some managed alternatives (or not, but I don’t cover unmanaged and self-hosted database on a VPS). And good news, we have a plenty out there. Even with good free tier.
Of course, you can choose what provider you want. Azure, AWS, DigitalOcean. It doesn’t matter, you are not constrained by fly. Only endpoint accessible via https from the internet is necessary. To begin with, I will only tell you about 2 that I tested, one without success and the other that I am currently using in free tier for creaboutique.
- Planetscale, The Bad one. It doesn’t appear bad, you could tell me, the free tier is generous, the dashboard is clear, performances are good. So what is the problem? To be clear, it is a good service the problem is PlanetScale does not support foreign key constraints. And it is a big deal in a Laravel app, where it is considered a good practices and worse, used in a lot in different package. For this reason, I don’t recommand you to use planetscale. Or only if you don’t use third party migrations in your project and so you can voir foreign key.
- Supabase, The Good one. Maybe you already know supabase, it is more than just a postgres database but more an Open Source Firebase like they call themself. With a way to manage identification, custom API and more out of the box.But we doesn’t care of this stuff, we only want a database. And good news, it is possible to use only it, even in the free tier (with 500MB database storage. A lot less than Planetscale but at least, this support foreign key constraints).
We will see in a later chapter how to configure the Supabase database and connect to it.
And where I save my files uploaded by my Laravel app?
Good question! If you want of course, you can upload them to AWS S3 or similar / compatible services. It is up to you.
But what if you don’t want to upload same to an external service? Because Fly.io serve your app in a docker container, we can’t just upload files in local storage. Because after an update, data well be losed. And anyway, it is readonly. But, like any Docker image, you can create Volumes. And this is exactly what we are going to do in our setup. Of course, you need to pay for the volume, depending the number of storage you need.
Let’s go
First step, create your account on fly.io. Depending your Operating System, follow this step to install the Fly CLI. Then, login yourself in the CLI following this second step. It is very easy so I don’t waste words in this article to guide you.
Now go to the root folder of your laravel app and run:
fly launch
When asked if you want to deploy now, say No. Fly should have already detected that your application uses Laravel and configured accordingly.
If you have other environment variables to set, you can edit the fly.toml
file and add them. Also replace APP_URL
with the URL your app will be served on (by default, “https://
For example:
# fly.toml app configuration file generated for creaboutique on 2023-09-20T17:27:40+02:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = "MyLaravelApp"
primary_region = "cdg"
swap_size_mb = 512
console_command = "php /var/www/html/artisan tinker"
[build]
[build.args]
NODE_VERSION = "18"
PHP_VERSION = "8.1"
[deploy]
release_command = "php /var/www/html/artisan migrate --force"
[env]
APP_ENV = "production"
APP_NAME = "MyLaravelApp"
APP_URL = "MyLaravelApp.dezeiraud.com"
DB_CONNECTION = "pgsql"
DB_HOST = "db.xxxxxxxxxxxxxx.supabase.co"
DB_DATABASE = "postgres"
DB_PORT = "5432"
LOG_CHANNEL = "stderr"
LOG_LEVEL = "info"
MAIL_ENCRYPTION = "tls"
MAIL_FROM_ADDRESS = "[email protected]"
MAIL_FROM_NAME = "MyLaravelApp"
MAIL_HOST = "smtp.gmail.com"
MAIL_MAILER = "smtp"
MAIL_PORT = "587"
MAIL_USERNAME = "[email protected]"
SESSION_DRIVER = "cookie"
SESSION_SECURE_COOKIE = "true"
[processes]
app = ""
[[mounts]]
source = "storage_dir"
destination = "/var/www/html/storage"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ["app"]
You can copy this configuration, obviously changing with your values. Also, this configuration automatically run the Laravel migration after each deploy.
You can also see in [[mount]]
the configuration of a volume.
Like you can see, we don’t set directly secrets in the [env]
section. For that, we have a fly command fly secrets set
. For example:
fly secrets set DB_USERNAME=your-username DB_PASSWORD=your-password MAIL_PASSWORD=your-password
Database
The next step is to setup your Supabase postgres database in the environement. For that, register and login to your Supabase dashboard, create a new project. Then, Project settings > Database. You can find there Connection info needed for your Laravel app. Copy / Past this values in the appropriate env config and DB_USERNAME
, DB_PASSWORD
with fly command fly secrets set
. Scroll down in the supabase database settings and check Enforce SSL on incoming connections. It is very important for security. And voila! It is okay for the database.
Now, we need to setup workers to run Laravel queue. This step is optionnal, depending if your Laravel app used queue or not. From your project root folder, you can see a .fly
folder. Open it and create a new file worker.conf
next to entrypoint.sh
with this content:
[program:worker]
priority=5
autostart=true
autorestart=true
stdout_events_enabled=true
stderr_events_enabled=true
command=php /var/www/html/artisan queue:listen
user=www-data
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
Storage
For the storage, it is a bit more complicated. First, in .fly/scripts/caches.sh
add storage:link
line at the bottom:
#!/usr/bin/env bash
/usr/bin/php /var/www/html/artisan config:cache --no-ansi -q
/usr/bin/php /var/www/html/artisan route:cache --no-ansi -q
/usr/bin/php /var/www/html/artisan view:cache --no-ansi -q
/usr/bin/php /var/www/html/artisan storage:link --no-ansi -q
Then, next to .fly/scripts/caches.sh
, create a new file called 00-volumes.sh
with this content:
if [ ! -d "/var/www/html/storage/app" ]; then
if [ ! -d "/var/www/html/storage_" ]; then
echo "could not find storage_ dir to copy to volume"
exit 1
else
cp -r /var/www/html/storage_/. /var/www/html/storage
rm -rf /var/www/html/storage_
fi
fi
This script is important for the first deployment. It allow us to paste the storage folders in the volume. Like you can see in it, you need to create a copy of your storage folder at the root of your laravel project called storage_. Next to the .env
folder for example. It is necessary because if not, the storage folder will be override by the virtual storage of the docker container and your app crash.
You also need to create the volume, for that run fly volumes create storage_dir --region ams --size 1
(remember to change the region if needed and the size in Go).
fly deploy
Now it’s time to run `fly deploy“. This command may take some time because it creates the Docker image for your project. Some informations can be prompted the first time during this step. Be warned. And after some times, the first deployement is the longest, your app is accessible on internet in a subdomain of fly! You can monitore everything from your fly.io dashboard.
By default, the shared-cpu-1x is not used, but a more expensive one, you can check that with fly scale show
. To change that, very simple, run fly scale vm shared-cpu-1x --memory 256
and deploy again.
Next step, but it doesn’t concern the deployment of an Laravel app only on fly.io, is the setup of a custom domain, you have multiple way to do it, check this documentation.