Docker container for Laravel and PHP projects. Graceful shutdown. No-python supervisor. No-SSH.
You can add your own one-time, start up scripts to /platform/start-container.d/
If any script in start-container.d
exists with a return code other than 0, the
container will fail to start.
You can change any PHP ini by bind mounting a file to /usr/local/etc/php-fpm.d/
or
using your container orchestrator's config file functionality.
- LARAVEL_WORK_DIR (defaults to /app)
- LARAVEL_HORIZON_WORK_DIR (defaults to /app)
- LARAVEL_QUEUE_WORK_DIR (defaults to /app)
- LARAVEL_MIGRATE_COMMAND (defaults to "/usr/local/bin/php artisan migrate --force")
- LARAVEL_SCHEDULER_COMMAND (defaults to "/usr/local/bin/php artisan schedule:run")
- ENABLE_MIGRATIONS
- ENABLE_CONFIG_CACHE
- ENABLE_CHANGE_OWNER
- ENABLE_ARTISAN_SCHEDULER (creates /etc/cron.d/laravel-artisan-schedule-run)
One of the start-up scripts will cache the config with artisan. You can enable
this step by setting the environment variable
ENABLE_CONFIG_CACHE
to any value.
The script /platform/start-queue
can be used to start artisan queue:work
set LARAVEL_QUEUE_WORK_COMMAND
with your desired_commands
---
version: '3.7'
services:
laravel:
image: your-docker-repo/your-app-name:latest-prod
queue:
image: markkimsal/php-platform:8.0-nginx-fpm
entrypoint: /platform/start-queue
environment:
LARAVEL_QUEUE_WORK_DIR: /app
LARAVEL_QUEUE_WORK_COMMAND: /usr/local/bin/php artisan queue:work default --tries=1 --sleep=3
The script /platform/start-horizon
can be used to call only artisan:horizon and no other processes.
You can deploy the same built app container with 2 different command
values to get 2 different
container behaviors.
---
version: '3.7'
networks:
traefik_ingress:
external: true
services:
laravel:
image: your-docker-repo/your-app-name:latest-prod
networks:
- 'traefik_ingress'
deploy:
endpoint_mode: dnsrr
mode: replicated
replicas: 3
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_ingress"
- "traefik.http.routers.laraveltest.rule=Host(`app.localhost.test`)"
- "traefik.http.routers.laraveltest.entrypoints=websecure"
- "traefik.http.routers.laraveltest.tls=true"
- "traefik.http.routers.laraveltest.tls.certresolver=myresolver"
- "traefik.http.routers.laraveltest.service=laraveltest"
- "traefik.http.services.laraveltest.loadbalancer.server.port=8080"
horizon:
image: your-docker-repo/your-app-name:latest-prod
entrypoint: /platform/start-horizon
deploy:
endpoint_mode: dnsrr
mode: replicated
replicas: 2
environment:
LARAVEL_HORIZON_WORK_DIR: /app
If you want to run horizon under the supervisor runit
you can add an environment variable START_HORIZON_AS_SERVICE=yes
to your docker container by any means.
The entrypoint /platform/start-cron
can be used to run cron, with ENABLE_ARTISAN_SCHEDULER
environment variable
you can run artisan schedule:run
every minute.
---
version: '3.7'
services:
scheduler:
image: your-docker-repo/your-app-name:latest-prod
entrypoint: /platform/start-queue
environment:
ENABLE_ARTISAN_SCHEDULER: 1
You can change the id of the www-data
user to match any owner of files on your production system.
On your local development system, you can set WWWUID and WWWGID to 1000 so there are no ownership problems between your code and the running www-data user. (Note, this is only an issue on Gnu/Linux)
Set WWWUID and WWWGID like this:
---
version: '3.7'
services:
queue:
image: markkimsal/php-platform:8.0-nginx-fpm
command: /platform/start-queue
environment:
LARAVEL_QUEUE_WORK_DIR: /app
LARAVEL_QUEUE_WORK_COMMAND: /usr/local/bin/php artisan queue:work default --tries=1 --sleep=3
WWWUID: '${WWWUID:-33}'
WWWGID: '${WWWGID:-33}'
I found that each extension barely takes up any more disk space or memory. If you don't want them, attach blank files to the container ini files when you deploy.
PHP Version - flavor | 7.3-nginx-fpm | 7.3-tools | 7.4-nginx-fpm | 7.4-tools | 8.0-nginx-fpm | 8.0-tools | 8.1-nginx-fpm | 8.1-tools |
---|---|---|---|---|---|---|---|---|
BCMATH | X | X | X | X | X | X | X | X |
Intl | X | X | X | X | X | X | X | X |
gd | X | X | X | X | X | X | X | X |
mysql | X | X | X | X | X | X | X | X |
pgsql | X | X | X | X | X | X | X | X |
sqlite3 | X | X | X | X | X | X | X | X |
readline | X | X | X | X | X | X | X | X |
memcached (igbinary) | X | X | X | X | X | X | X | X |
redis | X | X | X | X | X | X | X | X |
xdebug | X | X | X | X | X | X | X | X |
zip | X | X | X | X | X | X | X | X |
SOAP | X | X | X | X | X | X | X | X |
SSH2 | X | X | X | X | X | X | X | X |
pcntl | X | X | X | X | X | X | X | X |
sysvsem | X | X | X | X | X | X | X | X |
sysvshm | X | X | X | X | X | X | X | X |
sysvmsg | X | X | X | X | X | X | X | X |
lib sodium | X | X | X | X | X | X | X | X |
FFI | X | X | X | X | X | X | ||
nginx | X | X | X | X | X | X | X | X |
yarn | X | X | X | |||||
nodejs-15.x | X | X | X | |||||
composer2 | X | X | X | X | ||||
composer1.10 | X | X | X | X | ||||
deployer - 6 | X | X | X | X | ||||
deployer - 7 | X | |||||||
altax | X | X | X | |||||
git | X | X | X | X | ||||
jq | X | X | X | X | ||||
unzip | X | X | X | X | ||||
openssh | X | X | X | X |
Having an image that can also run tests and build your image as part of your CI pipeline is useful. I could not find an image of nodejs that also had git, and at least one of my node dependencies required git to install so... here we are with nodejs 15 baked into this image.
Sometimes composer complains when you don't have a host OS unzip
program, so that's been added to the tools variant.
Also, for deploying with PHP deployer, I found that having ssh, unzip, git are usefull or required.
I used to use altax
for remote deployment so that's also on there.
I can't find an image on google cloud build that has jq
, so I installed jq
as well.
Runit is a small, c process supervisor. It tries to restart failed processes until you tell it to stop. The problem with runit is that you cannot send a signal that will gracefully stop all managed services. You must stop all services "by-hand", which is weird. You'd think that a simple SIGSTOP could initiate a graceful shutdown, but there's no way to do this. Runit (or runsv or runsvdir) will respond to TERM signals mostly by dieing immediately and letting children float to PID 1 and get reaped.
There is a small bash file that - in combination with dumb-init
- will act as pid 1, take docker stop
SIGQUIT or SIGTERM and gracefully try
to shutdown the running services. This is essentially what the /sbin/my_init
program from phusion/baseimage does.
Update the all Dockerfiles
php ./make-versions.php
Build a single image images with
bash ./build-image.sh 8.0
If you are on a Mac M1 (or other ARM environments)
export DOCKER_DEFAULT_PLATFORM=linux/arm64
bash ./build-image.sh 8.0
I think for RaspberryPi it might be
export DOCKER_DEFAULT_PLATFORM=linux/arm/v7
bash ./build-image.sh 8.0