Skip to content

Deploy ​

The files that put the site on a server, written from the site as it is: a server script, cloud-init, a Deployer recipe, a CI workflow.

kitloom/deploy

The deploy kit. One command writes the files that put the site on a server, filled in from the site as it is — which admin and at which path, whether a theme is built with npm, whether the worker runs Horizon, whether the database is WordPress's — so nothing of the deploy is typed from a page of documentation and got wrong for this site:

bash
php artisan kitloom:deploy-kit                  # the targets
php artisan kitloom:deploy-kit server           # one VPS on Ubuntu 24.04
php artisan kitloom:deploy-kit laravel-cloud    # cloud.laravel.com
php artisan kitloom:deploy-kit forge            # Laravel Forge or Ploi on any VPS
php artisan kitloom:deploy-kit docker           # any Docker host: compose, Coolify, AWS ECS / App Runner
php artisan kitloom:deploy-kit digitalocean-app # DigitalOcean App Platform

The files are plain text in the project, to read, edit and commit. Run the kit again after a change — a Vue theme added, Horizon installed, another domain — and it rewrites its own files and keeps the ones you edited. Nothing of the package runs while the site serves pages.

The server target ​

One server on Ubuntu 24.04 — a DigitalOcean Droplet, a Hetzner server, an AWS EC2 instance or Lightsail — with Nginx, PHP-FPM, MySQL on the same server (or a managed one elsewhere) and the site deployed in releases, so a deploy never serves half a site and can be rolled back.

FileWhat it is
deploy/server.shSets the server up, run once as root: PHP-FPM with the extensions, Nginx with the site's server block, MySQL with the site's database and user, Composer with your kitloom token (and the Nova license on a Nova site), Node.js when a theme needs it, Redis when Horizon does; the deploy user that owns the site and runs PHP; the site's folder and its .env with a key and the database password made up; the scheduler in cron; the worker as a systemd service; a certificate from Let's Encrypt. Safe to run again: what is there is left, what is missing is added
deploy/cloud-init.yamlThe same script run by cloud-init at the server's first boot — a Droplet's User data, Hetzner's Cloud config, an EC2 instance's User data. Fill in the credentials in a copy; don't commit the copy
deploy.phpA Deployer recipe: each release in a folder of its own, composer install, the themes built, optimize, migrate, then current switched to it, PHP-FPM reloaded, the worker restarted, and the front page and the admin's login page asked for a 200. dep rollback switches back to the release before. dep kitloom:user makes the first administrator
.github/workflows/deploy.ymlEvery push to main runs the recipe, with one secret, a private SSH key. --ci=gitlab writes .gitlab-ci.yml instead, --ci=none neither
deploy/README.mdThe steps above for this site, in order

The kitloom token is saved on the server by server.sh, in the deploy user's Composer configuration, and nowhere in the repository: CI needs only the SSH key.

The first time ​

  1. composer require --dev deployer/deployer, and commit the kit's files.
  2. A server with 2 GB of memory (4 GB for a shop or Horizon) and deploy/cloud-init.yaml as its user data — or scp deploy/server.sh root@<ip>: and ssh root@<ip> bash server.sh, which asks for the credentials.
  3. The DNS record of the domain to the server, then the certificate: certbot --nginx -d example.com -d www.example.com --redirect as root (server.sh takes it itself when the record is already there).
  4. The server's deploy key, printed at the end of server.sh (or /var/log/kitloom-server.log with cloud-init), as a read-only deploy key of the repository.
  5. vendor/bin/dep deploy, then vendor/bin/dep kitloom:user.

A site moving from WordPress loads WordPress's database into the server's MySQL instead of making an administrator; DB_PREFIX in the server's .env (/var/www/<domain>/shared/.env) is its $table_prefix. The mailer, the modules' keys and the rest of Configuration go into that .env too; a deploy keeps it.

The laravel-cloud target ​

Laravel Cloud keeps its settings in its dashboard, not in files, so the kit writes the checklist with this site's values and the variables to paste:

