307 lines
8.0 KiB
Markdown
307 lines
8.0 KiB
Markdown
# Cleft Lip Data — Docker Deployment Plan
|
|
|
|
## Overview
|
|
|
|
Multi-container Docker deployment for a Laravel 11 cleft lip/palate medical records management app.
|
|
|
|
**Target:** Coolify
|
|
**Database:** MariaDB 10.6.25 (matching existing SQL dump)
|
|
**PHP:** 8.2 FPM Alpine
|
|
**Web Server:** Nginx 1.25 Alpine
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Coolify auto-generates Traefik labels
|
|
│
|
|
┌─────▼─────┐
|
|
│ nginx │ :80
|
|
│ Dockerfile.nginx
|
|
└─────┬─────┘
|
|
│ volume: app-photos:ro
|
|
┌─────▼──────┐
|
|
│ app (fpm) │ :9000
|
|
│ Dockerfile
|
|
└─────┬──────┘
|
|
│ volume: app-storage
|
|
┌───────────▼───────────┐
|
|
│ mariadb:10.6.25 │ :3306
|
|
│ healthcheck: custom │ volume: mariadb_data
|
|
└───────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Files to Create
|
|
|
|
| File | Content |
|
|
|---|---|
|
|
| `Dockerfile` | Multi-stage: Node build → Composer deps → PHP-FPM runtime |
|
|
| `Dockerfile.nginx` | Nginx with Laravel config baked in |
|
|
| `docker-nginx.conf` | Laravel-compatible Nginx site config |
|
|
| `docker-compose.yml` | 3 services: mariadb, app, nginx |
|
|
| `docker-entrypoint.sh` | Startup orchestration script |
|
|
| `.dockerignore` | Build context filter |
|
|
| `docker-healthcheck.sh` | MariaDB healthcheck (not used directly; Healthcheck is inline in compose for MariaDB) |
|
|
|
|
---
|
|
|
|
## 1. `Dockerfile` — Multi-Stage Build
|
|
|
|
### Stage 1: Node Build
|
|
- Base: `node:20-alpine`
|
|
- `npm ci`
|
|
- `npm run build` → outputs `public/build/assets/` + `public/build/manifest.json`
|
|
|
|
### Stage 2: Composer Dependencies
|
|
- Base: `php:8.2-cli-alpine`
|
|
- Copy `composer` binary from official `composer:2` image
|
|
- `composer install --no-dev --optimize-autoloader`
|
|
|
|
### Stage 3: PHP-FPM Runtime
|
|
- Base: `php:8.2-fpm-alpine`
|
|
- Install missing PHP extensions only:
|
|
- `pdo_mysql` (not in base)
|
|
- `gd` with `--with-freetype --with-jpeg` (not in base)
|
|
- `zip` (not in base)
|
|
- `intl` (recommended)
|
|
- `bcmath` (recommended)
|
|
- System packages: `libpng-dev`, `libjpeg-turbo-dev`, `freetype-dev`, `libzip-dev`, `icu-dev`, `oniguruma-dev`
|
|
- Create `app` user (non-root, UID 1001)
|
|
- Copy application source (excluding `node_modules`, `vendor`, `storage`, `.env`)
|
|
- Copy `vendor/` from Stage 2
|
|
- Copy `public/build/` from Stage 1
|
|
- Copy `docker-entrypoint.sh` → `/usr/local/bin/`
|
|
- Set `WORKDIR /var/www/html`
|
|
- `ENTRYPOINT ["docker-entrypoint.sh"]`
|
|
- `CMD ["php-fpm"]`
|
|
- `USER app`
|
|
|
|
---
|
|
|
|
## 2. `Dockerfile.nginx`
|
|
|
|
- Base: `nginx:1.25-alpine` (pinned)
|
|
- Copy `docker-nginx.conf` → `/etc/nginx/conf.d/default.conf`
|
|
|
|
---
|
|
|
|
## 3. `docker-nginx.conf`
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
root /var/www/html/public;
|
|
index index.php;
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.php?$query_string;
|
|
}
|
|
|
|
location ~ \.php$ {
|
|
fastcgi_pass app:9000;
|
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
|
include fastcgi_params;
|
|
}
|
|
|
|
location ~ /\.ht {
|
|
deny all;
|
|
}
|
|
|
|
location ~ /\.(?!well-known) {
|
|
deny all;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. `docker-entrypoint.sh`
|
|
|
|
```bash
|
|
#!/bin/sh
|
|
set -e
|
|
|
|
# === 1. Wait for MySQL ===
|
|
# PDO connection loop, 60s timeout, 2s intervals
|
|
# Uses getenv('DB_HOST'), getenv('DB_PORT'), getenv('DB_DATABASE'),
|
|
# getenv('DB_USERNAME'), getenv('DB_PASSWORD')
|
|
|
|
# === 2. Generate APP_KEY if missing or default ===
|
|
# php artisan key:generate if getenv('APP_KEY') is empty
|
|
# But only if running in PHP-FPM context (not during build)
|
|
|
|
# === 3. Import SQL dump if tables don't exist ===
|
|
# Check if any tables exist via mysql --ssl=0 ...
|
|
# If not, import humicpro_cleftlipdata.sql
|
|
|
|
# === 4. Run migrations ===
|
|
# php artisan migrate --force
|
|
|
|
# === 5. Cache ===
|
|
# php artisan config:cache
|
|
# php artisan route:cache
|
|
# php artisan view:cache
|
|
|
|
# === 6. Storage link ===
|
|
# php artisan storage:link (if not already linked)
|
|
|
|
# === 7. Start PHP-FPM ===
|
|
exec php-fpm
|
|
```
|
|
|
|
**Critical rules applied:**
|
|
- `set -e` — any failure stops the script (rule 9)
|
|
- `getenv()` in PHP — no bash variable interpolation (rule 4)
|
|
- `--ssl=0` for mysql client (rule 8)
|
|
- `exec php-fpm` — final process replaces shell (rule 9)
|
|
|
|
---
|
|
|
|
## 5. `docker-compose.yml`
|
|
|
|
```yaml
|
|
services:
|
|
mariadb:
|
|
image: mariadb:10.6.25
|
|
volumes:
|
|
- mariadb_data:/var/lib/mysql
|
|
environment:
|
|
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
|
MYSQL_DATABASE: ${DB_DATABASE}
|
|
MYSQL_USER: ${DB_USERNAME}
|
|
MYSQL_PASSWORD: ${DB_PASSWORD}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "mysqladmin ping -h localhost --ssl=0 -u root -p$$MYSQL_ROOT_PASSWORD"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 60s
|
|
restart: unless-stopped
|
|
|
|
app:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
volumes:
|
|
- app-storage:/var/www/html/storage
|
|
- app-photos:/var/www/html/public/photos
|
|
environment:
|
|
APP_ENV: production
|
|
APP_DEBUG: "false"
|
|
APP_URL: ${APP_URL}
|
|
APP_KEY: ${APP_KEY}
|
|
DB_HOST: mariadb
|
|
DB_PORT: "3306"
|
|
DB_DATABASE: ${DB_DATABASE}
|
|
DB_USERNAME: ${DB_USERNAME}
|
|
DB_PASSWORD: ${DB_PASSWORD}
|
|
SESSION_DRIVER: database
|
|
QUEUE_CONNECTION: database
|
|
CACHE_STORE: database
|
|
MAIL_MAILER: ${MAIL_MAILER:-log}
|
|
LOG_CHANNEL: stack
|
|
LOG_LEVEL: ${LOG_LEVEL:-warning}
|
|
depends_on:
|
|
mariadb:
|
|
condition: service_healthy
|
|
restart: unless-stopped
|
|
|
|
nginx:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.nginx
|
|
volumes:
|
|
- app-storage:/var/www/html/storage:ro
|
|
- app-photos:/var/www/html/public/photos:ro
|
|
ports:
|
|
- "${NGINX_PORT:-80}:80"
|
|
depends_on:
|
|
- app
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
mariadb_data:
|
|
app-storage:
|
|
app-photos:
|
|
```
|
|
|
|
**No Traefik labels** — Coolify auto-generates them (rule 6).
|
|
|
|
---
|
|
|
|
## 6. `.dockerignore`
|
|
|
|
```
|
|
.git
|
|
.gitattributes
|
|
.gitignore
|
|
.env
|
|
.env.example
|
|
local.env
|
|
node_modules
|
|
vendor
|
|
storage
|
|
.DS_Store
|
|
*.md
|
|
.dockerignore
|
|
docker-compose.yml
|
|
Dockerfile.nginx
|
|
docker-nginx.conf
|
|
humicpro_cleftlipdata.sql
|
|
AGENTS.MD
|
|
DEPLOY.htaccess
|
|
DEPLOYindex.php
|
|
database/database.sqlite
|
|
error_log
|
|
.build/
|
|
.cgi-bin/
|
|
.well-known/
|
|
```
|
|
|
|
---
|
|
|
|
## Key Compliance with AGENTS.md Rules
|
|
|
|
| Rule | How We Comply |
|
|
|---|---|
|
|
| 1. Pin image tags | `php:8.2-fpm-alpine`, `nginx:1.25-alpine`, `mariadb:10.6.25`, `node:20-alpine` — all pinned |
|
|
| 2. Verify PHP extensions | Base image has most; install only: `pdo_mysql`, `gd`, `zip`, `intl`, `bcmath` |
|
|
| 3. Shared volumes | `app-storage` and `app-photos` shared between `app` and `nginx` |
|
|
| 4. No bash var interpolation | Entrypoint uses PHP's `getenv()` where needed, not shell `${VAR}` |
|
|
| 5. No bind mounts | Nginx config baked into `Dockerfile.nginx`; all configs in images |
|
|
| 6. No Traefik labels | Zero labels in compose |
|
|
| 7. Match DB image | `mariadb:10.6.25` matches dump's `10.6.25-MariaDB` server version |
|
|
| 8. `--ssl=0` | Entrypoint and healthcheck use `--ssl=0` |
|
|
| 9. Entrypoint flow | `set -e`, step-by-step checks, `exec php-fpm` as final process |
|
|
| 10. DB healthcheck | Custom `mysqladmin ping` with 10 retries, 60s start period |
|
|
|
|
---
|
|
|
|
## Queue Worker
|
|
|
|
**Not needed.** The app contains:
|
|
- Zero job classes (`app/Jobs/` empty)
|
|
- Zero `dispatch()` calls in application code
|
|
- Zero `ShouldQueue` implementations
|
|
- `QUEUE_CONNECTION=database` is an unused default
|
|
|
|
All operations (data CRUD, file uploads, spreadsheet export, zip creation) are synchronous in HTTP requests.
|
|
|
|
---
|
|
|
|
## External Connections
|
|
|
|
**None.** The app is fully self-contained:
|
|
- No outgoing HTTP/API calls
|
|
- No third-party services
|
|
- No webhooks or callbacks
|
|
- No social login
|
|
|
|
Browser-only external resources:
|
|
- `fonts.bunny.net` (Google Fonts proxy)
|
|
- `cdn.jsdelivr.net/npm/flatpickr` (datepicker)
|