Deploy a Laravel Application

Getting an application running on Fly is essentially working out how to package it as a deployable image. Once packaged it can be deployed to the Fly infrastructure to run on the global application platform.

In this guide we'll learn how to deploy a Laravel application on Fly.

Prepare a Laravel App

Bring your own Laravel app, or create a new one!

If you want to start fresh, here's how to set up a new application. You'll need PHP 8+ and composer installed locally. You can check your PHP version using php --version.

composer create-project laravel/laravel fly-laravel
cd fly-laravel
php artisan serve

You should be able to visit http://localhost:8000 and see the home page.

Deploy to Fly.io

Install Fly

First, install flyctl, your Fly app command center, and sign up to Fly if you haven't already.

Launch

Next, we'll run fly launch to automagically configure your app for Fly.

The launch command adds a few files to your code base. Don't worry, it will ask before overwriting anything.

Here is what gets added:

  1. Dockerfile - Used to build a container image that is run in fly
  2. .dockerignore - Used to ensure certain files don't make its way into your repository
  3. fly.toml - Configuration specific to hosting on Fly
  4. docker - A directory containing configuration files for running Nginx/PHP in a container

Running fly launch (and later fly deploy) uses the Dockerfile to build a container image, copying your application files into the resulting image.

Fly doesn't care about the state of your git repository - it copies whatever files are present (except for files ignored by .dockerignore).

If you haven't already, go ahead and run fly launch!

  1. You'll be asked to provide secret APP_KEY. You can generate one using php artisan key:generate --show.
  2. When asked if you want to deploy now, say No.

If you have other environment variables to set, you can edit the fly.toml file and add them.

[env]
# Set any env vars you want here
# Caution: Don't add secrets here
APP_URL = "https://fly-hello-laravel.fly.dev"

Replace this with the URL your app will be served on (by default, "https://<your-app-name>.fly.dev").

For sensitive data, you can set secrets with the fly secrets set command:

fly secrets set SOME_SECRET_KEY=<the-value-from-your-env-file>

Deploy

Finally, run fly deploy to build and deploy your application!

You should be able to visit https://your-app-name.fly.dev and see the Laravel demo home page.

That's it! Run fly open to see your deployed app in action.

Try a few other commands:

  • fly logs - Tail your application logs
  • fly status - App deployment details
  • fly deploy - Deploy the application after making changes

CRON and Queues

You may need to run Laravel's scheduler or queue workers.

The included docker/supervisor.conf comes with presets for both!

Scheduler (CRON)

File docker/crontab defines a CRON task that runs the schedule:run command.

You can edit that file to add/remove cron tasks as needed.

To get CRON tasks running in your app, edit file docker/supervisor.conf and uncomment section [program:laravel-schedule]:

[program:laravel-schedule]
numprocs=1
autostart=true
autorestart=true
redirect_stderr=true
process_name=%(program_name)s_%(process_num)02d
command=/usr/sbin/crond -f -l 8
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

This will run crond the next time you deploy via fly deploy.

Queue Workers

The docker/supervisor.conf file also has a preset for running queue workers.

To enable those, uncomment the [program:laravel-queue] program:

[program:laravel-queue]
user=app
numprocs=1 ;numprocs=5
autostart=true
autorestart=true
redirect_stderr=true
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work sqs --sleep=3 --tries=3 --backoff=3 --max-time=3600
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

You'll want to edit the above to use the driver of your choice, or configure multiple worker processes.

Extra Credit

Your app is a special snowflake. Here are a few things you may want to customize further!

PHP Version

The Dockerfile installs PHP 8.0 by default. If you want PHP 8.1 or (bless your heart) PHP 7, you can find/replace instances of php8 to your version of choice.

For example, to update to PHP 8.1, you'll need to edit:

  1. Dockerfile - Change package names from php8 to php81 (e.g. php8-cli to php81-cli)
  2. docker/app.conf (if present) - Change listen to use php81-fpm.sock
  3. docker/php-fpm.conf (if present) - Change include to use file path /etc/php81
  4. server.conf - Change fastcgi_pass to pass to php81-fpm.sock
  5. supervisord.conf - Change [program:php8-fpm] (if present) to run command php-fpm81

If you need PHP 7, you can find other PHP versions (and matching Alpine container versions) here. Note that you may not be able to use alpine:edge as the base container.

Node Version

The Dockerfile uses fancy multi-stage builds to install your Node dependencies and build static assets.

You can change the Node version used in the Dockerfile by changing FROM node:14 to a version of your choice.

# Change this:
FROM node:14 as node_modules_go_brrr

# To whatever version you need:
FROM node:16 as node_modules_go_brrr

One last note: The node_modules directory ends up being excluded from your code base. If you need it, you'll have to adjust the Dockerfile:

# Keep this line
COPY --from=node_modules_go_brrr /app/public /var/www/html/public
# Add this line:
COPY --from=node_modules_go_brrr /app/node_modules /var/www/html/node_modules

Logging Stack Traces

By default, we set the Logging output to use a JsonFormatter. This makes the log output a bit cleaner, but has the trade-off of not showing you a full stack trace.

If you need to get the full stack trace, update your config/logging.php file and adjust the stderr channel to include a formatter_with configuration:

'stderr' => [
    'driver' => 'monolog',
    'level' => env('LOG_LEVEL', 'debug'),
    'handler' => StreamHandler::class,
    'formatter' => env('LOG_STDERR_FORMATTER'), // JsonFormatter
    'formatter_with' => [
        'includeStacktraces' => true,
    ],
    'with' => [
        'stream' => 'php://stderr',
    ],
],