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.
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:
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://
# 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
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_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
For the storage, it is a bit more complicated. First, in
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).
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.