Initial commit: multi-version PHP+Nginx Docker image for Laravel
This commit is contained in:
commit
7ccb8b94fe
|
|
@ -0,0 +1,13 @@
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
.gitignore
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
LICENSE
|
||||||
|
docker-bake.hcl
|
||||||
|
scripts/build.sh
|
||||||
|
scripts/publish.sh
|
||||||
|
docker-compose*.yml
|
||||||
|
.env*
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
# ===========================================================================
|
||||||
|
# docker-laravel — Multi-version PHP + Nginx image for Laravel
|
||||||
|
#
|
||||||
|
# Build args:
|
||||||
|
# PHP_VERSION — 7.4 | 8.0 | 8.1 | 8.2 | 8.3 | 8.4 | 8.5 (default: 8.4)
|
||||||
|
# NODE_MAJOR — Node.js major version (default: 22)
|
||||||
|
# ===========================================================================
|
||||||
|
ARG PHP_VERSION=8.4
|
||||||
|
FROM php:${PHP_VERSION}-fpm
|
||||||
|
|
||||||
|
ARG PHP_VERSION
|
||||||
|
|
||||||
|
LABEL maintainer="docker-laravel"
|
||||||
|
LABEL description="Laravel-optimized PHP-FPM + Nginx image"
|
||||||
|
LABEL php.version="${PHP_VERSION}"
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=UTC
|
||||||
|
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# System dependencies (single layer)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gnupg gosu curl wget ca-certificates zip unzip git \
|
||||||
|
supervisor sqlite3 libcap2-bin python3 pkg-config \
|
||||||
|
# GD
|
||||||
|
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libwebp-dev \
|
||||||
|
# PHP extension libs
|
||||||
|
libonig-dev libxml2-dev libzip-dev libicu-dev libcurl4-openssl-dev \
|
||||||
|
# PostgreSQL
|
||||||
|
libpq-dev \
|
||||||
|
# ImageMagick
|
||||||
|
libmagickwand-dev \
|
||||||
|
# Nginx + headers-more module
|
||||||
|
nginx libnginx-mod-http-headers-more-filter \
|
||||||
|
# Database clients
|
||||||
|
default-mysql-client \
|
||||||
|
# Ghostscript (PDF rendering)
|
||||||
|
ghostscript \
|
||||||
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# PHP extensions
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# GD configure flags changed between PHP 7.x and 8.0
|
||||||
|
RUN PHP_MAJOR=$(php -r "echo PHP_MAJOR_VERSION;") && \
|
||||||
|
if [ "$PHP_MAJOR" -ge 8 ]; then \
|
||||||
|
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \
|
||||||
|
else \
|
||||||
|
docker-php-ext-configure gd --with-freetype-dir=/usr --with-jpeg-dir=/usr --with-webp-dir=/usr; \
|
||||||
|
fi && \
|
||||||
|
docker-php-ext-install -j$(nproc) \
|
||||||
|
pdo_mysql \
|
||||||
|
pdo_pgsql \
|
||||||
|
mysqli \
|
||||||
|
mbstring \
|
||||||
|
exif \
|
||||||
|
pcntl \
|
||||||
|
bcmath \
|
||||||
|
gd \
|
||||||
|
sockets \
|
||||||
|
zip \
|
||||||
|
xml \
|
||||||
|
soap \
|
||||||
|
intl \
|
||||||
|
opcache
|
||||||
|
|
||||||
|
# PECL extensions (fail gracefully for bleeding-edge PHP)
|
||||||
|
RUN pecl install redis && docker-php-ext-enable redis || echo "NOTICE: redis unavailable for PHP ${PHP_VERSION}"
|
||||||
|
RUN pecl install ev && docker-php-ext-enable ev || echo "NOTICE: ev unavailable for PHP ${PHP_VERSION}"
|
||||||
|
RUN pecl install igbinary && docker-php-ext-enable igbinary || echo "NOTICE: igbinary unavailable for PHP ${PHP_VERSION}"
|
||||||
|
RUN pecl install imagick && docker-php-ext-enable imagick || echo "NOTICE: imagick unavailable for PHP ${PHP_VERSION}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# OPcache configuration (JIT enabled automatically for PHP 8.0+)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
COPY config/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
|
||||||
|
RUN PHP_MAJOR=$(php -r "echo PHP_MAJOR_VERSION;") && \
|
||||||
|
if [ "$PHP_MAJOR" -ge 8 ]; then \
|
||||||
|
{ echo ""; echo "; JIT (PHP 8.0+)"; echo "opcache.jit=1255"; echo "opcache.jit_buffer_size=128M"; } \
|
||||||
|
>> /usr/local/etc/php/conf.d/opcache.ini; \
|
||||||
|
echo "JIT enabled for PHP $(php -r 'echo PHP_VERSION;')"; \
|
||||||
|
else \
|
||||||
|
echo "JIT skipped (PHP $(php -r 'echo PHP_VERSION;') < 8.0)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ImageMagick PDF policy (for spatie/pdf-to-image etc.)
|
||||||
|
RUN IMGK_CONF=$(find /etc/ImageMagick* -name policy.xml 2>/dev/null | head -1) && \
|
||||||
|
if [ -n "$IMGK_CONF" ]; then \
|
||||||
|
sed -i 's/<policy domain="coder" rights="none" pattern="PDF" \/>/<policy domain="coder" rights="read|write" pattern="PDF" \/>/g' "$IMGK_CONF" && \
|
||||||
|
sed -i '/<\/policymap>/i\ <policy domain="coder" rights="read|write" pattern="LABEL" />' "$IMGK_CONF"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Custom PHP configuration
|
||||||
|
COPY config/php.ini /usr/local/etc/php/conf.d/99-custom.ini
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Composer
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Node.js + npm
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
ARG NODE_MAJOR=22
|
||||||
|
RUN mkdir -p /etc/apt/keyrings && \
|
||||||
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
|
||||||
|
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" \
|
||||||
|
> /etc/apt/sources.list.d/nodesource.list && \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends nodejs && \
|
||||||
|
npm install -g npm@latest && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Image optimization tools (spatie/image-optimizer) + ffmpeg
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
optipng pngquant gifsicle webp libavif-bin ffmpeg \
|
||||||
|
nano procps net-tools \
|
||||||
|
&& npm install -g svgo \
|
||||||
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Users & directories
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data
|
||||||
|
|
||||||
|
# PsySH config for artisan tinker
|
||||||
|
RUN mkdir -p /var/www/.config/psysh && chown -R www-data:www-data /var/www/.config
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Nginx configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
COPY config/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY config/default.conf /etc/nginx/sites-available/default
|
||||||
|
RUN rm -f /etc/nginx/sites-enabled/default && \
|
||||||
|
ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default && \
|
||||||
|
rm -f /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Supervisor configuration
|
||||||
|
# conf.d/ — core programs (php-fpm, nginx), override via volume mount
|
||||||
|
# laravel.d/ — generated at boot from ENABLE_* env vars
|
||||||
|
# custom.d/ — mount your own .conf files here
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
COPY config/supervisord.conf /etc/supervisor/supervisord.conf
|
||||||
|
COPY config/supervisor/php-fpm.conf /etc/supervisor/conf.d/php-fpm.conf
|
||||||
|
COPY config/supervisor/nginx.conf /etc/supervisor/conf.d/nginx.conf
|
||||||
|
RUN mkdir -p /etc/supervisor/conf.d /etc/supervisor/laravel.d /etc/supervisor/custom.d \
|
||||||
|
/var/log/supervisor /var/log/nginx /var/log/php
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Startup script
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
COPY scripts/start-container /usr/local/bin/start-container
|
||||||
|
RUN chmod +x /usr/local/bin/start-container
|
||||||
|
|
||||||
|
# Composer cache
|
||||||
|
RUN mkdir -p /.composer && chmod 0777 /.composer
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Environment variables for optional Laravel services
|
||||||
|
# Set to "true" to enable at container start
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
ENV ENABLE_QUEUE=false
|
||||||
|
ENV ENABLE_SCHEDULER=false
|
||||||
|
ENV ENABLE_HORIZON=false
|
||||||
|
ENV ENABLE_LARAVEL_PERMS=0
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["start-container"]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
# docker-laravel
|
||||||
|
|
||||||
|
Multi-version PHP + Nginx Docker image optimized for Laravel (9 – 13).
|
||||||
|
|
||||||
|
The image provides **PHP-FPM, Nginx, Composer, Node.js, Bun**, and common PHP extensions out of the box.
|
||||||
|
It does **not** contain any Laravel code — mount your project at `/var/www/html`.
|
||||||
|
|
||||||
|
## Supported PHP Versions
|
||||||
|
|
||||||
|
| Tag | PHP | Laravel |
|
||||||
|
|-----|-----|---------|
|
||||||
|
| `php7.4` | 7.4 | Legacy (< 9) |
|
||||||
|
| `php8.0` | 8.0 | 9 |
|
||||||
|
| `php8.1` | 8.1 | 9, 10 |
|
||||||
|
| `php8.2` | 8.2 | 9, 10, 11, 12 |
|
||||||
|
| `php8.3` | 8.3 | 10, 11, 12, 13 |
|
||||||
|
| `php8.4` | 8.4 | 11, 12, 13 |
|
||||||
|
| `php8.5` | 8.5 | 12, 13 |
|
||||||
|
| `latest` | 8.4 | (alias) |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build a single version
|
||||||
|
docker build --build-arg PHP_VERSION=8.4 -t docker-laravel:php8.4 .
|
||||||
|
|
||||||
|
# Build all versions
|
||||||
|
./scripts/build.sh
|
||||||
|
|
||||||
|
# Build only specific PHP versions
|
||||||
|
./scripts/build.sh 8.4 8.5
|
||||||
|
|
||||||
|
# Build all versions with buildx bake
|
||||||
|
docker buildx bake
|
||||||
|
|
||||||
|
# Build only actively-supported PHP versions
|
||||||
|
docker buildx bake active
|
||||||
|
```
|
||||||
|
|
||||||
|
## plug-n-pray 🙏
|
||||||
|
|
||||||
|
Generate a full Docker Compose boilerplate for any Laravel project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From your Laravel project root — one-liner:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/blax-software/docker-laravel/main/scripts/plug-n-pray.sh | bash
|
||||||
|
|
||||||
|
# With options:
|
||||||
|
./plug-n-pray.sh --php=8.4 --name=my-app --host=my-app.localhost --horizon
|
||||||
|
|
||||||
|
# Or via artisan (requires blax-software/laravel-workkit):
|
||||||
|
php artisan workkit:plug-n-pray
|
||||||
|
php artisan workkit:plug-n-pray --php=8.5 --horizon --no-mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/examples.md](docs/examples.md) for full usage examples.
|
||||||
|
|
||||||
|
## Usage in docker-compose.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: docker-laravel:php8.4
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/html
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
environment:
|
||||||
|
ENABLE_QUEUE: "true"
|
||||||
|
ENABLE_SCHEDULER: "true"
|
||||||
|
ENABLE_HORIZON: "false"
|
||||||
|
ENABLE_LARAVEL_PERMS: "1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Args
|
||||||
|
|
||||||
|
| Arg | Default | Description |
|
||||||
|
|-----|---------|-------------|
|
||||||
|
| `PHP_VERSION` | `8.4` | PHP version (7.4, 8.0 – 8.5) |
|
||||||
|
| `NODE_MAJOR` | `22` | Node.js major version |
|
||||||
|
|
||||||
|
## Runtime Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `ENABLE_QUEUE` | `false` | Start `artisan queue:work` via supervisor |
|
||||||
|
| `ENABLE_SCHEDULER` | `false` | Start `artisan schedule:work` via supervisor |
|
||||||
|
| `ENABLE_HORIZON` | `false` | Start `artisan horizon` via supervisor |
|
||||||
|
| `ENABLE_LARAVEL_PERMS` | `0` | Fix `storage/` and `bootstrap/cache/` permissions on boot |
|
||||||
|
|
||||||
|
## What's Included
|
||||||
|
|
||||||
|
- **PHP-FPM** with extensions: pdo_mysql, pdo_pgsql, mysqli, mbstring, exif, pcntl, bcmath, gd (freetype + jpeg + webp), sockets, zip, xml, soap, intl, opcache
|
||||||
|
- **PECL**: redis, ev, igbinary, imagick (graceful fallback if unavailable for a PHP version)
|
||||||
|
- **OPcache** with JIT auto-enabled on PHP 8.0+
|
||||||
|
- **Nginx** with headers-more module, optimized for Laravel
|
||||||
|
- **Composer** (latest)
|
||||||
|
- **Node.js** + npm
|
||||||
|
- **Image optimizers**: optipng, pngquant, gifsicle, webp, avif, svgo
|
||||||
|
- **ffmpeg**, **ghostscript**, **MySQL client**
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
start-container (entrypoint)
|
||||||
|
└─ supervisord
|
||||||
|
├─ php-fpm (always)
|
||||||
|
├─ nginx (always)
|
||||||
|
├─ queue.conf (if ENABLE_QUEUE=true)
|
||||||
|
├─ scheduler.conf (if ENABLE_SCHEDULER=true)
|
||||||
|
└─ horizon.conf (if ENABLE_HORIZON=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional supervisor configs are generated at runtime in `/etc/supervisor/laravel.d/`.
|
||||||
|
|
||||||
|
## Customizing Supervisor Programs
|
||||||
|
|
||||||
|
Every supervisor program lives in its own `.conf` file across three include directories:
|
||||||
|
|
||||||
|
| Directory | Purpose | How to customize |
|
||||||
|
|-----------|---------|------------------|
|
||||||
|
| `/etc/supervisor/conf.d/` | Core services (php-fpm, nginx) | Mount a replacement file to override |
|
||||||
|
| `/etc/supervisor/laravel.d/` | Queue, scheduler, horizon (auto-generated from `ENABLE_*` env vars) | Use env vars, or disable them and mount your own |
|
||||||
|
| `/etc/supervisor/custom.d/` | Empty — for your own programs | Mount a directory or individual files |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: blaxsoftware/laravel:php8.4
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/html
|
||||||
|
# Override php-fpm config (e.g. change pool settings)
|
||||||
|
- ./docker/php-fpm.conf:/etc/supervisor/conf.d/php-fpm.conf
|
||||||
|
# Add custom programs (e.g. reverb, octane, custom workers)
|
||||||
|
- ./docker/supervisor/:/etc/supervisor/custom.d/
|
||||||
|
environment:
|
||||||
|
ENABLE_QUEUE: "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
To **disable a core service** (e.g. nginx), mount an override with `autostart=false`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
; my-nginx-override.conf
|
||||||
|
[program:nginx]
|
||||||
|
command=/usr/sbin/nginx -g "daemon off;"
|
||||||
|
autostart=false
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
index index.php index.html;
|
||||||
|
root /var/www/html/public;
|
||||||
|
|
||||||
|
# Log to Docker (avoid writing to container FS)
|
||||||
|
access_log /dev/stdout;
|
||||||
|
error_log /dev/stderr warn;
|
||||||
|
|
||||||
|
client_max_body_size 500M;
|
||||||
|
|
||||||
|
# Add X-Forwarded-Proto header
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ [^/]\.php(/|$) {
|
||||||
|
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||||
|
fastcgi_param HTTP_PROXY "";
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
fastcgi_param PHP_SELF $fastcgi_script_name$fastcgi_path_info;
|
||||||
|
fastcgi_param SERVER_NAME $host;
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
user www-data;
|
||||||
|
worker_processes auto;
|
||||||
|
pid /run/nginx.pid;
|
||||||
|
|
||||||
|
# Send logs to Docker
|
||||||
|
error_log /dev/stderr warn;
|
||||||
|
include /etc/nginx/modules-enabled/*.conf;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
multi_accept on;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
# Custom headers
|
||||||
|
more_set_headers "Server: Laravel Proxy";
|
||||||
|
more_set_headers "X-Powered-By: Laravel Proxy";
|
||||||
|
|
||||||
|
# Security headers (safe behind Traefik)
|
||||||
|
more_set_headers "X-Frame-Options: SAMEORIGIN";
|
||||||
|
more_set_headers "X-Content-Type-Options: nosniff";
|
||||||
|
more_set_headers "Referrer-Policy: no-referrer-when-downgrade";
|
||||||
|
more_set_headers "X-XSS-Protection: 1; mode=block";
|
||||||
|
more_set_headers "Permissions-Policy: geolocation=(), microphone=(), camera=()";
|
||||||
|
|
||||||
|
# Gzip
|
||||||
|
gzip on;
|
||||||
|
gzip_comp_level 5;
|
||||||
|
gzip_min_length 256;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_types
|
||||||
|
text/plain text/css text/xml application/json application/javascript
|
||||||
|
application/x-javascript application/xml application/xml+rss
|
||||||
|
font/ttf font/otf image/svg+xml;
|
||||||
|
|
||||||
|
# Real IP from Traefik
|
||||||
|
set_real_ip_from 0.0.0.0/0;
|
||||||
|
real_ip_header X-Forwarded-For;
|
||||||
|
real_ip_recursive on;
|
||||||
|
|
||||||
|
# Buffers & timeouts for Laravel
|
||||||
|
client_max_body_size 50M;
|
||||||
|
client_body_buffer_size 128k;
|
||||||
|
client_header_timeout 30s;
|
||||||
|
client_body_timeout 30s;
|
||||||
|
send_timeout 30s;
|
||||||
|
|
||||||
|
# FastCGI settings for PHP (Laravel)
|
||||||
|
fastcgi_read_timeout 300;
|
||||||
|
fastcgi_buffers 16 16k;
|
||||||
|
fastcgi_buffer_size 32k;
|
||||||
|
|
||||||
|
# Logging (to Docker)
|
||||||
|
access_log /dev/stdout;
|
||||||
|
|
||||||
|
# Cache static assets aggressively (ideal for Laravel mix/vite builds)
|
||||||
|
map $sent_http_content_type $static_expires {
|
||||||
|
default off;
|
||||||
|
~*image/ 30d;
|
||||||
|
~*font/ 30d;
|
||||||
|
~*text/css 30d;
|
||||||
|
~*javascript 30d;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_headers_hash_max_size 1024;
|
||||||
|
proxy_headers_hash_bucket_size 128;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
include /etc/nginx/sites-enabled/*;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
; OPcache settings for Laravel (PHP 7.4+)
|
||||||
|
; JIT settings are appended automatically for PHP 8.0+ during build.
|
||||||
|
opcache.enable=1
|
||||||
|
opcache.enable_cli=1
|
||||||
|
|
||||||
|
; Memory — generous for large codebases
|
||||||
|
opcache.memory_consumption=256
|
||||||
|
opcache.interned_strings_buffer=32
|
||||||
|
opcache.max_accelerated_files=20000
|
||||||
|
|
||||||
|
; File validation — safe for development; disable for production
|
||||||
|
; (set validate_timestamps=0 and revalidate_freq=0 in production)
|
||||||
|
opcache.validate_timestamps=1
|
||||||
|
opcache.revalidate_freq=2
|
||||||
|
|
||||||
|
; Optimisation
|
||||||
|
opcache.optimization_level=0x7FFEBFFF
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
[PHP]
|
||||||
|
post_max_size = 2G
|
||||||
|
upload_max_filesize = 2G
|
||||||
|
memory_limit = 500M
|
||||||
|
variables_order = EGPCS
|
||||||
|
fastcgi.logging = Off
|
||||||
|
|
||||||
|
; Error Reporting
|
||||||
|
display_errors = Off
|
||||||
|
log_errors = On
|
||||||
|
error_log = /var/log/php/error.log
|
||||||
|
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||||
|
|
||||||
|
; OPcache Settings
|
||||||
|
; opcache.enable = 1
|
||||||
|
; opcache.memory_consumption = 128
|
||||||
|
; opcache.interned_strings_buffer = 16
|
||||||
|
; opcache.max_accelerated_files = 10000
|
||||||
|
; opcache.validate_timestamps = 1
|
||||||
|
; opcache.revalidate_freq = 2
|
||||||
|
; opcache.fast_shutdown = 1
|
||||||
|
; opcache.enable_cli = 1
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
[program:nginx]
|
||||||
|
command=/usr/sbin/nginx -g "daemon off;"
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
priority=10
|
||||||
|
startsecs=0
|
||||||
|
startretries=10
|
||||||
|
stdout_logfile=/proc/1/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/proc/1/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
[program:php-fpm]
|
||||||
|
command=/usr/local/sbin/php-fpm --nodaemonize
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
priority=5
|
||||||
|
stdout_logfile=/proc/1/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/proc/1/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/proc/1/fd/1
|
||||||
|
logfile_maxbytes=0
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
loglevel=info
|
||||||
|
|
||||||
|
[unix_http_server]
|
||||||
|
file=/var/run/supervisor.sock
|
||||||
|
chmod=0700
|
||||||
|
|
||||||
|
[rpcinterface:supervisor]
|
||||||
|
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||||
|
|
||||||
|
[supervisorctl]
|
||||||
|
serverurl=unix:///var/run/supervisor.sock
|
||||||
|
|
||||||
|
; ==========================================================================
|
||||||
|
; Include directories (order matters — later files can't override earlier ones,
|
||||||
|
; but you can mount over individual files in conf.d/)
|
||||||
|
;
|
||||||
|
; conf.d/ — Core services (php-fpm, nginx). Override by mounting a
|
||||||
|
; replacement file, e.g.:
|
||||||
|
; -v ./my-php-fpm.conf:/etc/supervisor/conf.d/php-fpm.conf
|
||||||
|
;
|
||||||
|
; laravel.d/ — Auto-generated at boot from ENABLE_* env vars.
|
||||||
|
; To use your own instead, set the env var to "false" and
|
||||||
|
; mount your config into custom.d/ or laravel.d/.
|
||||||
|
;
|
||||||
|
; custom.d/ — Mount any extra .conf files here for your own programs.
|
||||||
|
; -v ./my-programs/:/etc/supervisor/custom.d/
|
||||||
|
; ==========================================================================
|
||||||
|
[include]
|
||||||
|
files = /etc/supervisor/conf.d/*.conf /etc/supervisor/laravel.d/*.conf /etc/supervisor/custom.d/*.conf
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
# ===========================================================================
|
||||||
|
# docker-laravel — Multi-version build matrix
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# docker buildx bake # build all versions
|
||||||
|
# docker buildx bake php-84 # build PHP 8.4 only
|
||||||
|
# docker buildx bake --set "*.platform=linux/amd64,linux/arm64"
|
||||||
|
#
|
||||||
|
# Override registry:
|
||||||
|
# REGISTRY=ghcr.io/myorg IMAGE_NAME=docker-laravel docker buildx bake
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
variable "REGISTRY" {
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "IMAGE_NAME" {
|
||||||
|
default = "docker-laravel"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "NODE_MAJOR" {
|
||||||
|
default = "22"
|
||||||
|
}
|
||||||
|
|
||||||
|
function "tag" {
|
||||||
|
params = [php_version]
|
||||||
|
result = REGISTRY != "" ? "${REGISTRY}/${IMAGE_NAME}:php${php_version}" : "${IMAGE_NAME}:php${php_version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function "latest_tag" {
|
||||||
|
params = []
|
||||||
|
result = REGISTRY != "" ? "${REGISTRY}/${IMAGE_NAME}:latest" : "${IMAGE_NAME}:latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Individual targets
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
target "php-74" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
PHP_VERSION = "7.4"
|
||||||
|
NODE_MAJOR = "${NODE_MAJOR}"
|
||||||
|
}
|
||||||
|
tags = [tag("7.4")]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "php-80" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
PHP_VERSION = "8.0"
|
||||||
|
NODE_MAJOR = "${NODE_MAJOR}"
|
||||||
|
}
|
||||||
|
tags = [tag("8.0")]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "php-81" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
PHP_VERSION = "8.1"
|
||||||
|
NODE_MAJOR = "${NODE_MAJOR}"
|
||||||
|
}
|
||||||
|
tags = [tag("8.1")]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "php-82" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
PHP_VERSION = "8.2"
|
||||||
|
NODE_MAJOR = "${NODE_MAJOR}"
|
||||||
|
}
|
||||||
|
tags = [tag("8.2")]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "php-83" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
PHP_VERSION = "8.3"
|
||||||
|
NODE_MAJOR = "${NODE_MAJOR}"
|
||||||
|
}
|
||||||
|
tags = [tag("8.3")]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "php-84" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
PHP_VERSION = "8.4"
|
||||||
|
NODE_MAJOR = "${NODE_MAJOR}"
|
||||||
|
}
|
||||||
|
tags = [tag("8.4"), latest_tag()]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "php-85" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
PHP_VERSION = "8.5"
|
||||||
|
NODE_MAJOR = "${NODE_MAJOR}"
|
||||||
|
}
|
||||||
|
tags = [tag("8.5")]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Groups
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["php-74", "php-80", "php-81", "php-82", "php-83", "php-84", "php-85"]
|
||||||
|
}
|
||||||
|
|
||||||
|
group "active" {
|
||||||
|
targets = ["php-82", "php-83", "php-84", "php-85"]
|
||||||
|
}
|
||||||
|
|
||||||
|
group "legacy" {
|
||||||
|
targets = ["php-74", "php-80", "php-81"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Quick Start with plug-n-pray
|
||||||
|
|
||||||
|
The fastest way to Dockerize any Laravel project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From your Laravel project root:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/blax-software/docker-laravel/main/scripts/plug-n-pray.sh | bash
|
||||||
|
|
||||||
|
# Or with options:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/blax-software/docker-laravel/main/scripts/plug-n-pray.sh | bash -s -- \
|
||||||
|
--php=8.4 \
|
||||||
|
--name=my-app \
|
||||||
|
--host=my-app.localhost
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you have `blax-software/laravel-workkit` installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan workkit:plug-n-pray
|
||||||
|
```
|
||||||
|
|
||||||
|
This generates:
|
||||||
|
- `docker-compose.yml` — Full stack with app, MySQL, Redis, and Traefik
|
||||||
|
- `.env.docker` — Database/Redis connection values to merge into `.env`
|
||||||
|
- `docker/supervisor/` — Directory for custom supervisor programs
|
||||||
|
|
||||||
|
Then:
|
||||||
|
```bash
|
||||||
|
docker network create web # once per machine
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: Minimal API (no frontend tooling)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: blaxsoftware/laravel:php8.4
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/html
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
environment:
|
||||||
|
ENABLE_QUEUE: "true"
|
||||||
|
ENABLE_SCHEDULER: "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: Full Stack with Traefik
|
||||||
|
|
||||||
|
Assumes Traefik is already running on the `web` network.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
|
internal:
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql-data:
|
||||||
|
redis-data:
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: blaxsoftware/laravel:php8.4
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/html
|
||||||
|
- ./docker/supervisor:/etc/supervisor/custom.d
|
||||||
|
environment:
|
||||||
|
ENABLE_QUEUE: "true"
|
||||||
|
ENABLE_SCHEDULER: "true"
|
||||||
|
ENABLE_LARAVEL_PERMS: "1"
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
- internal
|
||||||
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.docker.network=web
|
||||||
|
- traefik.http.routers.my-app.rule=Host(`my-app.localhost`)
|
||||||
|
- traefik.http.routers.my-app.entrypoints=web
|
||||||
|
- traefik.http.routers.my-app.service=my-app-http
|
||||||
|
- traefik.http.services.my-app-http.loadbalancer.server.port=80
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: secret
|
||||||
|
MYSQL_DATABASE: my_app
|
||||||
|
volumes:
|
||||||
|
- mysql-data:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-psecret"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: With Horizon
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: blaxsoftware/laravel:php8.4
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/html
|
||||||
|
environment:
|
||||||
|
ENABLE_HORIZON: "true" # replaces basic queue worker
|
||||||
|
ENABLE_SCHEDULER: "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: Custom Supervisor Programs
|
||||||
|
|
||||||
|
Mount your own `.conf` files into `/etc/supervisor/custom.d/`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: blaxsoftware/laravel:php8.4
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/html
|
||||||
|
- ./docker/supervisor/reverb.conf:/etc/supervisor/custom.d/reverb.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
`docker/supervisor/reverb.conf`:
|
||||||
|
```ini
|
||||||
|
[program:reverb]
|
||||||
|
command=/usr/local/bin/php -d variables_order=EGPCS /var/www/html/artisan reverb:start --host=0.0.0.0 --port=8080
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=www-data
|
||||||
|
priority=25
|
||||||
|
startsecs=5
|
||||||
|
stdout_logfile=/proc/1/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/proc/1/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: Override PHP-FPM
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./docker/php-fpm.conf:/etc/supervisor/conf.d/php-fpm.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: Multiple PHP Versions in One Compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app-legacy:
|
||||||
|
image: blaxsoftware/laravel:php8.1
|
||||||
|
volumes:
|
||||||
|
- ../legacy-app:/var/www/html
|
||||||
|
|
||||||
|
app-new:
|
||||||
|
image: blaxsoftware/laravel:php8.4
|
||||||
|
volumes:
|
||||||
|
- ../new-app:/var/www/html
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## plug-n-pray.sh Options
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| `--php=VERSION` | `8.4` | PHP version |
|
||||||
|
| `--name=NAME` | directory name | Project name (used for container names & Traefik router) |
|
||||||
|
| `--host=HOST` | `NAME.localhost` | Traefik hostname |
|
||||||
|
| `--db=NAME` | project name | MySQL database name |
|
||||||
|
| `--db-pass=PASS` | `secret` | MySQL root password |
|
||||||
|
| `--image=IMAGE` | `blaxsoftware/laravel` | Docker image |
|
||||||
|
| `--no-queue` | — | Disable queue worker |
|
||||||
|
| `--no-scheduler` | — | Disable scheduler |
|
||||||
|
| `--horizon` | — | Enable Horizon (auto-disables basic queue) |
|
||||||
|
| `--no-redis` | — | Skip Redis service |
|
||||||
|
| `--no-mysql` | — | Skip MySQL service |
|
||||||
|
| `--force` | — | Overwrite existing files |
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ===========================================================================
|
||||||
|
# build.sh — Build all PHP version images with full Laravel tag matrix
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./build.sh # build all versions
|
||||||
|
# ./build.sh 8.4 # build only PHP 8.4
|
||||||
|
# ./build.sh 8.3 8.4 # build PHP 8.3 + 8.4
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# IMAGE_NAME — image name (default: docker-laravel)
|
||||||
|
# NODE_MAJOR — Node.js major ver (default: 22)
|
||||||
|
# PLATFORM — e.g. linux/amd64 (default: current platform)
|
||||||
|
# ===========================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-docker-laravel}"
|
||||||
|
NODE_MAJOR="${NODE_MAJOR:-22}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# PHP → Laravel version mapping
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
declare -A PHP_LARAVEL_MAP=(
|
||||||
|
["7.4"]=""
|
||||||
|
["8.0"]="9"
|
||||||
|
["8.1"]="9 10"
|
||||||
|
["8.2"]="9 10 11 12"
|
||||||
|
["8.3"]="10 11 12 13"
|
||||||
|
["8.4"]="11 12 13"
|
||||||
|
["8.5"]="12 13"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Recommended (highest) PHP version per Laravel version → gets the bare `laravelN` tag
|
||||||
|
declare -A LARAVEL_RECOMMENDED_PHP=(
|
||||||
|
["9"]="8.1"
|
||||||
|
["10"]="8.3"
|
||||||
|
["11"]="8.4"
|
||||||
|
["12"]="8.5"
|
||||||
|
["13"]="8.5"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Which PHP version gets the `latest` tag
|
||||||
|
LATEST_PHP="8.4"
|
||||||
|
|
||||||
|
ALL_PHP_VERSIONS=("7.4" "8.0" "8.1" "8.2" "8.3" "8.4" "8.5")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Determine which versions to build
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
BUILD_VERSIONS=("$@")
|
||||||
|
else
|
||||||
|
BUILD_VERSIONS=("${ALL_PHP_VERSIONS[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate requested versions
|
||||||
|
for v in "${BUILD_VERSIONS[@]}"; do
|
||||||
|
if [[ ! -v "PHP_LARAVEL_MAP[$v]" ]]; then
|
||||||
|
echo "ERROR: Unknown PHP version: $v"
|
||||||
|
echo "Available: ${ALL_PHP_VERSIONS[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Build
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
TOTAL=${#BUILD_VERSIONS[@]}
|
||||||
|
CURRENT=0
|
||||||
|
FAILED=()
|
||||||
|
|
||||||
|
PLATFORM_ARG=""
|
||||||
|
if [ -n "${PLATFORM:-}" ]; then
|
||||||
|
PLATFORM_ARG="--platform=${PLATFORM}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for PHP_VERSION in "${BUILD_VERSIONS[@]}"; do
|
||||||
|
CURRENT=$((CURRENT + 1))
|
||||||
|
|
||||||
|
# Collect all tags for this PHP version
|
||||||
|
TAGS=()
|
||||||
|
TAGS+=("${IMAGE_NAME}:php${PHP_VERSION}")
|
||||||
|
|
||||||
|
# Laravel combo tags: laravel12-php8.4, etc.
|
||||||
|
LARAVEL_VERSIONS="${PHP_LARAVEL_MAP[$PHP_VERSION]}"
|
||||||
|
for LV in $LARAVEL_VERSIONS; do
|
||||||
|
TAGS+=("${IMAGE_NAME}:laravel${LV}-php${PHP_VERSION}")
|
||||||
|
|
||||||
|
# Convenience bare tag: laravelN → recommended PHP
|
||||||
|
if [ "${LARAVEL_RECOMMENDED_PHP[$LV]}" = "$PHP_VERSION" ]; then
|
||||||
|
TAGS+=("${IMAGE_NAME}:laravel${LV}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# latest tag
|
||||||
|
if [ "$PHP_VERSION" = "$LATEST_PHP" ]; then
|
||||||
|
TAGS+=("${IMAGE_NAME}:latest")
|
||||||
|
fi
|
||||||
|
|
||||||
|
TAG_ARGS=""
|
||||||
|
TAG_LIST=""
|
||||||
|
for T in "${TAGS[@]}"; do
|
||||||
|
TAG_ARGS="${TAG_ARGS} -t ${T}"
|
||||||
|
TAG_LIST="${TAG_LIST} ${T}\n"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==========================================="
|
||||||
|
echo " [${CURRENT}/${TOTAL}] Building PHP ${PHP_VERSION}"
|
||||||
|
echo "==========================================="
|
||||||
|
echo -e "Tags:\n${TAG_LIST}"
|
||||||
|
|
||||||
|
if docker build ${PLATFORM_ARG} \
|
||||||
|
--build-arg PHP_VERSION="${PHP_VERSION}" \
|
||||||
|
--build-arg NODE_MAJOR="${NODE_MAJOR}" \
|
||||||
|
${TAG_ARGS} \
|
||||||
|
. ; then
|
||||||
|
echo "[${CURRENT}/${TOTAL}] PHP ${PHP_VERSION} — OK"
|
||||||
|
else
|
||||||
|
echo "[${CURRENT}/${TOTAL}] PHP ${PHP_VERSION} — FAILED"
|
||||||
|
FAILED+=("$PHP_VERSION")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "==========================================="
|
||||||
|
echo " Build complete"
|
||||||
|
echo "==========================================="
|
||||||
|
echo "Succeeded: $((TOTAL - ${#FAILED[@]}))/${TOTAL}"
|
||||||
|
|
||||||
|
if [ ${#FAILED[@]} -gt 0 ]; then
|
||||||
|
echo "Failed: ${FAILED[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,384 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ===========================================================================
|
||||||
|
# plug-n-pray.sh — Generate a boilerplate Docker setup for any Laravel project
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/blax-software/docker-laravel/main/scripts/plug-n-pray.sh | bash
|
||||||
|
# # or locally:
|
||||||
|
# ./plug-n-pray.sh
|
||||||
|
# ./plug-n-pray.sh --php=8.4 --name=my-app --host=my-app.localhost
|
||||||
|
#
|
||||||
|
# Assumes Traefik is already running on the "web" network.
|
||||||
|
#
|
||||||
|
# What it creates:
|
||||||
|
# docker-compose.yml — App + MySQL + Redis (with Traefik labels)
|
||||||
|
# .env.docker — Docker-specific env vars
|
||||||
|
# docker/supervisor/ — Empty custom supervisor dir
|
||||||
|
# ===========================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Defaults
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
PHP_VERSION="8.4"
|
||||||
|
PROJECT_NAME=""
|
||||||
|
TRAEFIK_HOST=""
|
||||||
|
DB_NAME=""
|
||||||
|
DB_PASSWORD="secret"
|
||||||
|
IMAGE="blaxsoftware/laravel"
|
||||||
|
ENABLE_QUEUE="true"
|
||||||
|
ENABLE_SCHEDULER="true"
|
||||||
|
ENABLE_HORIZON="false"
|
||||||
|
ENABLE_REDIS="true"
|
||||||
|
ENABLE_MYSQL="true"
|
||||||
|
ENABLE_WEBSOCKET="false"
|
||||||
|
WEBSOCKET_PORT="6001"
|
||||||
|
FORCE="false"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Parse args
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
--php=*) PHP_VERSION="${arg#*=}" ;;
|
||||||
|
--name=*) PROJECT_NAME="${arg#*=}" ;;
|
||||||
|
--host=*) TRAEFIK_HOST="${arg#*=}" ;;
|
||||||
|
--db=*) DB_NAME="${arg#*=}" ;;
|
||||||
|
--db-pass=*) DB_PASSWORD="${arg#*=}" ;;
|
||||||
|
--image=*) IMAGE="${arg#*=}" ;;
|
||||||
|
--no-queue) ENABLE_QUEUE="false" ;;
|
||||||
|
--no-scheduler) ENABLE_SCHEDULER="false" ;;
|
||||||
|
--horizon) ENABLE_HORIZON="true" ;;
|
||||||
|
--no-redis) ENABLE_REDIS="false" ;;
|
||||||
|
--no-mysql) ENABLE_MYSQL="false" ;;
|
||||||
|
--websocket) ENABLE_WEBSOCKET="true" ;;
|
||||||
|
--websocket-port=*) WEBSOCKET_PORT="${arg#*=}" ;;
|
||||||
|
--force) FORCE="true" ;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: plug-n-pray.sh [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --php=VERSION PHP version (default: 8.4)"
|
||||||
|
echo " --name=NAME Project/compose name (default: directory name)"
|
||||||
|
echo " --host=HOST Traefik hostname (default: NAME.localhost)"
|
||||||
|
echo " --db=NAME Database name (default: PROJECT_NAME)"
|
||||||
|
echo " --db-pass=PASS Database password (default: secret)"
|
||||||
|
echo " --image=IMAGE Docker image (default: blaxsoftware/laravel)"
|
||||||
|
echo " --no-queue Disable queue worker"
|
||||||
|
echo " --no-scheduler Disable scheduler"
|
||||||
|
echo " --horizon Enable Horizon (disables basic queue)"
|
||||||
|
echo " --no-redis Skip Redis service"
|
||||||
|
echo " --no-mysql Skip MySQL service"
|
||||||
|
echo " --websocket Enable WebSocket server (blax/laravel-websockets)"
|
||||||
|
echo " --websocket-port=N WebSocket port (default: 6001)"
|
||||||
|
echo " --force Overwrite existing files"
|
||||||
|
echo " --help Show this help"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $arg (try --help)"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Derive defaults from current directory
|
||||||
|
if [ -z "$PROJECT_NAME" ]; then
|
||||||
|
PROJECT_NAME="$(basename "$(pwd)")"
|
||||||
|
# Sanitize: lowercase, replace non-alnum with dash
|
||||||
|
PROJECT_NAME="$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$TRAEFIK_HOST" ]; then
|
||||||
|
TRAEFIK_HOST="${PROJECT_NAME}.localhost"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$DB_NAME" ]; then
|
||||||
|
DB_NAME="$(echo "$PROJECT_NAME" | sed 's/-/_/g')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If horizon is on, disable basic queue
|
||||||
|
if [ "$ENABLE_HORIZON" = "true" ]; then
|
||||||
|
ENABLE_QUEUE="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Safety check
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ "$FORCE" != "true" ] && [ -f "docker-compose.yml" ]; then
|
||||||
|
echo "docker-compose.yml already exists. Use --force to overwrite."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " plug-n-pray 🙏"
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Project: $PROJECT_NAME"
|
||||||
|
echo " PHP: $PHP_VERSION"
|
||||||
|
echo " Image: ${IMAGE}:php${PHP_VERSION}"
|
||||||
|
echo " Traefik: $TRAEFIK_HOST"
|
||||||
|
echo " Database: ${ENABLE_MYSQL:+MySQL ($DB_NAME)}${ENABLE_MYSQL:+}$([ "$ENABLE_MYSQL" = "false" ] && echo "disabled")"
|
||||||
|
echo " Redis: $ENABLE_REDIS"
|
||||||
|
echo " Queue: $ENABLE_QUEUE"
|
||||||
|
echo " Scheduler: $ENABLE_SCHEDULER"
|
||||||
|
echo " Horizon: $ENABLE_HORIZON"
|
||||||
|
echo " WebSocket: ${ENABLE_WEBSOCKET}$([ "$ENABLE_WEBSOCKET" = "true" ] && echo " (port ${WEBSOCKET_PORT})")"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Create directory for custom supervisor configs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
mkdir -p docker/supervisor
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Generate docker-compose.yml
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
cat > docker-compose.yml <<YAML
|
||||||
|
# ===========================================================================
|
||||||
|
# Generated by plug-n-pray.sh — $(date +%Y-%m-%d)
|
||||||
|
# Image: ${IMAGE}:php${PHP_VERSION}
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
|
internal:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
YAML
|
||||||
|
|
||||||
|
# --- Volumes ---
|
||||||
|
VOLUMES_SECTION=""
|
||||||
|
if [ "$ENABLE_MYSQL" = "true" ]; then
|
||||||
|
VOLUMES_SECTION="volumes:
|
||||||
|
mysql-data:
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
if [ "$ENABLE_REDIS" = "true" ]; then
|
||||||
|
if [ -n "$VOLUMES_SECTION" ]; then
|
||||||
|
VOLUMES_SECTION="${VOLUMES_SECTION} redis-data:
|
||||||
|
"
|
||||||
|
else
|
||||||
|
VOLUMES_SECTION="volumes:
|
||||||
|
redis-data:
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$VOLUMES_SECTION" ]; then
|
||||||
|
echo "$VOLUMES_SECTION" >> docker-compose.yml
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Services ---
|
||||||
|
cat >> docker-compose.yml <<YAML
|
||||||
|
services:
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# App (PHP-FPM + Nginx)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
app:
|
||||||
|
image: ${IMAGE}:php${PHP_VERSION}
|
||||||
|
container_name: ${PROJECT_NAME}-app
|
||||||
|
restart: unless-stopped
|
||||||
|
working_dir: /var/www/html
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/html
|
||||||
|
- ./docker/supervisor:/etc/supervisor/custom.d
|
||||||
|
environment:
|
||||||
|
ENABLE_QUEUE: "${ENABLE_QUEUE}"
|
||||||
|
ENABLE_SCHEDULER: "${ENABLE_SCHEDULER}"
|
||||||
|
ENABLE_HORIZON: "${ENABLE_HORIZON}"
|
||||||
|
ENABLE_LARAVEL_PERMS: "1"
|
||||||
|
$([ "$ENABLE_WEBSOCKET" = "true" ] && echo " PUSHER_PORT: \"${WEBSOCKET_PORT}\"")
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
- internal
|
||||||
|
depends_on:
|
||||||
|
YAML
|
||||||
|
|
||||||
|
if [ "$ENABLE_MYSQL" = "true" ]; then
|
||||||
|
cat >> docker-compose.yml <<YAML
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
YAML
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ENABLE_REDIS" = "true" ]; then
|
||||||
|
cat >> docker-compose.yml <<YAML
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
YAML
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >> docker-compose.yml <<YAML
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.docker.network=web
|
||||||
|
# HTTP
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}.rule=Host(\`${TRAEFIK_HOST}\`)
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}.entrypoints=web
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}.service=${PROJECT_NAME}-http
|
||||||
|
- traefik.http.services.${PROJECT_NAME}-http.loadbalancer.server.port=80
|
||||||
|
# HTTPS
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-tls.rule=Host(\`${TRAEFIK_HOST}\`)
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-tls.entrypoints=websecure
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-tls.tls=true
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-tls.service=${PROJECT_NAME}-https
|
||||||
|
- traefik.http.services.${PROJECT_NAME}-https.loadbalancer.server.port=80
|
||||||
|
$(if [ "$ENABLE_WEBSOCKET" = "true" ]; then
|
||||||
|
cat <<WSLABELS
|
||||||
|
# WebSocket
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-ws.rule=Host(\`ws-${TRAEFIK_HOST}\`)
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-ws.entrypoints=web
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-ws.service=${PROJECT_NAME}-ws
|
||||||
|
- traefik.http.services.${PROJECT_NAME}-ws.loadbalancer.server.port=${WEBSOCKET_PORT}
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-wss.rule=Host(\`ws-${TRAEFIK_HOST}\`)
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-wss.entrypoints=websecure
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-wss.tls=true
|
||||||
|
- traefik.http.routers.${PROJECT_NAME}-wss.service=${PROJECT_NAME}-wss
|
||||||
|
- traefik.http.services.${PROJECT_NAME}-wss.loadbalancer.server.port=${WEBSOCKET_PORT}
|
||||||
|
WSLABELS
|
||||||
|
fi)
|
||||||
|
YAML
|
||||||
|
|
||||||
|
# --- MySQL ---
|
||||||
|
if [ "$ENABLE_MYSQL" = "true" ]; then
|
||||||
|
cat >> docker-compose.yml <<YAML
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# MySQL
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: ${PROJECT_NAME}-mysql
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: "${DB_PASSWORD}"
|
||||||
|
MYSQL_DATABASE: "${DB_NAME}"
|
||||||
|
volumes:
|
||||||
|
- mysql-data:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${DB_PASSWORD}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
YAML
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Redis ---
|
||||||
|
if [ "$ENABLE_REDIS" = "true" ]; then
|
||||||
|
cat >> docker-compose.yml <<YAML
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Redis
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: ${PROJECT_NAME}-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
YAML
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- WebSocket supervisor config ---
|
||||||
|
if [ "$ENABLE_WEBSOCKET" = "true" ]; then
|
||||||
|
cat > docker/supervisor/websocket.conf <<CONF
|
||||||
|
[program:websocket]
|
||||||
|
command=/usr/local/bin/php -d variables_order=EGPCS /var/www/html/artisan websockets:serve --host=0.0.0.0 --port=${WEBSOCKET_PORT}
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=www-data
|
||||||
|
priority=30
|
||||||
|
startsecs=5
|
||||||
|
startretries=100
|
||||||
|
stopsignal=TERM
|
||||||
|
stopwaitsecs=15
|
||||||
|
stdout_logfile=/proc/1/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/proc/1/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
CONF
|
||||||
|
echo " Created docker/supervisor/websocket.conf"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Generate .env.docker
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
cat > .env.docker <<ENV
|
||||||
|
# ===========================================================================
|
||||||
|
# Docker environment — generated by plug-n-pray.sh
|
||||||
|
# Merge these into your .env or source this file
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
# App
|
||||||
|
APP_URL=http://${TRAEFIK_HOST}
|
||||||
|
ENV
|
||||||
|
|
||||||
|
if [ "$ENABLE_MYSQL" = "true" ]; then
|
||||||
|
cat >> .env.docker <<ENV
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=mysql
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=${DB_NAME}
|
||||||
|
DB_USERNAME=root
|
||||||
|
DB_PASSWORD=${DB_PASSWORD}
|
||||||
|
ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ENABLE_REDIS" = "true" ]; then
|
||||||
|
cat >> .env.docker <<ENV
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
CACHE_STORE=redis
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ENABLE_WEBSOCKET" = "true" ]; then
|
||||||
|
cat >> .env.docker <<ENV
|
||||||
|
|
||||||
|
# WebSocket (blax/laravel-websockets)
|
||||||
|
BROADCAST_CONNECTION=pusher
|
||||||
|
PUSHER_APP_ID=app-id
|
||||||
|
PUSHER_APP_KEY=app-key
|
||||||
|
PUSHER_APP_SECRET=app-secret
|
||||||
|
PUSHER_HOST=127.0.0.1
|
||||||
|
PUSHER_PORT=${WEBSOCKET_PORT}
|
||||||
|
PUSHER_SCHEME=http
|
||||||
|
LARAVEL_WEBSOCKETS_PORT=${WEBSOCKET_PORT}
|
||||||
|
ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Done
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Files created:"
|
||||||
|
echo "=========================================="
|
||||||
|
echo " docker-compose.yml — Full stack (app + db + redis, Traefik labels)"
|
||||||
|
echo " .env.docker — Environment variables to merge into .env"
|
||||||
|
echo " docker/supervisor/ — Mount dir for custom supervisor programs"
|
||||||
|
echo ""
|
||||||
|
echo " Next steps:"
|
||||||
|
echo " 1. Merge .env.docker into your .env"
|
||||||
|
echo " 2. Create the external network (once): docker network create web"
|
||||||
|
echo " 3. Start: docker compose up -d"
|
||||||
|
echo " 4. Visit: http://${TRAEFIK_HOST}"
|
||||||
|
echo ""
|
||||||
|
echo " Pray it works. 🙏"
|
||||||
|
echo "=========================================="
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ===========================================================================
|
||||||
|
# publish.sh — Push all built images to a container registry
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# REGISTRY=ghcr.io/myorg ./publish.sh # push all versions
|
||||||
|
# REGISTRY=ghcr.io/myorg ./publish.sh 8.4 # push only PHP 8.4 tags
|
||||||
|
# REGISTRY=ghcr.io/myorg ./publish.sh 8.3 8.4 # push PHP 8.3 + 8.4
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# REGISTRY — registry prefix (REQUIRED, e.g. ghcr.io/myorg)
|
||||||
|
# IMAGE_NAME — image name (default: docker-laravel)
|
||||||
|
# DRY_RUN — set to 1 to print commands without executing
|
||||||
|
# ===========================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ -z "${REGISTRY:-}" ]; then
|
||||||
|
echo "ERROR: REGISTRY is required."
|
||||||
|
echo ""
|
||||||
|
echo "Usage: REGISTRY=ghcr.io/myorg ./publish.sh [php_versions...]"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " REGISTRY=ghcr.io/myorg ./publish.sh"
|
||||||
|
echo " REGISTRY=docker.io/myuser ./publish.sh 8.4"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-docker-laravel}"
|
||||||
|
LOCAL="${IMAGE_NAME}"
|
||||||
|
REMOTE="${REGISTRY}/${IMAGE_NAME}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# PHP → Laravel version mapping (must match build.sh)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
declare -A PHP_LARAVEL_MAP=(
|
||||||
|
["7.4"]=""
|
||||||
|
["8.0"]="9"
|
||||||
|
["8.1"]="9 10"
|
||||||
|
["8.2"]="9 10 11 12"
|
||||||
|
["8.3"]="10 11 12 13"
|
||||||
|
["8.4"]="11 12 13"
|
||||||
|
["8.5"]="12 13"
|
||||||
|
)
|
||||||
|
|
||||||
|
declare -A LARAVEL_RECOMMENDED_PHP=(
|
||||||
|
["9"]="8.1"
|
||||||
|
["10"]="8.3"
|
||||||
|
["11"]="8.4"
|
||||||
|
["12"]="8.5"
|
||||||
|
["13"]="8.5"
|
||||||
|
)
|
||||||
|
|
||||||
|
LATEST_PHP="8.4"
|
||||||
|
ALL_PHP_VERSIONS=("7.4" "8.0" "8.1" "8.2" "8.3" "8.4" "8.5")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Determine which versions to push
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
PUSH_VERSIONS=("$@")
|
||||||
|
else
|
||||||
|
PUSH_VERSIONS=("${ALL_PHP_VERSIONS[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
for v in "${PUSH_VERSIONS[@]}"; do
|
||||||
|
if [[ ! -v "PHP_LARAVEL_MAP[$v]" ]]; then
|
||||||
|
echo "ERROR: Unknown PHP version: $v"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helper
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
run() {
|
||||||
|
if [ "${DRY_RUN:-0}" = "1" ]; then
|
||||||
|
echo "[dry-run] $*"
|
||||||
|
else
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tag_and_push() {
|
||||||
|
local LOCAL_TAG="$1"
|
||||||
|
local REMOTE_TAG="$2"
|
||||||
|
run docker tag "${LOCAL_TAG}" "${REMOTE_TAG}"
|
||||||
|
run docker push "${REMOTE_TAG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Push
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
TOTAL=0
|
||||||
|
PUSHED=0
|
||||||
|
|
||||||
|
for PHP_VERSION in "${PUSH_VERSIONS[@]}"; do
|
||||||
|
echo ""
|
||||||
|
echo "==========================================="
|
||||||
|
echo " Pushing PHP ${PHP_VERSION}"
|
||||||
|
echo "==========================================="
|
||||||
|
|
||||||
|
# Base PHP tag
|
||||||
|
tag_and_push "${LOCAL}:php${PHP_VERSION}" "${REMOTE}:php${PHP_VERSION}"
|
||||||
|
TOTAL=$((TOTAL + 1)); PUSHED=$((PUSHED + 1))
|
||||||
|
|
||||||
|
# Laravel combo tags
|
||||||
|
LARAVEL_VERSIONS="${PHP_LARAVEL_MAP[$PHP_VERSION]}"
|
||||||
|
for LV in $LARAVEL_VERSIONS; do
|
||||||
|
tag_and_push "${LOCAL}:php${PHP_VERSION}" "${REMOTE}:laravel${LV}-php${PHP_VERSION}"
|
||||||
|
TOTAL=$((TOTAL + 1)); PUSHED=$((PUSHED + 1))
|
||||||
|
|
||||||
|
# Bare laravelN tag
|
||||||
|
if [ "${LARAVEL_RECOMMENDED_PHP[$LV]}" = "$PHP_VERSION" ]; then
|
||||||
|
tag_and_push "${LOCAL}:php${PHP_VERSION}" "${REMOTE}:laravel${LV}"
|
||||||
|
TOTAL=$((TOTAL + 1)); PUSHED=$((PUSHED + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# latest
|
||||||
|
if [ "$PHP_VERSION" = "$LATEST_PHP" ]; then
|
||||||
|
tag_and_push "${LOCAL}:php${PHP_VERSION}" "${REMOTE}:latest"
|
||||||
|
TOTAL=$((TOTAL + 1)); PUSHED=$((PUSHED + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "==========================================="
|
||||||
|
echo " Publish complete"
|
||||||
|
echo "==========================================="
|
||||||
|
echo "Pushed ${PUSHED} tags to ${REGISTRY}/${IMAGE_NAME}"
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " docker-laravel container starting"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Date: $(date)"
|
||||||
|
echo "Hostname: $(hostname)"
|
||||||
|
echo "PHP: $(php -r 'echo PHP_VERSION;')"
|
||||||
|
echo "Node: $(node --version 2>/dev/null || echo 'n/a')"
|
||||||
|
echo "Composer: $(composer --version --no-ansi 2>/dev/null | head -1 || echo 'n/a')"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
start_ts=$(date +%s)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1) Writable directories
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "[1/5] Preparing writable dirs..."
|
||||||
|
|
||||||
|
mkdir -p /.composer && chmod 0777 /.composer 2>/dev/null || true
|
||||||
|
|
||||||
|
if [ "${ENABLE_LARAVEL_PERMS:-0}" = "1" ]; then
|
||||||
|
echo " ENABLE_LARAVEL_PERMS=1 — applying targeted writable-dir fixes"
|
||||||
|
for p in /var/www/html/storage /var/www/html/bootstrap/cache; do
|
||||||
|
if [ -d "$p" ]; then
|
||||||
|
chmod ug+rwX "$p" 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo " WARN: $p does not exist"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo " Skipping Laravel chmod (set ENABLE_LARAVEL_PERMS=1 to enable)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p /var/log/supervisor /var/log/nginx /var/log/php
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2) Generate optional supervisor programs based on ENABLE_* env vars
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "[2/5] Configuring services..."
|
||||||
|
|
||||||
|
echo " Core programs (conf.d/):"
|
||||||
|
for f in /etc/supervisor/conf.d/*.conf; do
|
||||||
|
[ -f "$f" ] && echo " $(basename "$f")"
|
||||||
|
done
|
||||||
|
|
||||||
|
LARAVEL_D="/etc/supervisor/laravel.d"
|
||||||
|
rm -f "${LARAVEL_D}"/*.conf 2>/dev/null || true
|
||||||
|
|
||||||
|
if [ "${ENABLE_QUEUE:-false}" = "true" ]; then
|
||||||
|
echo " + queue worker enabled"
|
||||||
|
cat > "${LARAVEL_D}/queue.conf" <<'CONF'
|
||||||
|
[program:queue]
|
||||||
|
command=/usr/local/bin/php -d variables_order=EGPCS /var/www/html/artisan queue:work --tries=3 --sleep=5 --timeout=600 --max-jobs=500 --max-time=3600
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=www-data
|
||||||
|
priority=20
|
||||||
|
startsecs=5
|
||||||
|
stdout_logfile=/proc/1/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/proc/1/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
CONF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${ENABLE_SCHEDULER:-false}" = "true" ]; then
|
||||||
|
echo " + scheduler enabled"
|
||||||
|
cat > "${LARAVEL_D}/scheduler.conf" <<'CONF'
|
||||||
|
[program:scheduler]
|
||||||
|
command=/usr/local/bin/php -d variables_order=EGPCS /var/www/html/artisan schedule:work
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=www-data
|
||||||
|
priority=20
|
||||||
|
startsecs=5
|
||||||
|
stdout_logfile=/proc/1/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/proc/1/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
CONF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${ENABLE_HORIZON:-false}" = "true" ]; then
|
||||||
|
echo " + Horizon enabled"
|
||||||
|
cat > "${LARAVEL_D}/horizon.conf" <<'CONF'
|
||||||
|
[program:horizon]
|
||||||
|
command=/usr/local/bin/php -d variables_order=EGPCS /var/www/html/artisan horizon
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=www-data
|
||||||
|
priority=20
|
||||||
|
startsecs=5
|
||||||
|
stdout_logfile=/proc/1/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/proc/1/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
CONF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Report custom programs
|
||||||
|
CUSTOM_COUNT=$(find /etc/supervisor/custom.d/ -name '*.conf' 2>/dev/null | wc -l)
|
||||||
|
if [ "$CUSTOM_COUNT" -gt 0 ]; then
|
||||||
|
echo " Custom programs (custom.d/):"
|
||||||
|
for f in /etc/supervisor/custom.d/*.conf; do
|
||||||
|
[ -f "$f" ] && echo " $(basename "$f")"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3) Config tests
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "[3/5] Testing configs..."
|
||||||
|
|
||||||
|
php-fpm -t 2>&1 || echo " PHP-FPM config test failed (continuing)"
|
||||||
|
nginx -t 2>&1 || echo " Nginx config test failed (continuing)"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4) Diagnostics
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "[4/5] Environment"
|
||||||
|
echo " APP_ENV=${APP_ENV:-<unset>}"
|
||||||
|
echo " APP_DEBUG=${APP_DEBUG:-<unset>}"
|
||||||
|
echo " ENABLE_QUEUE=${ENABLE_QUEUE:-false}"
|
||||||
|
echo " ENABLE_SCHEDULER=${ENABLE_SCHEDULER:-false}"
|
||||||
|
echo " ENABLE_HORIZON=${ENABLE_HORIZON:-false}"
|
||||||
|
|
||||||
|
end_ts=$(date +%s)
|
||||||
|
echo " Preflight took $((end_ts - start_ts))s"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 5) Launch
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
echo "[5/5] Running ad-hoc command: $*"
|
||||||
|
php-fpm -D
|
||||||
|
sleep 1
|
||||||
|
nginx
|
||||||
|
exec gosu 1000 "$@"
|
||||||
|
else
|
||||||
|
echo "[5/5] Starting Supervisord..."
|
||||||
|
echo "=========================================="
|
||||||
|
exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
||||||
|
fi
|
||||||
Loading…
Reference in New Issue