Max Schmitt

October 1 2024

Deploying Laravel with SQLite on Dokku

For a recent weekend project, I built a simple Laravel application that I deployed on Dokku. The application has the following features:

  • It uses SQLite as the database
  • It accepts file uploads and stores them locally in the storage directory
  • Serves public file uploads
  • It processes uploads with a job
  • Uses Puppeteer while processing uploads

Getting this deployed using Dokku was a bit of a challenge. Here are some of the key steps I took to get it working:

Setup Buildpacks

Dokku uses buildpacks to build and deploy applications. For Laravel, we need to use the PHP buildpack. We also need the Node.js buildpack for building the frontend assets and the Puppeteer buildpack for the Puppeteer dependency.

.buildpacks

https://github.com/jontewks/puppeteer-heroku-buildpack
https://github.com/heroku/heroku-buildpack-php
https://github.com/heroku/heroku-buildpack-nodejs

Configure PHP via php.ini

We'll add a php.ini file to the root of the project to enable the SQLite PHP extensions and increase the file upload size.

php.ini

extension=sqlite3
extension=pdo_sqlite
upload_max_filesize=50M
post_max_size=50M

Configure NGINX via nginx_app.conf

We'll add an nginx_app.conf file to the root of the project.

This confiruation is needed to rewrite all requests to index.php and also increase the file upload size on the NGINX layer.

nginx_app.conf

client_max_body_size 50M;
location / {
# try to serve file directly, fallback to rewrite
try_files $uri @rewriteapp;
}
location @rewriteapp {
# rewrite all to app.php
rewrite ^(.*)$ /index.php$1 last;
}

Customize Procfile

The Procfile defines the processes that Dokku will run for the app. Here's what we need:

  • Run migrations on release (with custom PHP configuration)
  • Run the web server with custom NGINX and PHP configuration
  • Run the queue worker (with custom PHP configuration)

Procfile

release: php -c php.ini artisan migrate --force
web: vendor/bin/heroku-php-nginx -C nginx_app.conf -i php.ini public/
worker: php -c php.ini artisan queue:work

Scale Worker Process

By default, Dokku will not run the worker process even if it's defined in our Procfile. We need to tell Dokku, how many instances of the worker process we want to run.

$ dokku ps:scale <my-app> worker=1

Set Environment Variables

Two important environment variables that we need to set are APP_KEY and APP_URL:

dokku config:set <my-app> APP_KEY=$(php artisan key:generate --show --no-ansi)
dokku config:set <my-app> APP_URL=<url>

Create Persistent Storage Outside the Container

The app needs to store its database and files outside the container. Otherwise they would be lost when the container is restarted.

First, create a storage directory and then mount it to the app's storage directory.

$ dokku storage:ensure-directory <my-app> storage
$ dokku storage:mount <my-app> /var/lib/dokku/data/storage/<my-app>/storage:/app/storage

Move SQLite Database to Persistent Storage

By default the SQLite database on Laravel is stored at database/database.sqlite. For deployment on Dokku, we need to move it to the persistent storage:

config/database.php

return [
'connections' => [
'sqlite' => [
'database' => env('DB_DATABASE', storage_path('database.sqlite')),
// ...
]
]
// ...
];

Create Storage Folders and Link Public Disk

To make sure the storage folders exist, we can add predeploy and postdeploy scripts to our app.json file.

We also want to make the public disk available to the web, so we need to link the storage directory to the public directory.

I found a very useful GitHub Gist that had the perfect scripts ready.

predeploy.sh

#!/bin/bash
set -eo pipefail;
# Remove old symlink in public/storage if exists
[[ -r public/storage ]] && rm public/storage
# Create storage paths if missing in container.
# NOTE: Persistent storage still not mounted in predeploy step.
mkdir -p storage/app/public
mkdir -p storage/framework/{cache,sessions,testing,views}
mkdir -p storage/logs
# Create storage symlink in predeploy linking to container, not persistent storage.
# Link target will be substituted when persistent storage is mounted.
php -c php.ini artisan storage:link

postdeploy.sh

#!/bin/bash
set -eo pipefail;
# Create storage paths if missing in persistent storage.
mkdir -p storage/app/public
mkdir -p storage/framework/{cache,sessions,testing,views}
mkdir -p storage/logs
# Link already created in predeploy.
# Run for a confirmation message that it was created successfully.
php -c php.ini artisan storage:link

Let's add both of these scripts to our app.json file. Don't forget to run chmod +x predeploy.sh postdeploy.sh to make them executable.

app.json

{
"scripts": {
"dokku": {
"predeploy": "php -c php.ini artisan storage:link",
"postdeploy": "php -c php.ini artisan storage:link"
}
}
}

Conclusion

I'm having a lot of fun playing around with Laravel. The Dokku deployment is pretty complicated and took me several hours to get right.

Hopefully this article saved you a bit of time.

I'm not a Laravel expert so if you have any tips on how to make the setup better, please let me know!