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-buildpackhttps://github.com/heroku/heroku-buildpack-phphttps://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=sqlite3extension=pdo_sqliteupload_max_filesize=50Mpost_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 rewritetry_files $uri @rewriteapp;}location @rewriteapp {# rewrite all to app.phprewrite ^(.*)$ /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 --forceweb: 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/bashset -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/publicmkdir -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/bashset -eo pipefail;# Create storage paths if missing in persistent storage.mkdir -p storage/app/publicmkdir -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!