Compare commits
No commits in common. "d8da3ba678ccc0f7aa7110c43d10fd1c89ebd950" and "862b9f6c1632289e90d77d0bbd7e7001244210ab" have entirely different histories.
d8da3ba678
...
862b9f6c16
|
|
@ -1,37 +1,10 @@
|
|||
node_modules
|
||||
vendor
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.sisyphus/
|
||||
.dev.pid
|
||||
.DS_Store
|
||||
.editorconfig
|
||||
.php-cs-fixer.cache
|
||||
|
||||
vendor/
|
||||
node_modules/
|
||||
|
||||
tests/
|
||||
test-results/
|
||||
phpunit.xml
|
||||
playwright.config.ts
|
||||
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
docs/
|
||||
README.md
|
||||
ref/
|
||||
|
||||
start_dev.sh
|
||||
stop_dev.sh
|
||||
use_local_pp_lib.sh
|
||||
|
||||
storage/logs/*
|
||||
storage/framework/cache/*
|
||||
storage/framework/views/*
|
||||
storage/framework/sessions/*
|
||||
test-results
|
||||
tests/e2e/.auth
|
||||
.dev.pid
|
||||
|
|
|
|||
169
build/Dockerfile
169
build/Dockerfile
|
|
@ -1,145 +1,44 @@
|
|||
# =============================================================================
|
||||
# Stage 1: Build
|
||||
# Installs all PHP + Node dependencies and builds Vite assets.
|
||||
# This stage is NOT used in production — only its outputs are copied.
|
||||
# =============================================================================
|
||||
FROM php:8.4-cli-bookworm AS build
|
||||
FROM php:8.4-fpm-alpine
|
||||
|
||||
# System deps for build stage
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
unzip \
|
||||
zip \
|
||||
curl \
|
||||
libzip-dev \
|
||||
libfreetype6-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libpng-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add --no-cache \
|
||||
curl \
|
||||
git \
|
||||
zip \
|
||||
unzip \
|
||||
libzip-dev \
|
||||
imagemagick \
|
||||
libreoffice \
|
||||
libreoffice-lang-de \
|
||||
ghostscript \
|
||||
poppler-utils \
|
||||
sqlite \
|
||||
sqlite-dev \
|
||||
postgresql-client \
|
||||
mysql-client \
|
||||
nodejs \
|
||||
npm \
|
||||
fcgi
|
||||
|
||||
# PHP extensions needed for composer install / post-autoload-dump
|
||||
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install gd zip
|
||||
RUN docker-php-ext-install \
|
||||
pdo_sqlite \
|
||||
pdo_mysql \
|
||||
zip \
|
||||
bcmath
|
||||
|
||||
# Install Node.js 20 LTS via NodeSource
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Composer
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ── PHP dependency layer (cached until composer.lock changes) ────────────────
|
||||
COPY composer.json composer.lock ./
|
||||
RUN composer install \
|
||||
--no-dev \
|
||||
--optimize-autoloader \
|
||||
--no-scripts \
|
||||
--no-interaction \
|
||||
--prefer-dist
|
||||
COPY build/fpm-healthcheck.conf /usr/local/etc/php-fpm.d/zz-healthcheck.conf
|
||||
COPY build/php-error-logging.conf /usr/local/etc/php-fpm.d/zz-error-logging.conf
|
||||
COPY build/php-errors.ini /usr/local/etc/php/conf.d/errors.ini
|
||||
COPY build/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# ── Node dependency layer (cached until package-lock.json changes) ───────────
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --ignore-scripts
|
||||
|
||||
# ── Full source + build ──────────────────────────────────────────────────────
|
||||
COPY . .
|
||||
|
||||
# Run composer post-autoload-dump scripts (package discovery etc.)
|
||||
RUN composer run-script post-autoload-dump --no-interaction || true
|
||||
|
||||
# Build Vite assets (outputs to public/build/)
|
||||
RUN npm run build
|
||||
|
||||
# Copy public/ → public-build/ so the production stage retains built assets
|
||||
# even after public/ becomes a runtime bind-mount target.
|
||||
RUN cp -r public/ public-build/
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Stage 2: Production
|
||||
# PHP-FPM runtime image with all system dependencies.
|
||||
# Node.js is NOT present here.
|
||||
# =============================================================================
|
||||
FROM php:8.4-fpm-bookworm AS production
|
||||
|
||||
# System packages (excluding Node.js — not needed at runtime)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
supervisor \
|
||||
libzip-dev \
|
||||
libfreetype6-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libpng-dev \
|
||||
libsqlite3-dev \
|
||||
default-mysql-client \
|
||||
ghostscript \
|
||||
poppler-utils \
|
||||
sqlite3 \
|
||||
libfcgi-bin \
|
||||
unzip \
|
||||
zip \
|
||||
curl \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# LibreOffice for PowerPoint → PDF conversion (large layer, separate cache)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libreoffice-impress \
|
||||
libreoffice-l10n-de \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# PHP extensions
|
||||
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install \
|
||||
gd \
|
||||
pdo_sqlite \
|
||||
pdo_mysql \
|
||||
zip \
|
||||
bcmath
|
||||
|
||||
# Install Composer (needed for artisan commands, not for installing packages)
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ── Copy built application from build stage ──────────────────────────────────
|
||||
COPY --from=build /app /app
|
||||
|
||||
# Remove node_modules — not needed at runtime
|
||||
RUN rm -rf /app/node_modules
|
||||
|
||||
# ── Install Docker config files ──────────────────────────────────────────────
|
||||
# Supervisord (manages php-fpm, queue:work, schedule:work)
|
||||
COPY build/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# PHP runtime config (upload limits, error logging, timezone)
|
||||
COPY build/php.ini /usr/local/etc/php/conf.d/zz-app.ini
|
||||
|
||||
# FPM pool config (replaces default www pool — sets ping endpoint, clear_env=no)
|
||||
COPY build/fpm-pool.conf /usr/local/etc/php-fpm.d/zz-www.conf
|
||||
|
||||
# FPM error logging to stderr
|
||||
COPY build/fpm-error-logging.conf /usr/local/etc/php-fpm.d/zz-error-logging.conf
|
||||
|
||||
# Remove default FPM pool config (replaced by our zz-www.conf above)
|
||||
RUN rm -f /usr/local/etc/php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf.default
|
||||
|
||||
# ── Boot / Init scripts ───────────────────────────────────────────────────────
|
||||
RUN chmod +x /app/build/boot-container.sh /app/build/init-app.sh
|
||||
|
||||
# ── Runtime setup ─────────────────────────────────────────────────────────────
|
||||
EXPOSE 9000
|
||||
|
||||
# Healthcheck: FPM ping endpoint (starts checking after 60s to allow boot)
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET \
|
||||
cgi-fcgi -bind -connect 127.0.0.1:9000 2>/dev/null | grep -q "pong" || exit 1
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||
CMD SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 | grep -q pong || exit 1
|
||||
|
||||
# boot-container.sh runs as root: creates dirs, sets permissions,
|
||||
# creates DB on first run, runs migrations, runs cache commands,
|
||||
# syncs built assets to the bind-mounted public/ directory,
|
||||
# then exec's supervisord (CMD).
|
||||
ENTRYPOINT ["/app/build/boot-container.sh"]
|
||||
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
CMD ["php-fpm"]
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd /app
|
||||
|
||||
echo "[boot] Starting pp-planer container boot..."
|
||||
|
||||
# Ensure all required directories exist (volumes may be freshly mounted)
|
||||
mkdir -p \
|
||||
storage/logs \
|
||||
storage/framework/views \
|
||||
storage/framework/cache/data \
|
||||
storage/framework/sessions \
|
||||
storage/app/public \
|
||||
database \
|
||||
public
|
||||
|
||||
# Run first-time initialization (idempotent — safe to call every boot)
|
||||
/app/build/init-app.sh
|
||||
|
||||
# Fix permissions: www-data must own all writable directories.
|
||||
# The || true prevents exit on macOS Docker Desktop (bind-mount ownership restrictions).
|
||||
# On a Linux host running as root, chown will succeed silently.
|
||||
chown -R www-data:www-data storage bootstrap/cache database 2>/dev/null || true
|
||||
chmod -R 775 storage bootstrap/cache database 2>/dev/null || true
|
||||
|
||||
# Sync built Vite assets from the image (public-build/) into the
|
||||
# bind-mounted public/ directory. This ensures Caddy always serves
|
||||
# up-to-date assets even though public/ is a host bind-mount.
|
||||
if [ -d /app/public-build ]; then
|
||||
echo "[boot] Syncing built assets to public/..."
|
||||
cp -r /app/public-build/. /app/public/
|
||||
chown -R www-data:www-data /app/public 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Create the storage symlink after volumes are mounted
|
||||
# (public/storage → storage/app/public)
|
||||
echo "[boot] Creating storage symlink..."
|
||||
php artisan storage:link --force 2>/dev/null || true
|
||||
|
||||
# Run database migrations
|
||||
echo "[boot] Running migrations..."
|
||||
php artisan migrate --force
|
||||
|
||||
# Warm up Laravel caches
|
||||
echo "[boot] Warming caches..."
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan event:cache
|
||||
|
||||
echo "[boot] Boot complete. Starting supervisord..."
|
||||
|
||||
# Hand off to CMD (supervisord)
|
||||
exec "$@"
|
||||
26
build/entrypoint.sh
Executable file
26
build/entrypoint.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ -n "$WWWUSER" ] && [ "$WWWUSER" != "0" ]; then
|
||||
deluser www-data 2>/dev/null || true
|
||||
adduser -D -u "$WWWUSER" -G www-data www-data 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -n "$WWWGROUP" ] && [ "$WWWGROUP" != "0" ]; then
|
||||
delgroup www-data 2>/dev/null || true
|
||||
addgroup -g "$WWWGROUP" www-data 2>/dev/null || true
|
||||
fi
|
||||
|
||||
composer install --no-interaction
|
||||
npm install
|
||||
mkdir -p storage/logs storage/framework/views storage/framework/cache storage/framework/sessions
|
||||
chown -R www-data:www-data storage bootstrap/cache
|
||||
chmod -R 775 storage bootstrap/cache
|
||||
|
||||
if [ "$APP_ENV" = "production" ] || [ "$APP_ENV" = "prod" ]; then
|
||||
npm run build
|
||||
fi
|
||||
|
||||
php artisan migrate --force
|
||||
|
||||
exec "$@"
|
||||
3
build/fpm-healthcheck.conf
Normal file
3
build/fpm-healthcheck.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[www]
|
||||
ping.path = /ping
|
||||
ping.response = pong
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
; PHP-FPM pool configuration for Laravel
|
||||
|
||||
[www]
|
||||
user = www-data
|
||||
group = www-data
|
||||
|
||||
; Listen on TCP (needed for Docker networking with Caddy)
|
||||
listen = 9000
|
||||
listen.owner = www-data
|
||||
listen.group = www-data
|
||||
|
||||
; Process management
|
||||
pm = dynamic
|
||||
pm.max_children = 10
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
pm.max_requests = 500
|
||||
|
||||
; Healthcheck ping endpoint
|
||||
ping.path = /ping
|
||||
ping.response = pong
|
||||
|
||||
; Pass environment variables from Docker into PHP
|
||||
clear_env = no
|
||||
|
||||
; Access log to /dev/null (access logs bloat container logs)
|
||||
access.log = /dev/null
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd /app
|
||||
|
||||
DB_PATH="/app/database/database.sqlite"
|
||||
|
||||
if [ -f "$DB_PATH" ]; then
|
||||
echo "[init] Database already exists, skipping first-run init."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[init] First run detected — initializing application..."
|
||||
|
||||
touch "$DB_PATH"
|
||||
chmod 664 "$DB_PATH"
|
||||
|
||||
if [ -z "${APP_KEY}" ]; then
|
||||
echo "[init] Generating application key..."
|
||||
php artisan key:generate --force
|
||||
fi
|
||||
|
||||
echo "[init] First-run init complete."
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
; FPM worker error logging to stderr (Docker logs)
|
||||
|
||||
[www]
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
||||
php_admin_flag[log_errors] = on
|
||||
php_admin_value[error_log] = /dev/stderr
|
||||
php_admin_value[error_log] = /proc/self/fd/2
|
||||
5
build/php-errors.ini
Normal file
5
build/php-errors.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
log_errors = On
|
||||
error_log = /proc/self/fd/2
|
||||
error_reporting = E_ALL
|
||||
display_errors = Off
|
||||
display_startup_errors = On
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
; Production PHP configuration for Laravel Docker deployment
|
||||
|
||||
; Upload limits (matches UPLOAD_MAX_FILE_SIZE in .env = 100MB)
|
||||
upload_max_filesize = 100M
|
||||
post_max_size = 110M
|
||||
|
||||
; Memory and execution
|
||||
memory_limit = 256M
|
||||
max_execution_time = 300
|
||||
max_input_time = 300
|
||||
|
||||
; Error logging (logs to stderr → Docker logs)
|
||||
log_errors = On
|
||||
error_log = /dev/stderr
|
||||
error_reporting = E_ALL
|
||||
display_errors = Off
|
||||
display_startup_errors = Off
|
||||
|
||||
; Timezone
|
||||
date.timezone = Europe/Berlin
|
||||
|
||||
; File uploads temp dir
|
||||
upload_tmp_dir = /tmp
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
pidfile=/var/run/supervisord.pid
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///var/run/supervisor.sock
|
||||
|
||||
[unix_http_server]
|
||||
file=/var/run/supervisor.sock
|
||||
chmod=0700
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[program:php-fpm]
|
||||
command=php-fpm --nodaemonize
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=10
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
stdout_events_enabled=true
|
||||
stderr_events_enabled=true
|
||||
|
||||
[program:laravel-worker]
|
||||
command=php /app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=www-data
|
||||
priority=20
|
||||
numprocs=1
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
stdout_events_enabled=true
|
||||
stderr_events_enabled=true
|
||||
|
||||
[program:laravel-scheduler]
|
||||
command=php /app/artisan schedule:work
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=www-data
|
||||
priority=20
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
stdout_events_enabled=true
|
||||
stderr_events_enabled=true
|
||||
|
|
@ -37,8 +37,8 @@
|
|||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
'busy_timeout' => 5000,
|
||||
'journal_mode' => 'wal',
|
||||
'busy_timeout' => null,
|
||||
'journal_mode' => null,
|
||||
'synchronous' => null,
|
||||
'transaction_mode' => 'DEFERRED',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,23 +1,3 @@
|
|||
# =============================================================================
|
||||
# Caddy configuration (add to your Caddy instance's Caddyfile):
|
||||
#
|
||||
# your-domain.example.com {
|
||||
# root * /path/on/caddy/host/to/pp-planer/public
|
||||
# php_fastcgi pp-planer-app:9000 {
|
||||
# root /app/public
|
||||
# }
|
||||
# file_server
|
||||
# encode gzip
|
||||
# }
|
||||
#
|
||||
# Notes:
|
||||
# - "root *" = path on the Caddy HOST to the bind-mounted ./public directory
|
||||
# - "root /app/public" inside php_fastcgi = path INSIDE this container
|
||||
# - "pp-planer-app" resolves via the shared "caddy" Docker network
|
||||
# - Both Caddy and this container must be on the "caddy" external network
|
||||
# - The ./public directory is bind-mounted so Caddy can serve static files
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
|
|
@ -25,23 +5,54 @@ services:
|
|||
dockerfile: build/Dockerfile
|
||||
container_name: pp-planer-app
|
||||
restart: unless-stopped
|
||||
working_dir: /app
|
||||
environment:
|
||||
- WWWUSER=${WWWUSER:-1000}
|
||||
- WWWGROUP=${WWWGROUP:-1000}
|
||||
- APP_ENV=${APP_ENV}
|
||||
- APP_DEBUG=${APP_DEBUG}
|
||||
- APP_KEY=${APP_KEY}
|
||||
- APP_URL=${APP_URL}
|
||||
- DB_CONNECTION=${DB_CONNECTION}
|
||||
- CTS_API_URL=${CTS_API_URL}
|
||||
- CTS_API_TOKEN=${CTS_API_TOKEN}
|
||||
- CHURCHTOOLS_URL=${CHURCHTOOLS_URL}
|
||||
- CHURCHTOOLS_CLIENT_ID=${CHURCHTOOLS_CLIENT_ID}
|
||||
- CHURCHTOOLS_CLIENT_SECRET=${CHURCHTOOLS_CLIENT_SECRET}
|
||||
- CHURCHTOOLS_REDIRECT_URI=${CHURCHTOOLS_REDIRECT_URI}
|
||||
volumes:
|
||||
- ./.env:/app/.env:ro
|
||||
- ./storage:/app/storage
|
||||
- ./database:/app/database
|
||||
- ./public:/app/public
|
||||
- ./:/app
|
||||
- /app/node_modules
|
||||
- /app/vendor
|
||||
expose:
|
||||
- "9000"
|
||||
networks:
|
||||
- caddy
|
||||
- internal
|
||||
|
||||
node:
|
||||
image: node:20-alpine
|
||||
container_name: pp-planer-node
|
||||
restart: unless-stopped
|
||||
working_dir: /app
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
volumes:
|
||||
- ./:/app
|
||||
- /app/node_modules
|
||||
expose:
|
||||
- "5173"
|
||||
networks:
|
||||
- caddy
|
||||
- internal
|
||||
command: sh -c "npm install && npm run dev"
|
||||
|
||||
# mysql:
|
||||
# image: mysql:8.0
|
||||
# container_name: pp-planer-mysql
|
||||
# restart: unless-stopped
|
||||
# environment:
|
||||
# MYSQL_DATABASE: ${DB_DATABASE:-pp_planer}
|
||||
# MYSQL_DATABASE: ${DB_DATABASE}
|
||||
# MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
|
||||
# MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
# MYSQL_USER: ${DB_USERNAME}
|
||||
|
|
@ -50,9 +61,6 @@ services:
|
|||
# networks:
|
||||
# - internal
|
||||
|
||||
# volumes:
|
||||
# mysql_data:
|
||||
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
||||
|
|
|
|||
Loading…
Reference in a new issue