Dokku with Laravel cookbook
Why Dokku rather than Forge or Ploi?
My reasons:
- π Zero downtime deploys baked-in
- π Each app is isolated by Docker
- πΈ No additonal costs or security risks
- βΎοΈ Not limited to PHP (anything deploys)
In comparison, Forge supports zero downtime deploys by making you subscribe to Envoyer (10$/mo) or reaching out for other hacks. Even then, such setup would be limited to PHP-FPM, making it impossible to run Octane or other languages/runtimes.
Setting an encryption key
Run this local command to receive a new encryption key:
php artisan key:generate --show
Then copy the key and set it remotely:
dokku config:set appname APP_KEY="YOUR_KEY"
SQLite as your database
Dokku stores persistent data in a “persistent” directory, so let’s create one:
dokku storage:ensure-directory appname-db
Then we can enter it to make a new production-ready WAL-mode database:
cd /var/lib/dokku/data/storage/appname-db
sqlite3 database.sqlite "
PRAGMA journal_mode = WAL;
PRAGMA auto_vacuum = INCREMENTAL;
PRAGMA page_size = 4096;
PRAGMA wal_autocheckpoint = 1000;
VACUUM;
"
Finally, let’s mount our directory to the app itself:
dokku storage:mount appname /var/lib/dokku/data/storage/appname-db:/app/database/sqlite
And set the required environment variable:
dokku config:set appname DB_DATABASE=/app/database/sqlite/database.sqlite
Octane for max performance (Dockerfile)
### Stage 1: Asset Build
FROM node:20 AS assets-builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
### Stage 2: Laravel Build
FROM composer:2 AS dependencies
WORKDIR /app
COPY . .
RUN composer install --no-dev --optimize-autoloader --no-interaction
### Stage 3: Final Image
FROM dunglas/frankenphp
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN install-php-extensions pcntl zip
WORKDIR /app
COPY . .
COPY --from=dependencies /app/vendor /app/vendor
COPY --from=assets-builder /app/public/build /app/public/build
EXPOSE 80
CMD ["php", "artisan", "octane:start", "--server=frankenphp", "--host=0.0.0.0", "--port=80", "--admin-port=2019"]
Queues
Procfile lets you define multiple processes for your app. Here’s mine:
web: php artisan octane:start --server=frankenphp --host=0.0.0.0 --port=80 --admin-port=2019
release: php artisan migrate --force
queue: php artisan queue:work
But, any new process won’t be activated until we scale it up:
dokku ps:scale appname queue=1
Server and provisioning
I prefer to use Debian 12 as it does not come with any extra bloat such as Snap on Ubuntu and thus uses minimum resources. It’s been proven to be super stable and run for years without reboots.
apt install unattended-upgrades -y
Disable password login for SSH:
sed -i -E 's/#?PasswordAuthentication (yes|no)/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl reload ssh
(assuming you have your SSH keys in authorized_keys)
Optimizations
You will get a much better resource utilization by enabling keepalive:
dokku nginx:set --global proxy-keepalive 64
Without it, there will consistently be a new TCP connection opened between Nginx and the Docker container, eventually making it hit the machineβs Linux configuration limit. Setting a keepalive to your Nginx proxy configuration prevents it.
Troubleshooting
Reset port configuration to a default state:
dokku ports:clear appname
dokku ports:set appname http:80:80
dokku ports:set appname https:443:80
dokku ps:restart appname