Skip to content

Docker: compose, App Platform, AWS ECS and App Runner, Coolify ​

One image of the site for any host that runs containers: a VPS with docker compose, Coolify, Fly.io, AWS ECS or App Runner, DigitalOcean's App Platform. PHP-FPM and Nginx run in one container; the same image runs the worker and the scheduler with another command. What the server needs and where else to host: Deployment.

With the deploy kit ​

bash
php artisan kitloom:deploy-kit docker --domain=example.com
FileWhat it is
Dockerfilephp:8.4-fpm with the extensions, Nginx and supervisord; a vendor stage that installs the packages with COMPOSER_AUTH as a build argument — the stage is not in the image, so the token never reaches its history; a stage with Node that builds the Vue and React themes when a theme needs it (PHP stays there: a TypeScript theme asks it for the page types), their SSR bundles copied into the image with Node's binary to run them; the image with the entrypoint, port 8080 and a health check on /up
docker/The container's nginx.conf, php.ini (uploads up to 64 MB, OPcache without file checks, on for the console too), supervisord.conf (PHP-FPM, Nginx and, with a Vue or React theme, the SSR server php artisan inertia:start-ssr), entrypoint.sh (Laravel's caches from the container's environment, the migrations and storage:link for the site, any command as www-data for the worker and the scheduler, a managed database's CA certificate from DB_CA_CERT), auth.json.example for the token
.dockerignorevendor, node_modules and the built themes are made inside the build; the environment and the credentials never go in
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
deploy/docker.envThe environment of every container, and MySQL's: to fill in and keep out of the repository
deploy/README.mdThe steps for this site, and what a platform that runs the image needs

One host with docker compose ​

  1. cp docker/auth.json.example docker/auth.json, fill in your kitloom customer name and token (and the Nova license on a Nova site); add docker/auth.json and deploy/docker.env to .gitignore.

  2. deploy/docker.env: APP_KEY (php artisan key:generate --show), the database passwords, a real mailer.

  3. Build and start:

    bash
    COMPOSER_AUTH="$(cat docker/auth.json)" docker compose up -d --build

    The app container runs the migrations once the database answers, then serves on port 80; the worker and the scheduler start once it is healthy. docker compose logs -f watches it come up.

  4. The first administrator — or WordPress's database loaded into the db container instead, with DB_PREFIX in deploy/docker.env:

    bash
    docker compose exec app php artisan kitloom:user --login=admin [email protected] --no-interaction
  5. HTTPS: a reverse proxy in front of port 80 — Caddy or Traefik in a container of their own, or the host's Nginx — with APP_URL as the address it answers on.

A new release is the same docker compose up -d --build after a pull; compose replaces the containers one by one. The uploads stay in the uploads volume, the database in db.

A platform that runs the image ​

ECS, App Runner, App Platform, Coolify, Fly.io: the image is built from the Dockerfile with COMPOSER_AUTH as a build-time variable, and the platform gives the containers their environment — deploy/docker.env without the MYSQL_* lines, the database's address from the platform. Three things change without a shared disk:

  • The uploads go to a bucket: MEDIA_DISK=s3 with the AWS_* variables of S3 or Spaces (composer require league/flysystem-aws-s3-v3 when it is not installed yet), and HEALTH_EPHEMERAL_DISK=true, so Site Health checks that nothing is left on a container's disk.
  • The worker and the scheduler are services of the same image with php artisan queue:work --sleep=3 --tries=3 --max-time=3600 (or php artisan horizon) and php artisan schedule:work as their command: the entrypoint runs any command as the web user, with the caches built first.
  • Import & Export reads the WXR file from IMPORT_EXPORT_DISK, and the worker that imports it runs in another container: a private bucket for it, as a private disk added to config/filesystems.php, and IMPORT_EXPORT_DISK=private.

KITLOOM_MODULES_CACHES=deploy stays: a module switched on the Modules screen asks for new containers, which build their caches with every module on (Enabling & Disabling).

DigitalOcean App Platform ​

bash
php artisan kitloom:deploy-kit digitalocean-app --domain=example.com

The same image, and .do/app.yaml: the web service, the worker and the scheduler as components of the image, built from the GitHub repository on every push; a managed MySQL cluster attached by name and 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.

In order: a Space for the uploads with its CDN and an access key; the database cluster, made before the app, since App Platform's own development databases are Postgres only (doctl databases create example-com-db --engine mysql --region fra1 --size db-s-1vcpu-1gb); the repository connected to DigitalOcean; doctl apps create --spec .do/app.yaml; the secrets in Settings → App-Level Environment Variables, which starts the build; the first administrator from the web component's Console; the domain's DNS as Settings → Domains says. A change to the spec goes up with doctl apps update <app-id> --spec .do/app.yaml; a change to the code, with a push. deploy/README.md has every step for this site.

AWS ECS and App Runner ​

The image as above, pushed to ECR (aws ecr get-login-password | docker login …, docker build --build-arg COMPOSER_AUTH="$(cat docker/auth.json)" -t <account>.dkr.ecr.<region>.amazonaws.com/example-com ., docker push). App Runner runs it as one service from the image with the environment in its console; ECS as a task definition with three containers of the image — the site on port 8080, the worker and the scheduler with their command — behind a load balancer whose health check is /up. RDS for MySQL (its endpoint as DB_HOST; the RDS CA bundle as MYSQL_ATTR_SSL_CA if the parameter group requires TLS), S3 for the uploads, SES for the mail. Both builders pass COMPOSER_AUTH as a build argument; nothing of it is in the pushed image.

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