fix(Docker): change ENV arg from 'prod' to 'production' to match actual ini filename
This commit is contained in:
@@ -51,7 +51,7 @@ RUN curl -sS https://getcomposer.org/installer | php \
|
||||
&& mv composer.phar /usr/local/bin/composer
|
||||
|
||||
# Installiere Xdebug nur im Entwicklungsmodus
|
||||
ARG ENV=prod
|
||||
ARG ENV=production
|
||||
RUN if [ "$ENV" = "dev" ]; then \
|
||||
pecl install xdebug \
|
||||
&& docker-php-ext-enable xdebug; \
|
||||
@@ -108,3 +108,136 @@ RUN apt-get update && apt-get install -y gosu && apt-get clean && rm -rf /var/li
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD ["php-fpm"]
|
||||
|
||||
#############################################
|
||||
# Production Stages (Extended Build)
|
||||
#############################################
|
||||
|
||||
# Stage: Composer Dependencies (Production)
|
||||
FROM base AS composer-deps-production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy composer files
|
||||
COPY composer.json composer.lock ./
|
||||
|
||||
# Install production dependencies only
|
||||
RUN composer install \
|
||||
--no-dev \
|
||||
--no-scripts \
|
||||
--no-autoloader \
|
||||
--ignore-platform-reqs \
|
||||
--prefer-dist
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Generate optimized autoloader
|
||||
RUN composer dump-autoload --optimize --classmap-authoritative
|
||||
|
||||
# Stage: Frontend Build
|
||||
FROM node:20-alpine AS frontend-build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
# Install npm dependencies
|
||||
RUN npm ci --production=false
|
||||
|
||||
# Copy source files needed for build
|
||||
COPY resources ./resources
|
||||
COPY vite.config.js ./
|
||||
COPY tsconfig.json ./
|
||||
|
||||
# Build production assets
|
||||
RUN npm run build
|
||||
|
||||
# Stage: Production Runtime
|
||||
FROM php:8.5.0RC3-fpm AS production
|
||||
|
||||
# Install system dependencies + nginx for production
|
||||
RUN apt-get update && apt-get install -y \
|
||||
nginx \
|
||||
curl \
|
||||
libzip-dev \
|
||||
libpng-dev \
|
||||
libjpeg-dev \
|
||||
libfreetype6-dev \
|
||||
libwebp-dev \
|
||||
libavif-dev \
|
||||
libxpm-dev \
|
||||
libsodium-dev \
|
||||
libpq-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PHP extensions (same as base)
|
||||
RUN docker-php-ext-configure gd \
|
||||
--with-freetype \
|
||||
--with-jpeg \
|
||||
--with-webp \
|
||||
--with-avif \
|
||||
--with-xpm \
|
||||
&& docker-php-ext-install -j$(nproc) gd
|
||||
|
||||
RUN docker-php-ext-install -j$(nproc) \
|
||||
zip \
|
||||
pdo \
|
||||
pdo_mysql \
|
||||
pdo_pgsql \
|
||||
pcntl \
|
||||
posix \
|
||||
shmop \
|
||||
bcmath \
|
||||
sodium
|
||||
|
||||
# Install PECL extensions
|
||||
RUN pecl install apcu redis-6.3.0RC1 \
|
||||
&& docker-php-ext-enable apcu redis
|
||||
|
||||
# Configure APCu
|
||||
RUN echo "apc.enable_cli=1" >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||
&& echo "apc.shm_size=128M" >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Configure PHP for production
|
||||
COPY docker/php/php.production.ini /usr/local/etc/php/conf.d/zzz-custom.ini
|
||||
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
|
||||
|
||||
# Configure PHP-FPM (clear_env = no for Docker secrets)
|
||||
COPY docker/php/www.production.conf /usr/local/etc/php-fpm.d/www.conf
|
||||
|
||||
# Configure Nginx for Traefik integration
|
||||
COPY docker/nginx/nginx.production.conf /etc/nginx/nginx.conf
|
||||
COPY docker/nginx/default.traefik.conf /etc/nginx/sites-available/default
|
||||
RUN ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
|
||||
|
||||
# Copy application code and dependencies
|
||||
COPY --chown=www-data:www-data . .
|
||||
COPY --from=composer-deps-production --chown=www-data:www-data /app/vendor ./vendor
|
||||
COPY --from=frontend-build --chown=www-data:www-data /app/public/assets ./public/assets
|
||||
COPY --from=frontend-build --chown=www-data:www-data /app/public/.vite ./public/.vite
|
||||
|
||||
# Create required directories
|
||||
RUN mkdir -p storage/logs storage/cache storage/uploads \
|
||||
&& chown -R www-data:www-data storage \
|
||||
&& chmod -R 775 storage
|
||||
|
||||
# Copy entrypoint script for production
|
||||
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Healthcheck
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost/health || exit 1
|
||||
|
||||
# Expose HTTP only (Traefik handles HTTPS)
|
||||
EXPOSE 80
|
||||
|
||||
# Use production entrypoint
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
CMD ["nginx-php-fpm"]
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
; php.ini für Produktion
|
||||
include = php.common.ini
|
||||
|
||||
[opcache]
|
||||
; Aktiviere OPcache
|
||||
opcache.enable=1
|
||||
; Aktiviere OPcache für CLI-Anwendungen (optional)
|
||||
opcache.enable_cli=0
|
||||
; Maximale Speichernutzung für Cache in MB
|
||||
opcache.memory_consumption=128
|
||||
; Maximale Anzahl an gecachten Dateien
|
||||
opcache.max_accelerated_files=10000
|
||||
; Wie oft wird der Cache validiert (0 = bei jedem Request)
|
||||
; TEMPORÄR: 0 für einfachere Deployments während aktiver Entwicklung
|
||||
; SPÄTER: Auf 60 erhöhen wenn System stabil ist
|
||||
opcache.revalidate_freq=0
|
||||
; Cache-Zeitstempel prüfen
|
||||
; TEMPORÄR: 1 aktiviert für Deployment-Flexibilität
|
||||
; SPÄTER: Auf 0 setzen für maximale Performance wenn stabil
|
||||
opcache.validate_timestamps=1
|
||||
; Performance-Optimierungen
|
||||
opcache.interned_strings_buffer=16
|
||||
; JIT (Just-In-Time Compilation) - Optional für PHP 8.0+
|
||||
opcache.jit_buffer_size=100M
|
||||
opcache.jit=1255
|
||||
|
||||
|
||||
display_errors = Off
|
||||
display_startup_errors = Off
|
||||
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||
memory_limit = 256M
|
||||
upload_max_filesize = 10M
|
||||
post_max_size = 12M
|
||||
max_execution_time = 30
|
||||
@@ -10,11 +10,14 @@ opcache.enable_cli=0
|
||||
opcache.memory_consumption=128
|
||||
; Maximale Anzahl an gecachten Dateien
|
||||
opcache.max_accelerated_files=10000
|
||||
; Wie oft wird der Cache validiert (0 = bei jedem Request, empfohlen für Entwicklung)
|
||||
; In Produktion höher setzen für bessere Performance
|
||||
opcache.revalidate_freq=60
|
||||
; Cache-Zeitstempel prüfen (0 für Produktionsumgebungen)
|
||||
opcache.validate_timestamps=0
|
||||
; Wie oft wird der Cache validiert (0 = bei jedem Request)
|
||||
; TEMPORÄR: 0 für einfachere Deployments während aktiver Entwicklung
|
||||
; SPÄTER: Auf 60 erhöhen wenn System stabil ist
|
||||
opcache.revalidate_freq=0
|
||||
; Cache-Zeitstempel prüfen
|
||||
; TEMPORÄR: 1 aktiviert für Deployment-Flexibilität
|
||||
; SPÄTER: Auf 0 setzen für maximale Performance wenn stabil
|
||||
opcache.validate_timestamps=1
|
||||
; Performance-Optimierungen
|
||||
opcache.interned_strings_buffer=16
|
||||
; JIT (Just-In-Time Compilation) - Optional für PHP 8.0+
|
||||
|
||||
58
src/Framework/Cache/Strategies/AccessPattern.php
Normal file
58
src/Framework/Cache/Strategies/AccessPattern.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Cache\Strategies;
|
||||
|
||||
/**
|
||||
* Access pattern tracking for adaptive TTL calculation
|
||||
*/
|
||||
final class AccessPattern
|
||||
{
|
||||
/** @var array<int, Timestamp> */
|
||||
private array $accessTimes = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly int $windowSize
|
||||
) {}
|
||||
|
||||
public function recordAccess(): void
|
||||
{
|
||||
$this->accessTimes[] = Timestamp::now();
|
||||
|
||||
// Keep only recent accesses within the window
|
||||
if (count($this->accessTimes) > $this->windowSize) {
|
||||
array_shift($this->accessTimes);
|
||||
}
|
||||
}
|
||||
|
||||
public function getRecentAccessCount(): int
|
||||
{
|
||||
return count($this->accessTimes);
|
||||
}
|
||||
|
||||
public function getTotalAccesses(): int
|
||||
{
|
||||
return count($this->accessTimes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate access frequency (accesses per hour)
|
||||
*/
|
||||
public function getAccessFrequency(): float
|
||||
{
|
||||
if (count($this->accessTimes) < 2) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$oldest = $this->accessTimes[0];
|
||||
$newest = end($this->accessTimes);
|
||||
$timeSpan = $newest->diff($oldest);
|
||||
|
||||
if ($timeSpan->toSeconds() <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$hours = $timeSpan->toSeconds() / 3600.0;
|
||||
|
||||
return count($this->accessTimes) / $hours;
|
||||
}
|
||||
}
|
||||
44
src/Framework/Cache/Strategies/AdaptiveTtlStats.php
Normal file
44
src/Framework/Cache/Strategies/AdaptiveTtlStats.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Cache\Strategies;
|
||||
|
||||
/**
|
||||
* Statistics for adaptive TTL optimization
|
||||
*/
|
||||
final class AdaptiveTtlStats
|
||||
{
|
||||
private int $hits = 0;
|
||||
|
||||
private int $misses = 0;
|
||||
|
||||
public function recordHitMiss(bool $isHit): void
|
||||
{
|
||||
if ($isHit) {
|
||||
$this->hits++;
|
||||
} else {
|
||||
$this->misses++;
|
||||
}
|
||||
}
|
||||
|
||||
public function getHitRate(): float
|
||||
{
|
||||
$total = $this->hits + $this->misses;
|
||||
|
||||
return $total > 0 ? ($this->hits / $total) : 0.0;
|
||||
}
|
||||
|
||||
public function getTotalRequests(): int
|
||||
{
|
||||
return $this->hits + $this->misses;
|
||||
}
|
||||
|
||||
public function getHits(): int
|
||||
{
|
||||
return $this->hits;
|
||||
}
|
||||
|
||||
public function getMisses(): int
|
||||
{
|
||||
return $this->misses;
|
||||
}
|
||||
}
|
||||
90
src/Framework/Cache/Strategies/HeatMapEntry.php
Normal file
90
src/Framework/Cache/Strategies/HeatMapEntry.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Cache\Strategies;
|
||||
|
||||
/**
|
||||
* Heat map entry for tracking cache key usage
|
||||
*/
|
||||
final class HeatMapEntry
|
||||
{
|
||||
/** @var array<array{timestamp: Timestamp, is_hit: bool, retrieval_time: ?float}> */
|
||||
private array $accesses = [];
|
||||
|
||||
private int $totalHits = 0;
|
||||
|
||||
private int $totalMisses = 0;
|
||||
|
||||
private float $totalRetrievalTime = 0.0;
|
||||
|
||||
private int $retrievalTimeCount = 0;
|
||||
|
||||
public function __construct(
|
||||
private readonly CacheKey $key
|
||||
) {}
|
||||
|
||||
public function recordAccess(bool $isHit, ?Duration $retrievalTime = null): void
|
||||
{
|
||||
$this->accesses[] = [
|
||||
'timestamp' => Timestamp::now(),
|
||||
'is_hit' => $isHit,
|
||||
'retrieval_time' => $retrievalTime?->toSeconds(),
|
||||
];
|
||||
|
||||
if ($isHit) {
|
||||
$this->totalHits++;
|
||||
} else {
|
||||
$this->totalMisses++;
|
||||
}
|
||||
|
||||
if ($retrievalTime !== null) {
|
||||
$this->totalRetrievalTime += $retrievalTime->toSeconds();
|
||||
$this->retrievalTimeCount++;
|
||||
}
|
||||
|
||||
// Keep only recent data to prevent memory bloat
|
||||
$cutoff = Timestamp::now()->subtract(Duration::fromHours(48)); // Keep 48 hours
|
||||
$this->accesses = array_filter(
|
||||
$this->accesses,
|
||||
fn($access) => $access['timestamp']->isAfter($cutoff)
|
||||
);
|
||||
}
|
||||
|
||||
public function getRecentAccesses(Timestamp $since): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->accesses,
|
||||
fn($access) => $access['timestamp']->isAfter($since)
|
||||
);
|
||||
}
|
||||
|
||||
public function getHitRate(): float
|
||||
{
|
||||
$total = $this->totalHits + $this->totalMisses;
|
||||
|
||||
return $total > 0 ? ($this->totalHits / $total) : 0.0;
|
||||
}
|
||||
|
||||
public function getAverageRetrievalTime(): float
|
||||
{
|
||||
return $this->retrievalTimeCount > 0 ? ($this->totalRetrievalTime / $this->retrievalTimeCount) : 0.0;
|
||||
}
|
||||
|
||||
public function getTotalAccesses(): int
|
||||
{
|
||||
return count($this->accesses);
|
||||
}
|
||||
|
||||
public function getLastAccessTime(): ?Timestamp
|
||||
{
|
||||
if (empty($this->accesses)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return end($this->accesses)['timestamp'];
|
||||
}
|
||||
|
||||
public function getKey(): CacheKey
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
}
|
||||
71
src/Framework/Cache/Strategies/PredictionPattern.php
Normal file
71
src/Framework/Cache/Strategies/PredictionPattern.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Cache\Strategies;
|
||||
|
||||
/**
|
||||
* Prediction pattern for a cache key
|
||||
*/
|
||||
final class PredictionPattern
|
||||
{
|
||||
/** @var array<array{timestamp: Timestamp, context: array}> */
|
||||
private array $accesses = [];
|
||||
|
||||
/** @var array<CacheKey> */
|
||||
private array $dependencies = [];
|
||||
|
||||
private mixed $warmingCallback = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly CacheKey $key
|
||||
) {}
|
||||
|
||||
public function recordAccess(Timestamp $timestamp, array $context = []): void
|
||||
{
|
||||
$this->accesses[] = [
|
||||
'timestamp' => $timestamp,
|
||||
'context' => $context,
|
||||
];
|
||||
|
||||
// Keep only recent accesses to prevent memory bloat
|
||||
$cutoff = $timestamp->subtract(Duration::fromHours(168)); // 1 week
|
||||
$this->accesses = array_filter(
|
||||
$this->accesses,
|
||||
fn($access) => $access['timestamp']->isAfter($cutoff)
|
||||
);
|
||||
}
|
||||
|
||||
public function addDependency(CacheKey $dependentKey): void
|
||||
{
|
||||
$this->dependencies[] = $dependentKey;
|
||||
}
|
||||
|
||||
public function setWarmingCallback(callable $callback): void
|
||||
{
|
||||
$this->warmingCallback = $callback;
|
||||
}
|
||||
|
||||
public function getKey(): CacheKey
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function getRecentAccesses(int $hours): array
|
||||
{
|
||||
$cutoff = Timestamp::now()->subtract(Duration::fromHours($hours));
|
||||
|
||||
return array_filter(
|
||||
$this->accesses,
|
||||
fn($access) => $access['timestamp']->isAfter($cutoff)
|
||||
);
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
public function getWarmingCallback(): ?callable
|
||||
{
|
||||
return $this->warmingCallback;
|
||||
}
|
||||
}
|
||||
16
src/Framework/Cache/Strategies/WarmingJob.php
Normal file
16
src/Framework/Cache/Strategies/WarmingJob.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Cache\Strategies;
|
||||
|
||||
/**
|
||||
* Active warming job
|
||||
*/
|
||||
final readonly class WarmingJob
|
||||
{
|
||||
public function __construct(
|
||||
public CacheKey $key,
|
||||
public mixed $callback,
|
||||
public string $reason,
|
||||
public Timestamp $startTime
|
||||
) {}
|
||||
}
|
||||
16
src/Framework/Cache/Strategies/WarmingResult.php
Normal file
16
src/Framework/Cache/Strategies/WarmingResult.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Cache\Strategies;
|
||||
|
||||
/**
|
||||
* Warming operation result
|
||||
*/
|
||||
final readonly class WarmingResult
|
||||
{
|
||||
public function __construct(
|
||||
public CacheKey $key,
|
||||
public bool $successful,
|
||||
public Duration $duration,
|
||||
public string $reason
|
||||
) {}
|
||||
}
|
||||
16
src/Framework/Cache/Strategies/WriteOperation.php
Normal file
16
src/Framework/Cache/Strategies/WriteOperation.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Cache\Strategies;
|
||||
|
||||
/**
|
||||
* Write operation tracking
|
||||
*/
|
||||
final readonly class WriteOperation
|
||||
{
|
||||
public function __construct(
|
||||
public CacheKey $key,
|
||||
public int $valueSize,
|
||||
public Duration $writeTime,
|
||||
public Timestamp $timestamp
|
||||
) {}
|
||||
}
|
||||
Reference in New Issue
Block a user