FileWhat it is
deploy/README.mdThe checklist: the application and its PHP, the resources (Laravel MySQL, the public media bucket, a private bucket when Import & Export or Backup use one, Valkey with Horizon), the build commands with your token (and Nova's), the deploy command, the scheduler and the worker, the first deploy, backups
deploy/laravel-cloud.envThe environment variables next to what Cloud fills in: MEDIA_DISK=media, MEDIA_UPLOADS_URL to fill, IMPORT_EXPORT_DISK=private when the module is there, KITLOOM_MODULES_CACHES=deploy, the queue on Valkey with Horizon, DB_PREFIX for a WordPress database

Cloud resets the file system on every deploy and each replica has its own, so the uploads live in a bucket and the caches are built in the build; the README says why each line is there. The S3 adapter the bucket needs is asked for (composer require league/flysystem-aws-s3-v3) only while it is missing.

The forge target ​

Laravel Forge and Ploi set the server up and deploy on a push; what they cannot know is kitloom's:

FileWhat it is
deploy/forge.sh, deploy/ploi.shThe deploy script to paste: composer install, the themes built when a theme needs it, migrate, optimize, PHP-FPM reloaded, the worker restarted the way it runs (queue:restart or horizon:terminate)
deploy/forge.envThe lines to add to the site's environment under what Forge filled in: the theme, the prefix, the queue, the mailer to set
deploy/README.mdThe checklist of their screens: the server, the token as the server's user, the site, the environment, the deploy script, the PHP upload size, the scheduler, the worker as a daemon, SSL, the first administrator

A Forge deploy updates the site in place, a few seconds of a half-updated site; for releases switched in one move the server target's Deployer recipe runs on a Forge server too.

The docker target ​

One image for any Docker host — a VPS with docker compose, Coolify, AWS ECS or App Runner: PHP-FPM and Nginx in one container, the packages installed with the kitloom token as a build argument of a stage that is not in the image, the themes built in; the same image runs the worker and the scheduler with another command.

FileWhat it is
Dockerfilephp:<version>-fpm with the extensions, Nginx and supervisord; a vendor stage with COMPOSER_AUTH (the token never reaches the image's history); a stage with Node for the themes when a theme needs it (PHP stays there: a TypeScript theme asks it for the page types); the image with the entrypoint, port 8080, a health check on /up
docker/nginx.conf, php.ini (uploads, OPcache without file checks), supervisord.conf, entrypoint.sh (the caches from the container's environment, migrations and storage:link for the site, any command as www-data for the worker and the scheduler; a managed database's CA from DB_CA_CERT), auth.json.example
.dockerignorevendor, node_modules, the built themes, the environment and the credentials stay out of the build
compose.ymlOne host: the site on port 80, the worker, the scheduler, MySQL 8.4, Redis with Horizon; volumes for the uploads and the database; COMPOSER_AUTH from the shell
deploy/docker.envThe environment of every container, and MySQL's; to fill in and keep out of the repository
deploy/README.mddocker compose on one host, and what a platform that runs the image needs: the uploads on S3, HEALTH_EPHEMERAL_DISK, the worker and the scheduler as services, a private disk for Import & Export

The digitalocean-app target ​

The docker image and .do/app.yaml, an App Platform spec for doctl apps create: the web service, the worker and the scheduler as components of the same image, built from the GitHub repository on every push; a managed MySQL bound into the environment (${db.HOSTNAME} and the rest, the CA certificate through the entrypoint); the uploads in a Space behind its CDN; Valkey with Horizon; the domain. The secrets — APP_KEY, COMPOSER_AUTH for the build, the Space's key and secret — are filled in the dashboard, so the spec can be committed. deploy/README.md has the steps and what the platform's reset disk asks of the site.

Options ​

Option
--domain=The site's domain; by default the host of APP_URL, or example.com while that is a local address
--repository=The Git repository the server clones; by default the origin remote
--php=The PHP the server gets; by default the lowest the project's composer.json allows (^8.4 — 8.4)
--ci=server only: github (default), gitlab or none
--forceRewrite files you edited, or files the kit did not write
--path=Write into another folder

What it reads off the site ​

Where it shows
The admin — Nova or Filament, and its pathserver.sh saves Laravel's Composer credential for Nova; deploy.php checks the admin's login page after a deploy
The themes — a Vue or React theme installed, or Blade onlyNode.js on the server and npm run build:themes in the release, or neither
Horizon installed and onRedis on the server, QUEUE_CONNECTION=redis, the worker runs php artisan horizon
Sentry installedA SENTRY_LARAVEL_DSN line to fill in .env
The database's prefixDB_PREFIX in .env, so a WordPress database is read as it is
Import & Export, Backup installedA private bucket on Laravel Cloud, and its disk in the variables
The S3 adapter installedLaravel Cloud's and App Platform's checklists ask for it only while it is missing
The repository on GitHub.do/app.yaml names it (owner/name); another host leaves a placeholder and a step

deploy/.kit.json remembers what the kit wrote, as a hash per file. Keep it with the files: it is how the next run tells its own files from yours.

For developers ​

A target is a Kitloom\Deploy\Kit\Target — its name, its options, the files it writes for a SiteFacts, the steps left — given to Targets in the provider. Stubs in stubs/<target>/ use [[value]], [[#flag]]…[[/flag]] and [[^flag]]…[[/flag]] (Kit\Stub); the flags come from SiteFacts::variables(). Tests render the targets with facts built by hand and run bash -n and php -l over what comes out.

kitloom — modules and themes for Laravel.
Contact · Pricing · Services · License · Refunds · Privacy · Community · YouTube · [email protected]