diff --git a/.env.example b/.env.example index 92355a4..0bb9e72 100644 --- a/.env.example +++ b/.env.example @@ -4,18 +4,19 @@ # ============================================ # Demo Mode (optional) # Set to 'true' to install demo landing page when no application exists -DEMO_MODE=false - +DEMO_MODE=true +# Mode debug (optional) 0=inactive 1=acrive +ENABLE_XDEBUG=1 # Health Check Installation (optional) # Set to 'true' to install health check endpoint # Automatically enabled when DEMO_MODE=true -HEALTH_CHECK_INSTALL=false +HEALTH_CHECK_INSTALL=true # ============================================ # Application # ============================================ APP_NAME=php-api-stack -APP_ENV=production -APP_DEBUG=false +APP_ENV=development +APP_DEBUG=true APP_PORT=8089 APP_DOMAIN=localhost @@ -36,17 +37,23 @@ COMPOSER_VERSION=2.8.12 SYMFONY_CLI_VERSION=5.15.1 # ============================================ -# PHP / Extensions +# PECL Extension Versions # ============================================ +PHP_REDIS_VERSION=6.1.0 +PHP_APCU_VERSION=5.1.24 +PHP_UUID_VERSION=1.2.1 +PHP_IMAGICK_VERSION=3.7.0 +PHP_AMQP_VERSION=2.1.2 +XDEBUG_VERSION=3.4.6 -# Core PHP extensions to install (installable only — built-ins are already available) -# Built-ins include: tokenizer, fileinfo, ctype, iconv, session, curl, etc. -# Available installable examples: pdo pdo_mysql pdo_pgsql opcache intl zip bcmath gd mysqli mbstring xml dom simplexml sockets pcntl exif +# ============================================ +# PHP / Extensions +# ============================================ +# Core PHP extensions (space-separated) +# IMPORTANTE: Use aspas duplas para evitar interpretação como comando PHP_CORE_EXTENSIONS="pdo pdo_mysql opcache intl zip bcmath gd mbstring xml sockets" -# PECL extensions to install -# Available: redis apcu uuid xdebug imagick amqp swoole -# NOTE: remove xdebug in production builds +# PECL extensions (space-separated) PHP_PECL_EXTENSIONS="redis apcu uuid" # ============================================ @@ -114,11 +121,13 @@ PHP_OPCACHE_JIT_BUFFER_SIZE=128M # Host / credentials REDIS_HOST=redis REDIS_PASSWORD=HmlRedis_3Qy7nFTZgW6M2bK9pX4c +# External Redis example service (compose profile) +REDIS_HOST_PORT=6378 # Database count used by the template REDIS_DATABASES=16 -# Memory policy — use lowercase units for Redis (e.g., 256mb) +# Memory policy – use lowercase units for Redis (e.g., 256mb) REDIS_MAXMEMORY=256mb REDIS_MAXMEMORY_POLICY=allkeys-lru REDIS_MAXMEMORY_SAMPLES=5 @@ -128,7 +137,6 @@ REDIS_MAXCLIENTS=10000 REDIS_TIMEOUT=0 # Persistence (AOF/RDB) -# If you keep REDIS_SAVE here, handle it in the entrypoint to expand into multiple "save ..." lines. REDIS_APPENDONLY=yes REDIS_APPENDFSYNC=everysec REDIS_SAVE="900 1 300 10 60 10000" @@ -158,17 +166,18 @@ ENABLE_HTTP2=true # ============================================ # Development (enable only in non-production) # ============================================ -XDEBUG_ENABLE=false +XDEBUG_ENABLE=1 XDEBUG_MODE=develop,debug,coverage XDEBUG_HOST=host.docker.internal XDEBUG_PORT=9003 -XDEBUG_IDE_KEY=PHPSTORM +XDEBUG_IDE_KEY=VSCODE +XDEBUG_VERSION=3.4.6 # ============================================ # Build / Registry # ============================================ BUILD_TARGET=production -IMAGE_TAG=latest +IMAGE_TAG=dev REGISTRY=docker.io REPOSITORY=kariricode/php-api-stack @@ -206,9 +215,6 @@ DB_USERNAME=phpapi_hml DB_PASSWORD=HmlUser_3kT8zQf DB_PORT=3307 -# External Redis example service (compose profile) -REDIS_HOST_PORT=6378 - # Grafana example service (compose profile) GRAFANA_PORT=3000 GRAFANA_PASSWORD=HmlGrafana_7uV4mRp diff --git a/.gitignore b/.gitignore index 5472b08..a5d2acc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,16 +6,18 @@ !.env.dev # Application -app/vendor/ -app/node_modules/ -app/var/ -app/.env -app/.env.local +./app/vendor/ +./app/node_modules/ +./app/var/ +./app/.env +./app/.env.local +./app/public/health.php +./app/public/index.php # Logs -logs/ +./logs/ *.log -/var/log/ +.//var/log/ # Docker docker-compose.override.yml @@ -23,14 +25,13 @@ docker-compose.override.yml *.tar.gz # SSL certificates -ssl/ +./ssl/ *.pem *.key *.crt # IDE .idea/ -.vscode/ *.swp *.swo *~ @@ -40,8 +41,8 @@ ssl/ Thumbs.db # Build artifacts -build/ -dist/ +./build/ +./dist/ # Test files test-app/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ca6b51b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003, + "pathMappings": { + "/var/www/html": "${workspaceFolder}" + }, + "log": true + } + ] +} diff --git a/DOCKER_HUB.md b/DOCKER_HUB.md index a8fefb4..47c4ba2 100644 --- a/DOCKER_HUB.md +++ b/DOCKER_HUB.md @@ -1,713 +1,707 @@ -# Docker Hub Publishing Guide +# KaririCode/php-api-stack -**Audience**: Image publishers and maintainers -**Purpose**: Complete guide for publishing images to Docker Hub +Production-ready **PHP API Stack** image built on Alpine Linux with **Nginx + PHP-FPM + Redis**. Secured, fast, and fully configurable via environment variables — ideal for modern APIs, web applications, and microservices. -## 📋 Table of Contents +

+ Docker Pulls + Docker Image Size + Docker Image Version + Source + KaririCode + Version + PHP + Alpine +

-- [Overview](#overview) -- [Prerequisites](#prerequisites) -- [Docker Hub Setup](#docker-hub-setup) -- [Versioning Strategy](#versioning-strategy) -- [Build Process](#build-process) -- [Tagging Strategy](#tagging-strategy) -- [Publishing Process](#publishing-process) -- [Multi-Platform Builds](#multi-platform-builds) -- [Automated Publishing](#automated-publishing) -- [Best Practices](#best-practices) -- [Troubleshooting](#troubleshooting) +--- -## 🎯 Overview +## 🔗 Official Links -This guide covers the complete workflow for publishing **kariricode/php-api-stack** to Docker Hub, including: +* **Docker Hub**: [https://hub.docker.com/r/kariricode/php-api-stack](https://hub.docker.com/r/kariricode/php-api-stack) +* **GitHub Repository**: [https://github.com/kariricode/php-api-stack](https://github.com/kariricode/php-api-stack) +* **Documentation**: [Full guides in GitHub repository](https://github.com/kariricode/php-api-stack#documentation) +* **Official Site**: [https://kariricode.org/](https://kariricode.org/) +* **KaririCode Framework**: [https://github.com/KaririCode-Framework](https://github.com/KaririCode-Framework) -- ✅ Proper versioning and tagging -- ✅ Multi-platform builds (amd64, arm64) -- ✅ Automated CI/CD pipelines -- ✅ Quality gates and validation -- ✅ Documentation synchronization +--- -## 🔧 Prerequisites +## ✨ Highlights -### Required Accounts +### Core Stack +* **Complete Integration**: Nginx 1.27.3 + PHP-FPM 8.4.13 + Redis 7.2.11 +* **Alpine Linux 3.21**: Minimal footprint (~225MB production, ~244MB dev) +* **Multi-platform**: Native support for amd64 and arm64 -1. **Docker Hub Account** - - Username: `kariricode` (or your organization) - - Repository: `php-api-stack` - - Access: Write permissions +### Performance +* **OPcache + JIT**: Tracing mode enabled by default for maximum performance +* **Optimized Configuration**: Tuned Nginx/PHP-FPM with Unix socket communication +* **FastCGI Cache**: Built-in for accelerated response times +* **Static Assets**: Direct Nginx serving with aggressive caching -2. **GitHub Account** (for CI/CD) - - Repository access - - Secrets configuration +### Security +* **Non-root Services**: All services run as unprivileged users +* **Hardened Defaults**: Security headers (CSP, HSTS, X-Frame-Options) +* **Rate Limiting**: Built-in protection against abuse +* **Regular Updates**: Automated security patches and vulnerability scanning -### Required Tools +### Developer Experience +* **100% Configurable**: All settings via environment variables +* **Three Specialized Makefiles**: Build, Docker Hub, and Compose operations (50+ commands) +* **Comprehensive Health Checks**: Simple and detailed endpoints for monitoring +* **CI/CD Ready**: GitHub Actions workflows and automated testing +* **Development Image**: Includes Xdebug 3.4.6 and Symfony CLI 5.15.1 -```bash -# Core tools -docker --version # >= 20.10 -docker buildx version # >= 0.10 -make --version -git --version - -# Optional but recommended -trivy --version # Security scanning -hadolint --version # Dockerfile linting -``` +--- + +## 🚀 Quick Start -### Environment Setup +### 30-Second Demo + +Pull and run the demo page: ```bash -# Clone repository -git clone https://github.com/kariricode/php-api-stack.git -cd php-api-stack +docker pull kariricode/php-api-stack:latest +docker run -d -p 8080:80 --name my-app kariricode/php-api-stack:latest -# Verify structure -ls -la -# Expected: Dockerfile, Makefile, VERSION, .env, etc. +# Open: http://localhost:8080 ``` -## 🚀 Docker Hub Setup +You'll see a comprehensive status page showing PHP version, loaded extensions, OPcache statistics, Redis connectivity, and system resources. -### 1. Login to Docker Hub +### With Your Application ```bash -# Interactive login -docker login +docker run -d \ + -p 8080:80 \ + --name my-app \ + -e APP_ENV=production \ + -e PHP_MEMORY_LIMIT=512M \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + -v $(pwd)/app:/var/www/html:ro \ + kariricode/php-api-stack:latest +``` -# Or with credentials -docker login -u kariricode -p YOUR_TOKEN +**Important**: Your application's public entry point must be at `/var/www/html/public/index.php` (Symfony/Laravel standard). -# Verify login -docker info | grep Username -# Expected: Username: kariricode -``` +--- -### 2. Create Access Token (Recommended) +## 🐳 Docker Compose -Instead of using password: +### Basic Setup -1. Go to https://hub.docker.com/settings/security -2. Click "New Access Token" -3. Name: `php-api-stack-ci` -4. Permissions: `Read, Write, Delete` -5. Copy token (you won't see it again!) +Create `docker-compose.yml`: -```bash -# Login with token -echo "YOUR_TOKEN" | docker login -u kariricode --password-stdin +```yaml +version: '3.9' + +services: + app: + image: kariricode/php-api-stack:latest + container_name: my-app + ports: + - "8080:80" + environment: + APP_ENV: production + PHP_MEMORY_LIMIT: 512M + PHP_FPM_PM_MAX_CHILDREN: 100 + REDIS_HOST: 127.0.0.1 # Using internal Redis + volumes: + - ./app:/var/www/html:ro + - ./logs:/var/log + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 3s + retries: 3 + restart: unless-stopped +``` + +### With External Database -# Save to environment (optional) -export DOCKER_HUB_TOKEN="YOUR_TOKEN" +```yaml +version: '3.9' + +services: + app: + image: kariricode/php-api-stack:latest + container_name: my-app + ports: + - "8080:80" + environment: + APP_ENV: production + DATABASE_URL: mysql://user:pass@db:3306/myapp + REDIS_HOST: 127.0.0.1 # Internal Redis + volumes: + - ./app:/var/www/html:ro + depends_on: + db: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + restart: unless-stopped + networks: + - app-network + + db: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: secret + MYSQL_DATABASE: myapp + MYSQL_USER: user + MYSQL_PASSWORD: pass + volumes: + - db-data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - app-network + +volumes: + db-data: + +networks: + app-network: + driver: bridge +``` + +**Note about Redis**: This image includes an internal Redis instance on `127.0.0.1:6379`. For external Redis, create a separate service and set `REDIS_HOST` to the service name. + +Start services: + +```bash +docker compose up -d +docker compose logs -f +docker compose ps ``` -### 3. Repository Configuration - -Ensure Docker Hub repository exists: -- **Name**: `php-api-stack` -- **Visibility**: Public -- **Description**: "Production-ready PHP 8.4 + Nginx + Redis + Supervisor stack" -- **README**: Synced from GitHub (see [Documentation Sync](#documentation-sync)) +--- -## 📦 Versioning Strategy +## 🏷️ Available Tags -The project follows [Semantic Versioning](https://semver.org/): +| Tag | Description | Size | Use Case | +|-----|-------------|------|----------| +| `latest` | Latest stable release | ~225MB | General use | +| `1.5.0` | Specific version | ~225MB | Production (pinned) | +| `1.5` | Latest patch in v1.5.x | ~225MB | Auto-patch updates | +| `1` | Latest minor in v1.x.x | ~225MB | Auto-minor updates | +| `dev` | Development build | ~244MB | Local development with Xdebug | +| `test` | With comprehensive health checks | ~216MB | Testing/monitoring | -``` -MAJOR.MINOR.PATCH +### Tagging Strategy -Examples: -1.2.1 → Patch release (bug fixes) -1.3.0 → Minor release (new features, backward compatible) -2.0.0 → Major release (breaking changes) -``` +- **Production**: Always pin to specific versions (`1.5.0`) for reproducibility +- **Development**: Use `dev` tag for debugging capabilities +- **Staging**: Use minor version tags (`1.5`) for automatic patch updates +- **Never** use `latest` in production -### Version Management +Pull a specific tag: -#### Check Current Version ```bash -make version -# Output: Current version: 1.2.1 +docker pull kariricode/php-api-stack:1.5.0 +docker pull kariricode/php-api-stack:dev ``` -#### Bump Version +--- -```bash -# Patch (1.2.1 → 1.2.2) -make bump-patch +## ⚙️ Configuration -# Minor (1.2.1 → 1.3.0) -make bump-minor +All configuration is done via environment variables. The image supports **100+ configuration options** covering PHP, PHP-FPM, Nginx, Redis, and application settings. -# Major (1.2.1 → 2.0.0) -make bump-major -``` +### Essential Variables -#### Manual Version Update ```bash -# Edit VERSION file -echo "1.3.0" > VERSION - -# Commit -git add VERSION -git commit -m "chore: bump version to 1.3.0" -git tag v1.3.0 -git push origin main --tags -``` +# Application +APP_ENV=production # production|development|test +APP_DEBUG=false # Enable debug mode +APP_NAME=my-application # Application name -## 🏗️ Build Process +# PHP Runtime +PHP_MEMORY_LIMIT=512M # Memory per request +PHP_MAX_EXECUTION_TIME=60 # Script timeout +PHP_UPLOAD_MAX_FILESIZE=100M # Max upload size +PHP_POST_MAX_SIZE=100M # Max POST size +PHP_DATE_TIMEZONE=UTC # Timezone -### Local Build +# PHP-FPM +PHP_FPM_PM=static # static|dynamic|ondemand +PHP_FPM_PM_MAX_CHILDREN=100 # Worker processes -#### Simple Build -```bash -# Production build -make build +# OPcache +PHP_OPCACHE_ENABLE=1 # Enable OPcache +PHP_OPCACHE_MEMORY=256 # OPcache memory (MB) +PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 # 0 for prod, 1 for dev +PHP_OPCACHE_JIT=tracing # JIT mode -# Expected output: -# Building Docker image... -# Image: kariricode/php-api-stack:1.2.1 -# ✓ Build complete! -``` - -#### Build with Tests -```bash -# Build + quick tests -make build-test +# Nginx +NGINX_WORKER_PROCESSES=auto # auto = CPU cores +NGINX_CLIENT_MAX_BODY_SIZE=100M # Max request size -# Build test image with comprehensive health -make build-test-image +# Redis (Internal) +REDIS_HOST=127.0.0.1 # Internal Redis (standalone) +REDIS_PASSWORD= # Optional password ``` -#### Build without Cache -```bash -# Force rebuild -make build-no-cache +**Complete reference**: See [.env.example](https://github.com/kariricode/php-api-stack/blob/main/.env.example) in the repository. -# Or manual -./build-from-env.sh --no-cache -``` +### Configuration Examples -### Build Validation +#### High Performance Setup ```bash -# 1. Check image exists -docker images kariricode/php-api-stack - -# 2. Verify tags -docker images kariricode/php-api-stack --format "table {{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" - -# 3. Test image -make test-quick - -# 4. Scan for vulnerabilities -make scan +docker run -d \ + -p 80:80 \ + -e PHP_MEMORY_LIMIT=512M \ + -e PHP_FPM_PM=static \ + -e PHP_FPM_PM_MAX_CHILDREN=200 \ + -e PHP_OPCACHE_MEMORY=512 \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + -e PHP_OPCACHE_JIT=tracing \ + -e NGINX_WORKER_CONNECTIONS=4096 \ + -v $(pwd)/app:/var/www/html:ro \ + kariricode/php-api-stack:latest ``` -## 🏷️ Tagging Strategy - -### Standard Tags +#### Memory-Constrained Environment -For version `1.2.1`, the following tags are created: +```bash +docker run -d \ + -p 80:80 \ + --memory="512m" \ + -e PHP_MEMORY_LIMIT=256M \ + -e PHP_FPM_PM=dynamic \ + -e PHP_FPM_PM_MAX_CHILDREN=25 \ + -e PHP_OPCACHE_MEMORY=128 \ + -v $(pwd)/app:/var/www/html:ro \ + kariricode/php-api-stack:latest +``` -| Tag | Description | Auto-updates | -|-----|-------------|--------------| -| `1.2.1` | Specific version | Never | -| `1.2` | Minor version | Patch only | -| `1` | Major version | Minor + Patch | -| `latest` | Latest stable | All releases | -| `stable` | Production | Stable releases | +--- -### Environment-Specific Tags +## 📊 Stack Components -| Tag | Description | When | -|-----|-------------|------| -| `dev` | Development | `APP_ENV=development` | -| `staging` | Staging | `APP_ENV=staging` | -| `stable` | Production | `APP_ENV=production` | -| `test` | Testing | `--test` flag | +| Component | Version | Purpose | +|-----------|---------|---------| +| **PHP-FPM** | 8.4.13 | PHP processing with optimized pool | +| **Nginx** | 1.27.3 | High-performance web server | +| **Redis** | 7.2.11 | Cache and session management | +| **Alpine Linux** | 3.21 | Minimal base image | +| **Composer** | 2.8.12 | PHP dependency manager | +| **Symfony CLI** | 5.15.1 | Symfony tools (dev only) | +| **Xdebug** | 3.4.6 | PHP debugger (dev only) | -### Manual Tagging +### PHP Extensions -```bash -# Tag existing image -docker tag kariricode/php-api-stack:1.2.1 kariricode/php-api-stack:latest -docker tag kariricode/php-api-stack:1.2.1 kariricode/php-api-stack:1.2 -docker tag kariricode/php-api-stack:1.2.1 kariricode/php-api-stack:1 -docker tag kariricode/php-api-stack:1.2.1 kariricode/php-api-stack:stable +**Core Extensions** (Pre-installed): +``` +pdo, pdo_mysql, opcache, intl, zip, bcmath, gd, mbstring, xml, sockets ``` -### Automated Tagging +**PECL Extensions** (Pre-installed): +``` +redis (6.1.0), apcu (5.1.24), uuid (1.2.1), imagick (3.7.0), amqp (2.1.2) +``` -The `build-from-env.sh` script handles tagging automatically: +**Built-in Extensions** (Always available): +``` +json, curl, fileinfo, ctype, iconv, session, tokenizer, filter, hash, openssl +``` -```bash -# Production build (creates all tags) -./build-from-env.sh --version=1.2.1 +--- -# Test build (creates test tags) -./build-from-env.sh --test --version=1.2.1 -``` +## 🏥 Health Checks -## 📤 Publishing Process +### Simple Health Check -### Quick Publish +Lightweight endpoint for load balancers and orchestrators: -#### Using Makefile (Recommended) ```bash -# Build and push -make build -make push - -# Or combined -make release # lint + build + test + scan + push +curl http://localhost:8080/health +# Response: healthy ``` -#### Using Script -```bash -# Build with push -./build-from-env.sh --push +HTTP 200 if healthy, 503 if unhealthy. -# Or separate -./build-from-env.sh -docker push kariricode/php-api-stack:1.2.1 -docker push kariricode/php-api-stack:latest -``` +### Comprehensive Health Check -### Complete Release Workflow +Detailed diagnostics with component-level checks: ```bash -# 1. Update version -make bump-patch # or bump-minor, bump-major - -# 2. Quality gates -make lint # Dockerfile validation -make build # Build image -make test # Run tests -make scan # Security scan - -# 3. Push to registry -make push - -# 4. Tag in git -git tag v$(cat VERSION) -git push origin main --tags - -# 5. Create GitHub release (optional) -gh release create v$(cat VERSION) \ - --title "Release $(cat VERSION)" \ - --notes "See CHANGELOG.md" +curl http://localhost:8080/health.php | jq ``` -### Push Individual Tags +Returns JSON with: +- Overall status (healthy/degraded/unhealthy) +- PHP runtime details (version, memory, extensions) +- OPcache statistics (hit rate, memory, JIT status) +- Redis connectivity (latency, memory, persistence) +- System resources (disk, CPU, memory) +- Application directories (permissions, accessibility) -```bash -VERSION=$(cat VERSION) +**Docker Healthcheck**: -# Push specific version -docker push kariricode/php-api-stack:$VERSION +```yaml +healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 10s +``` -# Push semantic versions -docker push kariricode/php-api-stack:${VERSION%.*} # 1.2 -docker push kariricode/php-api-stack:${VERSION%%.*} # 1 +**Kubernetes Probes**: -# Push latest -docker push kariricode/php-api-stack:latest +```yaml +livenessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 30 + periodSeconds: 10 -# Push environment-specific -docker push kariricode/php-api-stack:stable +readinessProbe: + httpGet: + path: /health.php + port: 80 + initialDelaySeconds: 10 + periodSeconds: 5 ``` -## 🌍 Multi-Platform Builds +--- -Build for multiple architectures (amd64, arm64): +## 🎭 Framework Integration -### Setup Buildx +### Symfony ```bash -# Create builder -docker buildx create --name php-api-stack-builder --use - -# Verify -docker buildx ls -# Expected: php-api-stack-builder running +docker run -d \ + -p 8080:80 \ + -e APP_ENV=prod \ + -e APP_SECRET=$(openssl rand -hex 16) \ + -e DATABASE_URL=mysql://user:pass@db:3306/symfony \ + -v $(pwd):/var/www/html:ro \ + kariricode/php-api-stack:latest ``` -### Multi-Platform Build +### Laravel -#### Using Script ```bash -# Build and push for multiple platforms -./build-from-env.sh --multi-platform --push - -# Platforms: linux/amd64, linux/arm64 +docker run -d \ + -p 8080:80 \ + -e APP_ENV=production \ + -e APP_KEY=base64:your-key-here \ + -e DB_CONNECTION=mysql \ + -e DB_HOST=db \ + -e DB_DATABASE=laravel \ + -v $(pwd):/var/www/html:ro \ + kariricode/php-api-stack:latest ``` -#### Manual Build -```bash -docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --tag kariricode/php-api-stack:1.2.1 \ - --tag kariricode/php-api-stack:latest \ - --push \ - . -``` - -### Verify Multi-Platform +### Custom PHP App ```bash -# Check manifest -docker manifest inspect kariricode/php-api-stack:latest - -# Expected output shows: -# - linux/amd64 -# - linux/arm64 +docker run -d \ + -p 8080:80 \ + -e APP_ENV=production \ + -v $(pwd)/my-app:/var/www/html:ro \ + kariricode/php-api-stack:latest ``` -## 🤖 Automated Publishing +**Directory Structure**: Your app must have `public/index.php` as the entry point. -### GitHub Actions +--- -Create `.github/workflows/publish.yml`: +## 🔐 Security Features -```yaml -name: Publish to Docker Hub - -on: - push: - tags: - - 'v*' - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_TOKEN }} - - - name: Extract version - id: version - run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT - - - name: Build and test - run: | - make build - make test - make scan - - - name: Build and push multi-platform - run: | - ./build-from-env.sh \ - --version=${{ steps.version.outputs.VERSION }} \ - --multi-platform \ - --push - - - name: Update Docker Hub description - uses: peter-evans/dockerhub-description@v3 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_TOKEN }} - repository: kariricode/php-api-stack - readme-filepath: ./IMAGE_USAGE_GUIDE.md -``` +### Built-in Security -### Required GitHub Secrets +- ✅ **Non-root Services**: All services run as unprivileged users (`www-data`, `nginx`, `redis`) +- ✅ **Security Headers**: X-Frame-Options, X-Content-Type-Options, CSP, HSTS +- ✅ **Rate Limiting**: 10 req/s (general), 100 req/s (API endpoints) +- ✅ **Disabled Functions**: Dangerous PHP functions blocked +- ✅ **Open Basedir**: Restricted to `/var/www/html` and `/tmp` +- ✅ **Hidden Tokens**: Server version and PHP version hidden -Add these secrets to your GitHub repository: -- `DOCKER_HUB_USERNAME`: Your Docker Hub username -- `DOCKER_HUB_TOKEN`: Your Docker Hub access token +### Best Practices -Settings → Secrets and variables → Actions → New repository secret +- Always use specific version tags in production +- Mount application code as read-only (`:ro`) +- Store secrets in environment variables +- Enable HTTPS via reverse proxy (Nginx, Traefik, etc.) +- Regularly update to latest patch versions +- Use vulnerability scanning (Trivy included in CI/CD) -### Trigger Automated Publish +--- -```bash -# 1. Bump version -make bump-patch +## 🧪 Testing & Development -# 2. Commit and tag -git add VERSION -git commit -m "chore: bump version to $(cat VERSION)" -git tag v$(cat VERSION) +### Development Image -# 3. Push (triggers GitHub Action) -git push origin main --tags +The `dev` tag includes additional tools for local development: -# 4. Monitor workflow -# Visit: https://github.com/kariricode/php-api-stack/actions +```bash +docker run -d \ + -p 8001:80 \ + -p 9003:9003 \ + -e APP_ENV=development \ + -e APP_DEBUG=true \ + -e PHP_DISPLAY_ERRORS=On \ + -e XDEBUG_ENABLE=1 \ + -v $(pwd)/app:/var/www/html \ + kariricode/php-api-stack:dev ``` -## 🎯 Best Practices +**Includes**: +- Xdebug 3.4.6 (configurable) +- Symfony CLI 5.15.1 +- Extended error reporting +- File change detection (OPcache revalidation) -### Pre-Publish Checklist +### Running Tests ```bash -# ✅ 1. Code quality -make lint +# Quick version check +docker run --rm kariricode/php-api-stack:latest php -v -# ✅ 2. Build succeeds -make build-no-cache +# Full test suite +docker run --rm kariricode/php-api-stack:test /usr/local/bin/health-check.sh +``` -# ✅ 3. All tests pass -make test +--- -# ✅ 4. No vulnerabilities -make scan +## 🛠 Makefile Commands -# ✅ 5. Health check works -make run-test -make test-health -make stop-test +The repository includes **three specialized Makefiles** with 50+ commands: -# ✅ 6. Documentation updated -# Review: README.md, TESTING.md, DOCKER_HUB.md, IMAGE_USAGE_GUIDE.md +### Main Makefile (Build & Runtime) -# ✅ 7. Changelog updated -# Add entry to CHANGELOG.md +```bash +make build # Build production image +make build-dev # Build dev image +make run # Run production container +make run-dev # Run dev container +make test # Run test suite +make lint # Lint Dockerfile +make scan # Security scan +``` -# ✅ 8. Version bumped -cat VERSION # Should be new version +### Makefile.dockerhub (Publishing) -# ✅ 9. Git tagged -git tag | tail -1 # Should match VERSION +```bash +make hub-help # Show Docker Hub commands +make version # Show current version +make bump-patch # Bump patch version +make tag-production # Tag production image +make push-production # Push to Docker Hub +make release-production # Full release pipeline ``` -### Image Size Optimization +### Makefile.compose (Orchestration) ```bash -# Check current size -docker images kariricode/php-api-stack:latest - -# Optimize .dockerignore -cat > .dockerignore << EOF -.git -.github -.gitignore -tests/ -docs/ -*.md -!README.md -.env -.env.* -!.env.example -EOF - -# Rebuild -make build-no-cache - -# Compare -docker images kariricode/php-api-stack +make compose-help # Show Compose commands +make compose-up # Start services +make compose-logs # View logs +make compose-shell # Access container ``` -### Security Best Practices +**Get the source**: [https://github.com/kariricode/php-api-stack](https://github.com/kariricode/php-api-stack) -1. **Use Access Tokens**: Never use passwords directly -2. **Scan Before Push**: Always run `make scan` -3. **Pin Base Images**: Use specific versions (already done) -4. **Regular Updates**: Update base images monthly -5. **Minimal Privileges**: Use non-root user when possible - -### Documentation Sync +--- -#### Docker Hub Description +## 🐛 Troubleshooting -Option 1: Automated (GitHub Actions) -```yaml -# Already in publish.yml workflow -- name: Update Docker Hub description - uses: peter-evans/dockerhub-description@v3 - with: - readme-filepath: ./IMAGE_USAGE_GUIDE.md -``` +### Container Won't Start -Option 2: Manual ```bash -# Install tool -npm install -g dockerhub-description - -# Update description -dockerhub-description \ - kariricode/php-api-stack \ - ./IMAGE_USAGE_GUIDE.md \ - --username kariricode \ - --password "$DOCKER_HUB_TOKEN" -``` +# Check logs +docker logs -## 🐛 Troubleshooting +# Common causes: +# - Port already in use → Change port: -p 8081:80 +# - Volume permissions → Fix: chmod -R 755 app/ +# - Memory limits → Increase: --memory="1g" +``` -### Build Issues +### 502 Bad Gateway -#### Error: Cannot connect to Docker daemon ```bash -# Check Docker is running -docker ps +# Check PHP-FPM +docker exec ps aux | grep php-fpm + +# Check logs +docker exec tail -f /var/log/php/fpm-error.log +docker exec tail -f /var/log/nginx/error.log -# Restart Docker -sudo systemctl restart docker # Linux -# or restart Docker Desktop +# Restart PHP-FPM +docker exec kill -USR2 $(cat /var/run/php/php-fpm.pid) ``` -#### Error: Build fails with "no space left on device" +### Redis Connection Issues + ```bash -# Clean up -docker system prune -af --volumes +# Check Redis (internal) +docker exec redis-cli -h 127.0.0.1 ping +# Should return: PONG + +# Check REDIS_HOST variable +docker exec env | grep REDIS_HOST +# Should be: 127.0.0.1 (standalone) or redis (compose) -# Check space -df -h +# Test with password (if configured) +docker exec redis-cli -h 127.0.0.1 -a "password" ping ``` -### Push Issues +### Slow Performance -#### Error: Authentication required ```bash -# Re-login -docker logout -docker login +# Check OPcache hit rate (should be >95%) +docker exec php -r " +\$stats = opcache_get_status()['opcache_statistics']; +echo 'Hit Rate: ' . \$stats['opcache_hit_rate'] . '%' . PHP_EOL; +" -# Verify -docker info | grep Username +# Check resource usage +docker stats + +# Solutions: +# - Increase OPcache memory: -e PHP_OPCACHE_MEMORY=512 +# - Increase FPM workers: -e PHP_FPM_PM_MAX_CHILDREN=100 +# - Add container resources: --memory="2g" --cpus="4" ``` -#### Error: Denied: requested access to the resource is denied -```bash -# Check repository name -docker images | grep php-api-stack +**Complete troubleshooting guide**: [IMAGE_USAGE_GUIDE.md](https://github.com/kariricode/php-api-stack/blob/main/IMAGE_USAGE_GUIDE.md#troubleshooting) -# Should be: kariricode/php-api-stack -# Not: php-api-stack +--- -# Retag if needed -docker tag php-api-stack:latest kariricode/php-api-stack:latest -``` +## 📚 Documentation -#### Error: Image push failed -```bash -# Check network -ping registry-1.docker.io +| Document | Description | +|----------|-------------| +| **[README.md](https://github.com/kariricode/php-api-stack)** | Project overview and quick start | +| **[IMAGE_USAGE_GUIDE.md](https://github.com/kariricode/php-api-stack/blob/main/IMAGE_USAGE_GUIDE.md)** | Complete usage guide for end users | +| **[DOCKER_COMPOSE_GUIDE.md](https://github.com/kariricode/php-api-stack/blob/main/DOCKER_COMPOSE_GUIDE.md)** | Docker Compose orchestration | +| **[TESTING.md](https://github.com/kariricode/php-api-stack/blob/main/TESTING.md)** | Testing procedures for maintainers | -# Check size (max 10GB per layer) -docker images kariricode/php-api-stack:latest +--- -# Try again with retry -for i in {1..3}; do - docker push kariricode/php-api-stack:latest && break - sleep 5 -done -``` +## 🔄 Version History -### Multi-Platform Issues +### v1.5.0 (2025-10-24) - Latest -#### Error: Multiple platforms feature is currently not supported -```bash -# Enable experimental features -export DOCKER_CLI_EXPERIMENTAL=enabled +**Docker Hub Integration**: +- Fixed `hub-check` command display bug +- Simplified dev tagging: only `dev` tag (removed `dev-X.Y.Z`) +- Fixed version bump commands (`bump-patch`, `bump-minor`, `bump-major`) +- Improved `hub-check` output with checkmarks (✓/✗) +- Added comprehensive Docker Hub utilities -# Or in ~/.docker/config.json: -{ - "experimental": "enabled" -} -``` +**Breaking Changes**: +- Dev versioned tags (`dev-X.Y.Z`) are no longer created -#### Error: Buildx builder not found -```bash -# Create builder -docker buildx create --name php-api-stack-builder --use +### v1.4.5 (2025-10-24) -# Bootstrap -docker buildx inspect --bootstrap -``` +**Build System**: +- Fixed PHP extension quoting in Makefile +- Secure `.env` parsing to prevent command execution +- Proper escaping for build args -## 📊 Monitoring Published Images +**Redis Integration**: +- Automatic `REDIS_HOST` override for standalone containers +- Smart DNS fallback in health checks +- Documentation improvements -### Docker Hub Stats +**Dockerfile**: +- Fixed OPcache validation (Zend extension check) +- Added `util-linux` for UUID extension +- Fixed shellcheck errors -```bash -# View on Docker Hub -open https://hub.docker.com/r/kariricode/php-api-stack +### v1.4.3 (2025-10-20) -# Check pulls (requires Docker Hub account) -curl -s "https://hub.docker.com/v2/repositories/kariricode/php-api-stack/" | jq '.pull_count' -``` +- Refactored Makefile with 50+ organized commands +- Enhanced development workflow +- Improved health check monitoring +- Updated documentation -### Image Verification +### v1.2.1 (2025-10-18) -```bash -# Pull and verify -docker pull kariricode/php-api-stack:latest +- Added comprehensive Makefile +- Docker Compose integration +- Multiple service profiles +- Enhanced health checks -# Check digest -docker inspect kariricode/php-api-stack:latest | jq '.[0].RepoDigests' +### v1.2.0 (2025-10-15) -# Verify signature (if signed) -docker trust inspect kariricode/php-api-stack:latest -``` +- PHP 8.4, Nginx 1.27.3, Redis 7.2 +- OPcache + JIT optimization +- Socket-based PHP-FPM +- Environment variable configuration -## 📅 Release Schedule +**Full changelog**: [GitHub Releases](https://github.com/kariricode/php-api-stack/releases) -### Recommended Schedule +--- -- **Patch releases**: As needed (bug fixes) -- **Minor releases**: Monthly (new features) -- **Major releases**: Quarterly (breaking changes) -- **Security patches**: Immediately (critical vulnerabilities) +## 🧭 Related Projects -### Release Process Timeline +This image is part of the **KaririCode** ecosystem: -``` -Week 1: Development - - Feature development - - Bug fixes - - Testing - -Week 2: Pre-release - - Code freeze - - Final testing - - Documentation update - -Week 3: Release - - Version bump - - Build and test - - Publish to Docker Hub - - GitHub release - -Week 4: Monitoring - - Monitor issues - - Quick patches if needed - - Plan next release -``` +### KaririCode Framework -## 📞 Support +Modern PHP framework with advanced features: +- **Repository**: [KaririCode-Framework](https://github.com/KaririCode-Framework) +- **30+ Components**: DI, Router, Auth, Cache, EventDispatcher, etc. +- **ARFA Architecture**: Adaptive Reactive Flow Architecture -### For Publishing Issues +### KaririCode DevKit -- **GitHub Issues**: [Report issues](https://github.com/kariricode/php-api-stack/issues) -- **Docker Hub**: [Repository page](https://hub.docker.com/r/kariricode/php-api-stack) -- **Discussions**: [GitHub Discussions](https://github.com/kariricode/php-api-stack/discussions) +Development environment automation: +- **Repository**: [kariricode/devkit](https://github.com/kariricode/devkit) +- **Features**: Docker, Compose, quality tools, CI/CD +- **Integration**: Uses this Docker image -### Quick Reference +--- -```bash -# Complete release -make version # Check version -make bump-patch # Bump version -make release # Full pipeline -git tag v$(cat VERSION) -git push origin main --tags - -# Emergency hotfix -make build-no-cache -make test -make push -``` +## 🤝 Contributing + +Contributions are welcome! Please: + +1. Fork the repository: [https://github.com/kariricode/php-api-stack](https://github.com/kariricode/php-api-stack) +2. Create a feature branch +3. Make your changes +4. Submit a Pull Request + +**Standards**: +- Follow PSR-12 for PHP +- Use Conventional Commits +- Add tests for new features +- Update documentation + +--- + +## 📄 License + +This project is licensed under the MIT License. +See [LICENSE](https://github.com/kariricode/php-api-stack/blob/main/LICENSE) in the repository. + +--- + +## 🙌 Support + +- **Issues**: [GitHub Issues](https://github.com/kariricode/php-api-stack/issues) +- **Discussions**: [GitHub Discussions](https://github.com/kariricode/php-api-stack/discussions) +- **Docker Hub**: [kariricode/php-api-stack](https://hub.docker.com/r/kariricode/php-api-stack) --- -**Next**: [IMAGE_USAGE_GUIDE.md](IMAGE_USAGE_GUIDE.md) - Learn how to use the published image \ No newline at end of file +
+ +**Made with 💚 by [KaririCode](https://kariricode.org)** + +[![KaririCode](https://img.shields.io/badge/KaririCode-Framework-green)](https://kariricode.org) +[![GitHub](https://img.shields.io/badge/GitHub-KaririCode-black)](https://github.com/KaririCode-Framework) + +
\ No newline at end of file diff --git a/DOCKER_HUB_OVERVIEW.md b/DOCKER_HUB_OVERVIEW.md deleted file mode 100644 index a59d1c5..0000000 --- a/DOCKER_HUB_OVERVIEW.md +++ /dev/null @@ -1,234 +0,0 @@ -# KaririCode/php-api-stack - -Production‑ready **PHP API Stack** image built on Alpine Linux with **Nginx + PHP‑FPM + Redis**. Secured, fast, and configurable via environment variables — ideal for modern APIs and web apps. - -

- Docker Pulls - Source - KaririCode - Version - PHP - Alpine -

- ---- - -## 🔗 Official Links - -* **Repository (source)**: [https://github.com/kariricode/php-api-stack](https://github.com/kariricode/php-api-stack) -* **Official site**: [https://kariricode.org/](https://kariricode.org/) -* **KaririCode Framework (org)**: [https://github.com/KaririCode-Framework](https://github.com/KaririCode-Framework) - ---- - -## ✨ Highlights - -* **Complete Stack**: Nginx (1.27.3) + PHP‑FPM (8.4) + Redis (7.2) -* **Performance**: OPcache + JIT, tuned Nginx/PHP‑FPM, socket‑based FPM -* **Security‑first**: non‑root services, hardened defaults, CSP/HSTS headers -* **Easy Config**: 100% via environment variables or `.env` -* **Healthcheck**: `/health.php` (simple or comprehensive build) -* **CI/CD‑ready**: multi‑platform builds; Makefile targets; scan/lint helpers - -> Image tag `1.2.0` is the latest stable at publication time. Use `:latest` for automatic updates or pin to exact versions for reproducibility. - ---- - -## 🚀 Quick Start - -Pull and run the demo page: - -```bash -docker run -d \ - -p 8080:80 \ - --name php-api-stack-demo \ - kariricode/php-api-stack:latest - -# open: http://localhost:8080 -``` - -Run with your application mounted: - -```bash -docker run -d \ - -p 8080:80 \ - --name my-php-app \ - -e APP_ENV=production \ - -e PHP_MEMORY_LIMIT=512M \ - -v $(pwd)/app:/var/www/html \ - kariricode/php-api-stack:latest -``` - -> **Note**: Your app’s public entry point must be at `/var/www/html/public/index.php`. - ---- - -## 🐳 Docker Compose - -Minimal `docker-compose.yml`: - -```yaml -version: '3.9' -services: - app: - image: kariricode/php-api-stack:latest - container_name: php-api-stack - ports: - - "8080:80" - env_file: .env - volumes: - - ./app:/var/www/html - - ./logs:/var/log - depends_on: - - redis - redis: - image: redis:7.2-alpine - command: ["redis-server", "/etc/redis/redis.conf", "--appendonly", "yes"] - volumes: - - redis_data:/data -volumes: - redis_data: -``` - -Start services: - -```bash -docker compose up -d -``` - -**Production tips** - -* Mount app read‑only: `./app:/var/www/html:ro` -* Add healthcheck: - -```yaml -healthcheck: - test: ["CMD-SHELL", "curl -fsS http://localhost/health.php || exit 1"] - interval: 30s - timeout: 3s - retries: 3 - start_period: 10s -``` - ---- - -## ⚙️ Configuration (most used) - -Set via `-e` or `.env`: - -| Variable | Default | Description | -| ---------------------------- | ----------------------- | ------------------------------------------------------- | -| `APP_ENV` | `production` | `production`, `staging`, `development` | -| `APP_DEBUG` | `false` | Verbose errors (use only in dev) | -| `PHP_MEMORY_LIMIT` | `256M` | Per‑request memory limit | -| `PHP_UPLOAD_MAX_FILESIZE` | `100M` | Upload size | -| `PHP_DATE_TIMEZONE` | `UTC/America/Sao_Paulo` | Server timezone | -| `PHP_FPM_PM` | `dynamic`* | FPM process manager (*auto‑forced to `static` in prod*) | -| `PHP_FPM_PM_MAX_CHILDREN` | `60` | FPM workers (increase in prod) | -| `NGINX_CLIENT_MAX_BODY_SIZE` | `100M` | Request body limit | -| `PHP_SESSION_SAVE_HANDLER` | `redis` | `redis` or `files` | -| `PHP_SESSION_SAVE_PATH` | `tcp://redis:6379` | Session DSN | -| `PHP_OPCACHE_ENABLE` | `1` | Enable OPcache | -| `PHP_OPCACHE_JIT` | `tracing` | JIT mode | - -> See the **full matrix** in the GitHub repo’s `.env.example` and docs. - ---- - -## 🏷️ Tags - -* `1.2.0` – latest stable -* `latest` – tracks the most recent stable release -* `X.Y` (e.g., `1.2`) – latest patch within the minor series -* `X` (e.g., `1`) – latest minor within the major series -* `test` – image variant with extended health checks for testing - -Pull a specific tag: - -```bash -docker pull kariricode/php-api-stack:latest -``` - ---- - -## 🔍 Health Check - -* **Simple**: lightweight JSON at `/health.php` -* **Comprehensive**: build with `HEALTH_CHECK_TYPE=comprehensive` for deeper PHP/Redis checks. - -Runtime verification: - -```bash -curl -fsS http://localhost:8080/health.php | jq -``` - ---- - -## 🧪 Troubleshooting - -**Container fails to start** - -```bash -docker logs -``` - -**502 Bad Gateway** (FPM not responding) - -```bash -docker exec ps aux | grep php-fpm - -docker exec tail -f /var/log/php/fpm-error.log -``` - -**Slow performance** - -```bash -docker stats - -docker exec php -r "print_r(opcache_get_status()['opcache_statistics']['opcache_hit_rate']);" -``` - ---- - -## 🔐 Security Notes - -* Runs services without root privileges; hardened defaults -* Enable/add headers via `SECURITY_HEADERS`, `SECURITY_CSP`, `SECURITY_HSTS_MAX_AGE` -* Prefer immutable deployments in prod (`PHP_OPCACHE_VALIDATE_TIMESTAMPS=0`) - ---- - -## 🧭 Roadmap & Contributing - -Feature requests and PRs are welcome in the source repository: - -* GitHub: [https://github.com/kariricode/php-api-stack](https://github.com/kariricode/php-api-stack) - -For broader ecosystem projects, visit: - -* KaririCode Framework: [https://github.com/KaririCode-Framework](https://github.com/KaririCode-Framework) - ---- - -## 📝 Changelog (excerpt) - -**1.2.0** - -* PHP 8.4, Nginx 1.27.3, Redis 7.2 -* Socket‑based PHP‑FPM; OPcache + JIT optimized -* `/health.php` endpoint; improved entrypoint & config processor -* Extensive env‑var configuration for Nginx/PHP/Redis - -> Full release notes are available in the GitHub repository. - ---- - -## 📄 License - -See `LICENSE` in the source repository. - ---- - -## 🙌 Credits - -Made with 💚 by **KaririCode** — [https://kariricode.org/](https://kariricode.org/) diff --git a/Dockerfile b/Dockerfile index 554d0c2..da8a367 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,16 @@ -# Multi-stage build for kariricode/php-api-stack — FINAL (lint‑clean, syntax‑fixed) +# ============================================================================ +# Multi-stage build for kariricode/php-api-stack # Production-ready PHP + Nginx + Redis (+ optional Dev tooling) -# Notes: -# - Avoid inline comments after a trailing backslash (\). They break line continuation. -# - Hadolint DL3018 is selectively ignored where pinning is impractical; see comments. +# Architecture: Base → Production | Dev +# ============================================================================ +# Author: KaririCode +# License: MIT +# Repository: https://github.com/kariricode/php-api-stack +# ============================================================================ -# ------------------------------------------------------------ -# Versions (overridable via build args) -# ------------------------------------------------------------ +# ---------------------------------------------------------------------------- +# Build Arguments - Component Versions +# ---------------------------------------------------------------------------- ARG PHP_VERSION=8.4 ARG NGINX_VERSION=1.27.3 ARG REDIS_VERSION=7.2 @@ -19,42 +23,62 @@ ARG VERSION=1.2.0 ARG BUILD_DATE ARG VCS_REF -# ------------------------------------------------------------ -# Stage: Redis binaries (from official image) -# ------------------------------------------------------------ +# ---------------------------------------------------------------------------- +# PECL Extension Versions +# Reference: https://pecl.php.net/ +# ---------------------------------------------------------------------------- +ARG PHP_REDIS_VERSION=6.1.0 +ARG PHP_APCU_VERSION=5.1.24 +ARG PHP_UUID_VERSION=1.2.1 +ARG PHP_IMAGICK_VERSION=3.7.0 +ARG PHP_AMQP_VERSION=2.1.2 +ARG XDEBUG_VERSION=3.4.6 + +# ============================================================================ +# Stage: Redis Binaries (from official image) +# ============================================================================ FROM redis:${REDIS_VERSION}-alpine AS redis-build -# ------------------------------------------------------------ -# Stage: Nginx binaries (from official image) -# ------------------------------------------------------------ +# ============================================================================ +# Stage: Nginx Binaries (from official image) +# ============================================================================ FROM nginx:${NGINX_VERSION}-alpine AS nginx-build -# ------------------------------------------------------------ -# Stage: Base (production runtime) -# ------------------------------------------------------------ +# ============================================================================ +# Stage: Base - Common runtime foundation (used by both production and dev) +# ============================================================================ FROM php:${PHP_VERSION}-fpm-alpine${ALPINE_VERSION} AS base SHELL ["/bin/ash", "-o", "pipefail", "-c"] +# ---------------------------------------------------------------------------- # Propagate build args to this stage +# CRITICAL: Must be declared BEFORE any RUN that uses them +# ---------------------------------------------------------------------------- ARG PHP_VERSION -ARG PHP_OPCACHE_VALIDATE_TIMESTAMPS -ARG PHP_OPCACHE_MAX_ACCELERATED_FILES -ARG PHP_OPCACHE_ENABLE -ARG PHP_OPCACHE_MEMORY_CONSUMPTION ARG NGINX_VERSION ARG REDIS_VERSION ARG ALPINE_VERSION ARG COMPOSER_VERSION -ARG SYMFONY_CLI_VERSION ARG VERSION ARG BUILD_DATE ARG VCS_REF -# Feature flags -ARG HEALTH_CHECK_TYPE=simple +# PECL extension versions +ARG PHP_REDIS_VERSION +ARG PHP_APCU_VERSION +ARG PHP_UUID_VERSION +ARG PHP_IMAGICK_VERSION +ARG PHP_AMQP_VERSION + +# Extension lists (defaults can be overridden by --build-arg) +ARG PHP_CORE_EXTENSIONS="pdo pdo_mysql opcache intl zip bcmath gd mbstring xml sockets" +ARG PHP_PECL_EXTENSIONS="redis apcu uuid" +# ---------------------------------------------------------------------------- # Labels for OCI metadata and traceability +# Reference: https://github.com/opencontainers/image-spec/blob/main/annotations.md +# ---------------------------------------------------------------------------- LABEL maintainer="KaririCode " \ org.opencontainers.image.title="PHP API Stack" \ org.opencontainers.image.description="Production-ready PHP + Nginx + Redis + Symfony stack" \ @@ -62,46 +86,69 @@ LABEL maintainer="KaririCode " \ org.opencontainers.image.created="${BUILD_DATE}" \ org.opencontainers.image.revision="${VCS_REF}" \ org.opencontainers.image.source="https://github.com/kariricode/php-api-stack" \ + org.opencontainers.image.licenses="MIT" \ + stack.version="${VERSION}" \ stack.php.version="${PHP_VERSION}" \ stack.nginx.version="${NGINX_VERSION}" \ stack.redis.version="${REDIS_VERSION}" \ - stack.version="${VERSION}" + stack.alpine.version="${ALPINE_VERSION}" \ + stack.composer.version="${COMPOSER_VERSION}" \ + stack.php.redis.version="${PHP_REDIS_VERSION}" \ + stack.php.apcu.version="${PHP_APCU_VERSION}" \ + stack.php.uuid.version="${PHP_UUID_VERSION}" +# ---------------------------------------------------------------------------- # Environment defaults for Composer, PHP, and stack +# ---------------------------------------------------------------------------- ENV COMPOSER_ALLOW_SUPERUSER=1 \ COMPOSER_HOME=/composer \ - PATH="/composer/vendor/bin:/symfony/bin:/usr/local/bin:/usr/sbin:/sbin:$PATH" \ - PHP_OPCACHE_VALIDATE_TIMESTAMPS=${PHP_OPCACHE_VALIDATE_TIMESTAMPS} \ - PHP_OPCACHE_MAX_ACCELERATED_FILES=${PHP_OPCACHE_MAX_ACCELERATED_FILES} \ - PHP_OPCACHE_MEMORY_CONSUMPTION=${PHP_OPCACHE_MEMORY_CONSUMPTION} \ - PHP_OPCACHE_ENABLE=${PHP_OPCACHE_ENABLE} \ + PATH="/usr/bin:/usr/local/bin:/composer/vendor/bin:/symfony/bin:/usr/sbin:/sbin:$PATH" \ STACK_VERSION=${VERSION} -# ------------------------------------------------------------ +# ---------------------------------------------------------------------------- # System runtime dependencies +# Reference: https://pkgs.alpinelinux.org/packages # hadolint ignore=DL3018 +# ---------------------------------------------------------------------------- RUN set -eux; \ apk update; \ - # process control and init + \ + # Process control and init apk add --no-cache bash shadow su-exec tini; \ - # network / TLS + \ + # Network / TLS apk add --no-cache git curl wget ca-certificates openssl; \ - # misc runtime libs - apk add --no-cache gettext tzdata pcre2 zlib unzip; \ - # image/zip/xml/icu + \ + # Misc runtime libs + apk add --no-cache gettext tzdata pcre2 zlib p7zip; \ + \ + # Image/zip/xml/icu runtime libs apk add --no-cache icu-libs libzip libpng libjpeg-turbo freetype libxml2; \ - update-ca-certificates \ \ - git config --global --add safe.directory /var/www/html + # Security: Remove tar to mitigate CVE-2025-45582 + apk del tar 2>/dev/null || true; \ + \ + update-ca-certificates; \ + \ + # Git safe directory for mounted volumes + git config --global --add safe.directory /var/www/html; \ + \ + echo "✓ System dependencies installed successfully" -# ------------------------------------------------------------ -# Users & directories (least privilege) -# SC3009-safe: no brace expansion; explicit dirs listed +# ---------------------------------------------------------------------------- +# Users & directories (least privilege principle) +# Reference: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +# ---------------------------------------------------------------------------- RUN set -eux; \ - addgroup -g 101 -S nginx || true; \ - adduser -u 101 -S -D -H -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx || true; \ - addgroup -S redis || true; \ - adduser -S -D -h /var/lib/redis -s /sbin/nologin -G redis redis || true; \ + # Create nginx user/group (idempotent) + addgroup -g 101 -S nginx 2>/dev/null || true; \ + adduser -u 101 -S -D -H -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx 2>/dev/null || true; \ + \ + # Create redis user/group (idempotent) + addgroup -S redis 2>/dev/null || true; \ + adduser -S -D -h /var/lib/redis -s /sbin/nologin -G redis redis 2>/dev/null || true; \ + \ + # Create directory structure install -d -m 0755 \ /var/www/html/public \ /var/www/html/var \ @@ -114,7 +161,6 @@ RUN set -eux; \ /run/nginx \ /etc/nginx \ /etc/nginx/conf.d \ - /var/cache/nginx \ /var/cache/nginx/fastcgi \ /var/cache/nginx/proxy \ /var/cache/nginx/client_temp \ @@ -124,47 +170,176 @@ RUN set -eux; \ /var/cache/nginx/scgi_temp \ /var/lib/redis \ /tmp/symfony; \ - chown -R nginx:nginx /var/www/html /var/log/nginx /var/cache/nginx /run/nginx /var/run/php /var/log/symfony; \ + \ + # Set ownership + chown -R nginx:nginx \ + /var/www/html \ + /var/log/nginx \ + /var/log/symfony \ + /var/cache/nginx \ + /run/nginx \ + /var/run/php; \ chown -R redis:redis /var/lib/redis /var/log/redis; \ + \ + # Ensure correct permissions chmod 0755 /tmp/symfony; \ - chmod -R 0755 /var/www/html + chmod -R 0755 /var/www/html; \ + \ + echo "✓ Users and directories configured successfully" -# ------------------------------------------------------------ -# PHP core extensions (built from source with temporary deps) +# ---------------------------------------------------------------------------- +# PHP Core Extensions (built from source) +# Reference: https://github.com/docker-library/docs/blob/master/php/README.md # hadolint ignore=DL3018 -ARG PHP_CORE_EXTENSIONS="pdo pdo_mysql opcache intl zip bcmath gd mbstring xml" +# ---------------------------------------------------------------------------- RUN set -eux; \ - apk add --no-cache --virtual .build-deps \ - $PHPIZE_DEPS \ - icu-dev libzip-dev libpng-dev libjpeg-turbo-dev freetype-dev \ - libxml2-dev curl-dev oniguruma-dev postgresql-dev linux-headers; \ + echo "Installing PHP core extensions: ${PHP_CORE_EXTENSIONS}"; \ + \ + # Install build dependencies as virtual package + apk add --no-cache --virtual .build-deps \ + $PHPIZE_DEPS \ + icu-dev \ + libzip-dev \ + libpng-dev \ + libjpeg-turbo-dev \ + freetype-dev \ + libxml2-dev \ + curl-dev \ + oniguruma-dev \ + postgresql-dev \ + linux-headers; \ + \ + # Configure GD if present in extension list if echo " ${PHP_CORE_EXTENSIONS} " | grep -q " gd "; then \ + echo "Configuring GD with FreeType and JPEG support..."; \ docker-php-ext-configure gd --with-freetype --with-jpeg; \ fi; \ - # Intentional word splitting for extension list + \ + # Install extensions (intentional word splitting) # shellcheck disable=SC2086 docker-php-ext-install -j"$(nproc)" ${PHP_CORE_EXTENSIONS}; \ + \ + # Verify installation + echo "Verifying core extensions..."; \ + for ext in ${PHP_CORE_EXTENSIONS}; do \ + # OPcache is a Zend extension, check differently + if [ "${ext}" = "opcache" ]; then \ + if ! php -v | grep -qi "Zend OPcache"; then \ + echo "ERROR: Extension ${ext} not loaded!" >&2; \ + php -v; \ + exit 1; \ + fi; \ + elif ! php -m | grep -qi "^${ext}$"; then \ + echo "ERROR: Extension ${ext} not loaded!" >&2; \ + echo "Available extensions:"; \ + php -m; \ + exit 1; \ + fi; \ + echo " ✓ ${ext}"; \ + done; \ + \ + # Cleanup apk del .build-deps; \ - php -m | sed 's/^/ -> /' + rm -rf /tmp/*; \ + \ + echo "✓ Core extensions installed successfully"; \ + php -m | sed 's/^/ → /' -# ------------------------------------------------------------ -# PECL extensions (prod-safe) +# ---------------------------------------------------------------------------- +# PECL Extensions (production-safe) +# Reference: https://pecl.php.net/ # hadolint ignore=DL3018 -ARG PHP_PECL_EXTENSIONS="redis apcu uuid" +# ---------------------------------------------------------------------------- RUN set -eux; \ - apk add --no-cache --virtual .pecl-build-deps "$PHPIZE_DEPS" util-linux-dev || true; \ + echo "Installing PECL extensions: ${PHP_PECL_EXTENSIONS}"; \ + echo "Extension versions:"; \ + echo " - Redis: ${PHP_REDIS_VERSION}"; \ + echo " - APCu: ${PHP_APCU_VERSION}"; \ + echo " - UUID: ${PHP_UUID_VERSION}"; \ + echo " - ImageMagick: ${PHP_IMAGICK_VERSION}"; \ + echo " - AMQP: ${PHP_AMQP_VERSION}"; \ + \ + # Install build dependencies as virtual package + apk add --no-cache --virtual .pecl-build-deps \ + $PHPIZE_DEPS \ + util-linux-dev; \ + \ + # Install runtime dependencies (kept after build) + apk add --no-cache \ + util-linux; \ + \ + # Install each extension with version pinning for ext in ${PHP_PECL_EXTENSIONS}; do \ - case "$ext" in \ - imagick) apk add --no-cache imagemagick-dev ;; \ - amqp) apk add --no-cache rabbitmq-c-dev ;; \ + echo "Processing PECL extension: ${ext}"; \ + \ + case "${ext}" in \ + redis) \ + pecl install "redis-${PHP_REDIS_VERSION}" && \ + docker-php-ext-enable redis \ + ;; \ + apcu) \ + pecl install "apcu-${PHP_APCU_VERSION}" && \ + docker-php-ext-enable apcu \ + ;; \ + uuid) \ + pecl install "uuid-${PHP_UUID_VERSION}" && \ + docker-php-ext-enable uuid \ + ;; \ + imagick) \ + apk add --no-cache imagemagick-dev imagemagick && \ + pecl install "imagick-${PHP_IMAGICK_VERSION}" && \ + docker-php-ext-enable imagick \ + ;; \ + amqp) \ + apk add --no-cache rabbitmq-c-dev rabbitmq-c && \ + pecl install "amqp-${PHP_AMQP_VERSION}" && \ + docker-php-ext-enable amqp \ + ;; \ + xdebug) \ + echo "Skipping xdebug in base stage (handled in dev stage)" \ + ;; \ + *) \ + echo "ERROR: Unknown PECL extension: ${ext}" >&2; \ + echo "Available: redis, apcu, uuid, imagick, amqp, xdebug" >&2; \ + exit 1 \ + ;; \ + esac; \ + done; \ + \ + # Verify installation + echo "Verifying PECL extensions..."; \ + for ext in ${PHP_PECL_EXTENSIONS}; do \ + # Skip xdebug verification in base stage + if [ "${ext}" = "xdebug" ]; then continue; fi; \ + \ + if ! php -m | grep -qi "^${ext}$"; then \ + echo "ERROR: Extension ${ext} not loaded!" >&2; \ + echo "Available extensions:"; \ + php -m; \ + exit 1; \ + fi; \ + \ + # Show installed version with proper error handling + case "${ext}" in \ + redis|apcu|uuid|imagick) \ + php -r "echo ' ✓ ${ext} ' . phpversion('${ext}') . \"\n\";" || echo " ✓ ${ext} (version check failed)" \ + ;; \ + *) \ + echo " ✓ ${ext}" \ + ;; \ esac; \ - if pecl install "$ext"; then docker-php-ext-enable "$ext"; else echo "[warn] PECL $ext failed (non-fatal)"; fi; \ done; \ - pecl clear-cache; rm -rf /tmp/pear + \ + # Cleanup + pecl clear-cache; \ + rm -rf /tmp/pear ~/.pearrc /tmp/*; \ + apk del .pecl-build-deps; \ + \ + echo "✓ PECL extensions installed successfully" -# ------------------------------------------------------------ +# ---------------------------------------------------------------------------- # Copy Nginx & Redis binaries from official builds -# ------------------------------------------------------------ +# ---------------------------------------------------------------------------- COPY --from=nginx-build /usr/sbin/nginx /usr/sbin/nginx COPY --from=nginx-build /usr/lib/nginx /usr/lib/nginx COPY --from=nginx-build /etc/nginx/mime.types /etc/nginx/mime.types @@ -174,23 +349,51 @@ COPY --from=nginx-build /etc/nginx/scgi_params /etc/nginx/scgi_params COPY --from=nginx-build /etc/nginx/uwsgi_params /etc/nginx/uwsgi_params COPY --from=redis-build /usr/local/bin/redis-* /usr/local/bin/ -# ------------------------------------------------------------ +# Verify binaries +RUN set -eux; \ + nginx -v 2>&1 | sed 's/^/ → /'; \ + redis-server --version | sed 's/^/ → /'; \ + echo "✓ Nginx and Redis binaries copied successfully" + +# ---------------------------------------------------------------------------- # Composer (signature verified) -# ------------------------------------------------------------ +# Reference: https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md +# ---------------------------------------------------------------------------- RUN set -eux; \ + echo "Installing Composer ${COMPOSER_VERSION}..."; \ mkdir -p /composer; \ + \ + # Download and verify installer EXPECTED_CHECKSUM="$(wget -q -O - https://composer.github.io/installer.sig)"; \ wget -q -O composer-setup.php https://getcomposer.org/installer; \ ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384','composer-setup.php');")"; \ - if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then echo "Composer installer checksum mismatch" >&2; exit 1; fi; \ - php composer-setup.php --install-dir=/usr/local/bin --filename=composer --version="${COMPOSER_VERSION}"; \ + \ + # Fail-fast on checksum mismatch + if [ "${EXPECTED_CHECKSUM}" != "${ACTUAL_CHECKSUM}" ]; then \ + echo "ERROR: Composer installer checksum mismatch!" >&2; \ + echo "Expected: ${EXPECTED_CHECKSUM}" >&2; \ + echo "Got: ${ACTUAL_CHECKSUM}" >&2; \ + exit 1; \ + fi; \ + \ + # Install Composer + php composer-setup.php \ + --install-dir=/usr/local/bin \ + --filename=composer \ + --version="${COMPOSER_VERSION}"; \ + \ + # Cleanup rm -f composer-setup.php; \ - composer --version; \ - chmod 0775 /composer + chmod 0775 /composer; \ + \ + # Verify installation + composer --version | sed 's/^/ → /'; \ + echo "✓ Composer installed successfully" -# ------------------------------------------------------------ +# ---------------------------------------------------------------------------- # PHP-FPM socket tuning (for Nginx integration) -# ------------------------------------------------------------ +# Reference: https://www.php.net/manual/en/install.fpm.configuration.php +# ---------------------------------------------------------------------------- RUN set -eux; \ { \ echo '[www]'; \ @@ -199,11 +402,14 @@ RUN set -eux; \ echo 'listen.group = nginx'; \ echo 'listen.mode = 0660'; \ echo 'listen.backlog = 511'; \ - } > /usr/local/etc/php-fpm.d/zzz-socket-override.conf + } > /usr/local/etc/php-fpm.d/zzz-socket-override.conf; \ + \ + echo "✓ PHP-FPM socket configuration applied"; \ + cat /usr/local/etc/php-fpm.d/zzz-socket-override.conf | sed 's/^/ → /' -# ------------------------------------------------------------ +# ---------------------------------------------------------------------------- # Config templates & helper scripts (processed by entrypoint) -# ------------------------------------------------------------ +# ---------------------------------------------------------------------------- COPY nginx/nginx.conf /etc/nginx/nginx.conf.template COPY nginx/default.conf /etc/nginx/conf.d/default.conf.template COPY php/php.ini /usr/local/etc/php/php.ini.template @@ -211,29 +417,50 @@ COPY php/php-fpm.conf /usr/local/etc/php-fpm.conf.template COPY php/www.conf /usr/local/etc/php-fpm.d/www.conf.template COPY php/monitoring.conf /usr/local/etc/php-fpm.d/monitoring.conf.template COPY redis/redis.conf /etc/redis/redis.conf.template +COPY php/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini.template COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint COPY scripts/process-configs.sh /usr/local/bin/process-configs COPY scripts/quick-start.sh /usr/local/bin/quick-start + RUN chmod +x /usr/local/bin/docker-entrypoint /usr/local/bin/process-configs /usr/local/bin/quick-start -# ------------------------------------------------------------ -# Healthcheck templates (simple/comprehensive) -# ------------------------------------------------------------ -COPY --chown=nginx:nginx index.php /usr/local/share/php-api-stack/index.php -COPY --chown=nginx:nginx health.php /usr/local/share/php-api-stack/health-comprehensive.php +# ---------------------------------------------------------------------------- +# Healthcheck templates (staged, published at runtime) +# ---------------------------------------------------------------------------- +RUN install -d -m 0755 /opt/php-api-stack-templates +COPY --chown=nginx:nginx php/index.php /opt/php-api-stack-templates/index.php +COPY --chown=nginx:nginx php/health.php /opt/php-api-stack-templates/health.php + +# Validate syntax at build time RUN set -eux; \ - install -d -m 0755 /usr/local/share/php-api-stack; \ - if [ "$HEALTH_CHECK_TYPE" = "comprehensive" ]; then \ - cp /usr/local/share/php-api-stack/health-comprehensive.php /usr/local/share/php-api-stack/health.php; \ - else \ - printf "'healthy','timestamp'=>date('c')]);\\n" > /usr/local/share/php-api-stack/health.php; \ - fi; \ - php -l /usr/local/share/php-api-stack/health.php; \ - php -l /usr/local/share/php-api-stack/index.php + php -l /opt/php-api-stack-templates/index.php; \ + php -l /opt/php-api-stack-templates/health.php; \ + echo "✓ Healthcheck templates validated successfully" + +WORKDIR /var/www/html + +# ============================================================================ +# Stage: Production - Optimized production runtime +# ============================================================================ +FROM base AS production + +# Propagate production-specific build args +ARG PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 +ARG PHP_OPCACHE_MAX_ACCELERATED_FILES=20000 +ARG PHP_OPCACHE_ENABLE=1 +ARG PHP_OPCACHE_MEMORY_CONSUMPTION=256 + +# Production environment variables +ENV APP_ENV=production \ + APP_DEBUG=false \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS=${PHP_OPCACHE_VALIDATE_TIMESTAMPS} \ + PHP_OPCACHE_MAX_ACCELERATED_FILES=${PHP_OPCACHE_MAX_ACCELERATED_FILES} \ + PHP_OPCACHE_MEMORY_CONSUMPTION=${PHP_OPCACHE_MEMORY_CONSUMPTION} \ + PHP_OPCACHE_ENABLE=${PHP_OPCACHE_ENABLE} -# Materialize configs at build time (useful for some CIs) +# Materialize configs at build time for production RUN /usr/local/bin/process-configs # Healthcheck script used by Docker HEALTHCHECK @@ -245,36 +472,95 @@ RUN set -eux; \ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD /usr/local/bin/healthcheck -WORKDIR /var/www/html EXPOSE 80 443 VOLUME ["/var/www/html", "/var/log", "/var/lib/redis"] ENTRYPOINT ["/sbin/tini", "--", "docker-entrypoint"] CMD ["start"] -# ====================================================================== -# Stage: dev — extra tools only for development -# ====================================================================== +# ============================================================================ +# Stage: Dev - Development with additional tooling +# ============================================================================ FROM base AS dev +# Propagate dev-specific build args ARG SYMFONY_CLI_VERSION -ARG ENABLE_XDEBUG=0 +ARG XDEBUG_VERSION +ARG XDEBUG_ENABLE=1 +ARG APP_ENV=development + +# Development environment variables +ENV APP_ENV=development \ + APP_DEBUG=true \ + XDEBUG_ENABLE=${XDEBUG_ENABLE} \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS=1 \ + PHP_OPCACHE_REVALIDATE_FREQ=0 +# ---------------------------------------------------------------------------- +# Install development tools and build dependencies +# Consolidate all build-time dependencies in a single layer # hadolint ignore=DL3018 +# ---------------------------------------------------------------------------- RUN set -eux; \ + echo "Setting up development environment..."; \ + \ + # 1. Install runtime dev tools (kept after build) apk add --no-cache procps htop; \ - wget -q -O /tmp/symfony-installer https://get.symfony.com/cli/installer; \ - bash /tmp/symfony-installer; \ - mv /root/.symfony*/bin/symfony /usr/local/bin/symfony; \ + \ + # 2. Install ALL build-time dependencies as a single virtual package + apk add --no-cache --virtual .dev-build-deps \ + $PHPIZE_DEPS \ + build-base \ + curl \ + tar \ + linux-headers; \ + \ + # 3. Install Symfony CLI + if [ "${APP_ENV}" = "development" ]; then \ + echo "Installing Symfony CLI v${SYMFONY_CLI_VERSION}..."; \ + wget -q -O /tmp/symfony.tar.gz \ + "https://github.com/symfony-cli/symfony-cli/releases/download/v${SYMFONY_CLI_VERSION}/symfony-cli_linux_amd64.tar.gz"; \ + tar -xzf /tmp/symfony.tar.gz -C /usr/local/bin symfony; \ chmod +x /usr/local/bin/symfony; \ - rm -rf /root/.symfony* /tmp/symfony-installer; \ - symfony version || true + rm /tmp/symfony.tar.gz; \ + symfony version | sed 's/^/ → /'; \ + fi; \ + \ + # 4. Install Xdebug (manual compile with version pinning) + if [ "${APP_ENV}" = "development" ] && [ "${XDEBUG_ENABLE}" = "1" ]; then \ + echo "Installing Xdebug v${XDEBUG_VERSION}..."; \ + curl -fsSL "https://pecl.php.net/get/xdebug-${XDEBUG_VERSION}.tgz" -o xdebug.tgz; \ + tar -xzf xdebug.tgz; \ + cd "xdebug-${XDEBUG_VERSION}"; \ + phpize; \ + ./configure; \ + make -j"$(nproc)"; \ + make install; \ + cd ..; \ + rm -rf xdebug.tgz "xdebug-${XDEBUG_VERSION}"; \ + echo " ✓ Xdebug ${XDEBUG_VERSION} installed successfully"; \ + else \ + echo "Skipping Xdebug install (APP_ENV=${APP_ENV} XDEBUG_ENABLE=${XDEBUG_ENABLE})"; \ + fi; \ + \ + # 5. Cleanup all build dependencies + apk del .dev-build-deps; \ + rm -rf /tmp/*; \ + \ + echo "✓ Development environment setup complete" -# Optional Xdebug installation for local debugging -# hadolint ignore=DL3018 +# Materialize configs for development +RUN /usr/local/bin/process-configs + +# Development healthcheck (more verbose for debugging) RUN set -eux; \ - if [ "${ENABLE_XDEBUG}" = "1" ]; then \ - apk add --no-cache --virtual .xd-build "$PHPIZE_DEPS"; \ - pecl install xdebug; \ - docker-php-ext-enable xdebug; \ - apk del --no-cache .xd-build || true; \ - fi + printf '#!/bin/sh\n' > /usr/local/bin/healthcheck; \ + printf 'curl -f http://localhost/health || exit 1\n' >> /usr/local/bin/healthcheck; \ + chmod +x /usr/local/bin/healthcheck + +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD /usr/local/bin/healthcheck + +EXPOSE 80 443 9003 +VOLUME ["/var/www/html", "/var/log", "/var/lib/redis"] +ENTRYPOINT ["/sbin/tini", "--", "docker-entrypoint"] +CMD ["start"] \ No newline at end of file diff --git a/IMAGE_USAGE_GUIDE.md b/IMAGE_USAGE_GUIDE.md index 2dd8a81..4187034 100644 --- a/IMAGE_USAGE_GUIDE.md +++ b/IMAGE_USAGE_GUIDE.md @@ -1,22 +1,28 @@ # Image Usage Guide - PHP API Stack **Audience**: End users and developers -**Purpose**: Complete guide for using the published Docker image +**Purpose**: Complete guide for using the published Docker image +**Version**: 1.5.0 + +--- ## 📋 Table of Contents -- [Quick Start](#quick-start) -- [Installation](#installation) -- [Basic Usage](#basic-usage) -- [Configuration](#configuration) -- [Framework Integration](#framework-integration) -- [Docker Compose](#docker-compose) -- [Kubernetes Deployment](#kubernetes-deployment) -- [Production Setup](#production-setup) -- [Monitoring and Health](#monitoring-and-health) -- [Performance Tuning](#performance-tuning) -- [Troubleshooting](#troubleshooting) -- [Examples](#examples) +- [Quick Start](#-quick-start) +- [Installation](#-installation) +- [Basic Usage](#-basic-usage) +- [Configuration](#%EF%B8%8F-configuration) +- [Framework Integration](#-framework-integration) +- [Docker Compose](#-docker-compose) +- [Kubernetes Deployment](#%EF%B8%8F-kubernetes-deployment) +- [Production Setup](#-production-setup) +- [Monitoring and Health](#-monitoring-and-health) +- [Performance Tuning](#-performance-tuning) +- [Troubleshooting](#-troubleshooting) +- [Examples](#-examples) +- [Best Practices](#-best-practices) + +--- ## 🚀 Quick Start @@ -25,13 +31,13 @@ ```bash # Pull and run docker pull kariricode/php-api-stack:latest -docker run -d -p 8080:80 kariricode/php-api-stack:latest +docker run -d -p 8080:80 --name my-app kariricode/php-api-stack:latest # Test curl http://localhost:8080 ``` -**That's it!** You'll see the demo landing page showing stack status. +**That's it!** You'll see the demo landing page showing stack status and component information. ### With Your Application @@ -47,21 +53,27 @@ docker run -d \ curl http://localhost:8080 ``` +--- + ## 📥 Installation ### Prerequisites - **Docker**: Version 20.10 or higher -- **Docker Compose**: Version 2.0 or higher (optional) +- **Docker Compose**: Version 2.0 or higher (optional, for multi-service setups) +- **Disk Space**: Minimum 300MB for the image ### Pull Image ```bash -# Latest version +# Latest stable version docker pull kariricode/php-api-stack:latest -# Specific version -docker pull kariricode/php-api-stack:1.2.1 +# Specific version (recommended for production) +docker pull kariricode/php-api-stack:1.5.0 + +# Development version (with Xdebug, Symfony CLI) +docker pull kariricode/php-api-stack:dev # Verify docker images kariricode/php-api-stack @@ -69,40 +81,55 @@ docker images kariricode/php-api-stack ### Available Tags -| Tag | Description | When to Use | -|-----|-------------|-------------| -| `latest` | Latest stable | Most cases | -| `stable` | Production release | Production | -| `1.2.1` | Specific version | Version pinning | -| `1.2` | Minor version | Auto-patch updates | -| `1` | Major version | Auto-minor updates | -| `test` | With comprehensive health | Testing/Monitoring | +| Tag | Description | Size | When to Use | +|-----|-------------|------|-------------| +| `latest` | Latest stable production | ~225MB | Most cases | +| `1.5.0` | Specific version | ~225MB | Version pinning | +| `1.5` | Minor version | ~225MB | Auto-patch updates | +| `1` | Major version | ~225MB | Auto-minor updates | +| `dev` | Development build | ~244MB | Local development with debugging | +| `test` | With comprehensive health | ~216MB | Testing/Monitoring | + +**Recommendation**: Use specific version tags (`1.5.0`) in production for predictability. + +--- ## 🎯 Basic Usage ### Standalone Container +#### Basic Run + ```bash -# Basic run docker run -d \ --name my-php-app \ -p 8080:80 \ kariricode/php-api-stack:latest +``` + +#### With Application Volume -# With volume +```bash docker run -d \ --name my-php-app \ -p 8080:80 \ -v $(pwd)/app:/var/www/html \ kariricode/php-api-stack:latest +``` -# With environment variables +#### Production Setup + +```bash docker run -d \ --name my-php-app \ - -p 8080:80 \ + -p 80:80 \ + -v $(pwd)/app:/var/www/html:ro \ + -v $(pwd)/logs:/var/log \ -e APP_ENV=production \ + -e APP_DEBUG=false \ -e PHP_MEMORY_LIMIT=512M \ - -v $(pwd)/app:/var/www/html \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + --restart unless-stopped \ kariricode/php-api-stack:latest ``` @@ -115,203 +142,396 @@ docker logs -f my-php-app # Access shell docker exec -it my-php-app bash -# Restart services -docker exec my-php-app supervisorctl restart all +# Check running processes +docker exec my-php-app ps aux + +# Check PHP version +docker exec my-php-app php -v + +# Check PHP extensions +docker exec my-php-app php -m # Stop container docker stop my-php-app # Remove container docker rm my-php-app + +# Restart container +docker restart my-php-app ``` +### Service Management + +Services are managed by the custom entrypoint script: + +```bash +# View all processes +docker exec my-php-app ps aux | grep -E "(nginx|php-fpm|redis)" + +# Nginx +docker exec my-php-app nginx -t # Test config +docker exec my-php-app nginx -s reload # Reload config + +# PHP-FPM +docker exec my-php-app php-fpm -t # Test config +docker exec my-php-app kill -USR2 $(cat /var/run/php/php-fpm.pid) # Reload + +# Redis +docker exec my-php-app redis-cli ping # Test connection +docker exec my-php-app redis-cli info # Get info +``` + +--- + ## ⚙️ Configuration ### Environment Variables -#### PHP Settings +#### Application Settings + +```bash +# Environment and debug +-e APP_ENV=production # production|development|test +-e APP_DEBUG=false # Enable/disable debug mode +-e APP_NAME=my-application # Application name +``` + +#### PHP Runtime Settings + ```bash # Memory and execution --e PHP_MEMORY_LIMIT=512M --e PHP_MAX_EXECUTION_TIME=60 --e PHP_MAX_INPUT_TIME=60 --e PHP_POST_MAX_SIZE=100M --e PHP_UPLOAD_MAX_FILESIZE=100M +-e PHP_MEMORY_LIMIT=512M # Memory limit per request +-e PHP_MAX_EXECUTION_TIME=60 # Script execution timeout +-e PHP_MAX_INPUT_TIME=60 # Input parsing timeout +-e PHP_POST_MAX_SIZE=100M # Max POST data size +-e PHP_UPLOAD_MAX_FILESIZE=100M # Max file upload size # Error handling --e PHP_DISPLAY_ERRORS=Off --e PHP_ERROR_LOG=/var/log/php/error.log +-e PHP_DISPLAY_ERRORS=Off # Display errors (On for dev) +-e PHP_ERROR_LOG=/var/log/php/error.log # Error log path +-e PHP_LOG_ERRORS=On # Enable error logging # Timezone --e PHP_DATE_TIMEZONE=America/New_York +-e PHP_DATE_TIMEZONE=UTC # Default timezone ``` -#### PHP-FPM Pool +#### PHP-FPM Pool Configuration + ```bash -# Process manager --e PHP_FPM_PM=dynamic --e PHP_FPM_PM_MAX_CHILDREN=50 --e PHP_FPM_PM_START_SERVERS=5 --e PHP_FPM_PM_MIN_SPARE_SERVERS=5 --e PHP_FPM_PM_MAX_SPARE_SERVERS=10 --e PHP_FPM_PM_MAX_REQUESTS=500 +# Process manager mode +-e PHP_FPM_PM=dynamic # static|dynamic|ondemand + +# Dynamic mode settings +-e PHP_FPM_PM_MAX_CHILDREN=60 # Maximum child processes +-e PHP_FPM_PM_START_SERVERS=10 # Initial server count +-e PHP_FPM_PM_MIN_SPARE_SERVERS=5 # Minimum idle servers +-e PHP_FPM_PM_MAX_SPARE_SERVERS=20 # Maximum idle servers +-e PHP_FPM_PM_MAX_REQUESTS=500 # Max requests per child + +# Static mode (for production with predictable load) +-e PHP_FPM_PM=static +-e PHP_FPM_PM_MAX_CHILDREN=100 # Fixed number of children ``` -#### OPcache +#### OPcache Configuration + ```bash -# OPcache configuration --e PHP_OPCACHE_ENABLE=1 --e PHP_OPCACHE_MEMORY=256 --e PHP_OPCACHE_MAX_FILES=20000 --e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 --e PHP_OPCACHE_JIT=tracing --e PHP_OPCACHE_JIT_BUFFER_SIZE=128M +# Enable/disable +-e PHP_OPCACHE_ENABLE=1 # 1=enabled, 0=disabled + +# Memory settings +-e PHP_OPCACHE_MEMORY=256 # Memory consumption (MB) +-e PHP_OPCACHE_MAX_ACCELERATED_FILES=20000 # Max cached files + +# Validation (0 for production, 1 for development) +-e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 # 0=never revalidate, 1=check mtimes +-e PHP_OPCACHE_REVALIDATE_FREQ=0 # Revalidation frequency (seconds) + +# JIT configuration +-e PHP_OPCACHE_JIT=tracing # off|tracing|function +-e PHP_OPCACHE_JIT_BUFFER_SIZE=128M # JIT buffer size ``` -#### Application +#### Nginx Configuration + ```bash -# Application settings --e APP_ENV=production --e APP_DEBUG=false --e APP_NAME=my-api +# Worker configuration +-e NGINX_WORKER_PROCESSES=auto # auto = CPU cores +-e NGINX_WORKER_CONNECTIONS=2048 # Connections per worker + +# Timeouts +-e NGINX_KEEPALIVE_TIMEOUT=65 # Keep-alive timeout (seconds) +-e NGINX_CLIENT_BODY_TIMEOUT=60 # Client body timeout +-e NGINX_SEND_TIMEOUT=60 # Send timeout + +# Limits +-e NGINX_CLIENT_MAX_BODY_SIZE=20M # Max upload size +``` + +#### Redis Configuration + +```bash +# Connection (standalone mode) +-e REDIS_HOST=127.0.0.1 # Redis host +-e REDIS_PASSWORD=your-password # Redis password +-e REDIS_DATABASES=16 # Number of databases + +# Memory settings +-e REDIS_MAXMEMORY=256mb # Max memory usage +-e REDIS_MAXMEMORY_POLICY=allkeys-lru # Eviction policy + +# Persistence +-e REDIS_APPENDONLY=yes # Enable AOF persistence +-e REDIS_APPENDFSYNC=everysec # Fsync frequency ``` +**Note**: When using Docker Compose, `REDIS_HOST` should be set to the service name (e.g., `redis`). For standalone containers, it uses `127.0.0.1` (internal Redis). + ### Volume Mounts +#### Application Code + ```bash -# Application code (read-only in production) +# Development (read-write) +-v $(pwd)/app:/var/www/html + +# Production (read-only) -v $(pwd)/app:/var/www/html:ro +``` -# Logs (for external monitoring) +#### Logs + +```bash +# Export logs to host -v $(pwd)/logs:/var/log -# Custom configurations --v $(pwd)/php.ini:/usr/local/etc/php/php.ini:ro --v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro +# Then access: +# - $(pwd)/logs/nginx/access.log +# - $(pwd)/logs/nginx/error.log +# - $(pwd)/logs/php/error.log +# - $(pwd)/logs/redis/redis.log +``` + +#### Custom Configurations + +```bash +# Custom PHP configuration +-v $(pwd)/custom-php.ini:/usr/local/etc/php/conf.d/99-custom.ini:ro + +# Custom Nginx configuration +-v $(pwd)/custom-nginx.conf:/etc/nginx/conf.d/custom.conf:ro + +# Custom Redis configuration (if needed) +-v $(pwd)/custom-redis.conf:/etc/redis/redis-custom.conf:ro ``` ### Network Configuration ```bash -# Custom network -docker network create my-network +# Create custom network +docker network create my-app-network -# Run with network +# Run container in network docker run -d \ --name my-php-app \ - --network my-network \ + --network my-app-network \ -p 8080:80 \ kariricode/php-api-stack:latest + +# Connect another container +docker run -d \ + --name mysql \ + --network my-app-network \ + -e MYSQL_ROOT_PASSWORD=secret \ + mysql:8.0 ``` +--- + ## 🚀 Framework Integration ### Symfony Application #### Project Structure + ``` my-symfony-app/ ├── bin/ ├── config/ -├── public/ ← Mount point +├── public/ ← Document root (Nginx serves from here) │ └── index.php ├── src/ ├── var/ -└── vendor/ +│ ├── cache/ +│ └── log/ +├── vendor/ +├── .env +└── composer.json ``` #### Docker Run + ```bash docker run -d \ --name symfony-app \ -p 8080:80 \ -v $(pwd):/var/www/html \ -e APP_ENV=prod \ - -e APP_SECRET=your-secret-here \ - -e DATABASE_URL=mysql://user:pass@db:3306/dbname \ + -e APP_SECRET=$(openssl rand -hex 16) \ + -e DATABASE_URL="mysql://user:pass@db:3306/symfony" \ kariricode/php-api-stack:latest ``` #### With Database + ```bash # Create network -docker network create symfony-net +docker network create symfony-network # Run MySQL docker run -d \ --name symfony-db \ - --network symfony-net \ + --network symfony-network \ -e MYSQL_ROOT_PASSWORD=root \ -e MYSQL_DATABASE=symfony \ + -e MYSQL_USER=symfony \ + -e MYSQL_PASSWORD=symfony \ mysql:8.0 # Run Symfony app docker run -d \ --name symfony-app \ - --network symfony-net \ + --network symfony-network \ -p 8080:80 \ -v $(pwd):/var/www/html \ -e APP_ENV=prod \ - -e DATABASE_URL=mysql://root:root@symfony-db:3306/symfony \ + -e DATABASE_URL="mysql://symfony:symfony@symfony-db:3306/symfony" \ kariricode/php-api-stack:latest ``` -#### Symfony Commands +#### Common Symfony Commands + ```bash -# Cache clear +# Cache operations docker exec symfony-app php bin/console cache:clear +docker exec symfony-app php bin/console cache:warmup -# Database migrations +# Database +docker exec symfony-app php bin/console doctrine:database:create docker exec symfony-app php bin/console doctrine:migrations:migrate --no-interaction -# Assets install +# Assets docker exec symfony-app php bin/console assets:install public --symlink + +# Debug +docker exec symfony-app php bin/console debug:router +docker exec symfony-app php bin/console debug:container ``` ### Laravel Application #### Project Structure + ``` my-laravel-app/ ├── app/ ├── bootstrap/ ├── config/ -├── public/ ← Mount point +├── database/ +├── public/ ← Document root (Nginx serves from here) │ └── index.php ├── resources/ ├── routes/ -└── storage/ +├── storage/ +│ ├── app/ +│ ├── framework/ +│ └── logs/ +├── vendor/ +├── .env +└── composer.json ``` #### Docker Run + ```bash docker run -d \ --name laravel-app \ -p 8080:80 \ -v $(pwd):/var/www/html \ -e APP_ENV=production \ - -e APP_KEY=base64:your-app-key-here \ + -e APP_KEY=$(php -r "echo 'base64:'.base64_encode(random_bytes(32));") \ -e DB_CONNECTION=mysql \ -e DB_HOST=db \ -e DB_DATABASE=laravel \ - -e DB_USERNAME=root \ + -e DB_USERNAME=laravel \ -e DB_PASSWORD=secret \ kariricode/php-api-stack:latest ``` -#### Laravel Commands +#### With Database and Redis + ```bash -# Optimize -docker exec laravel-app php artisan optimize +# Create network +docker network create laravel-network + +# Run MySQL +docker run -d \ + --name laravel-db \ + --network laravel-network \ + -e MYSQL_ROOT_PASSWORD=root \ + -e MYSQL_DATABASE=laravel \ + -e MYSQL_USER=laravel \ + -e MYSQL_PASSWORD=secret \ + mysql:8.0 + +# Run Redis (external) +docker run -d \ + --name laravel-redis \ + --network laravel-network \ + redis:7-alpine + +# Run Laravel app +docker run -d \ + --name laravel-app \ + --network laravel-network \ + -p 8080:80 \ + -v $(pwd):/var/www/html \ + -e APP_ENV=production \ + -e APP_KEY=base64:your-key-here \ + -e DB_CONNECTION=mysql \ + -e DB_HOST=laravel-db \ + -e DB_DATABASE=laravel \ + -e DB_USERNAME=laravel \ + -e DB_PASSWORD=secret \ + -e CACHE_DRIVER=redis \ + -e SESSION_DRIVER=redis \ + -e QUEUE_CONNECTION=redis \ + -e REDIS_HOST=laravel-redis \ + kariricode/php-api-stack:latest +``` + +**Note**: The container has an internal Redis instance on `127.0.0.1:6379`. For external Redis (as shown above), use the container name as host. -# Cache config +#### Common Laravel Commands + +```bash +# Optimization +docker exec laravel-app php artisan optimize docker exec laravel-app php artisan config:cache +docker exec laravel-app php artisan route:cache +docker exec laravel-app php artisan view:cache -# Migrations +# Database docker exec laravel-app php artisan migrate --force +docker exec laravel-app php artisan db:seed --force -# Queue worker (if needed) -docker exec laravel-app supervisorctl start symfony-messenger +# Queue worker (run in background) +docker exec -d laravel-app php artisan queue:work --tries=3 + +# Cache +docker exec laravel-app php artisan cache:clear +docker exec laravel-app php artisan config:clear ``` ### Custom PHP Application @@ -319,10 +539,16 @@ docker exec laravel-app supervisorctl start symfony-messenger ```bash # Simple PHP app structure my-app/ -└── public/ - ├── index.php - ├── api.php - └── assets/ +├── public/ +│ ├── index.php +│ ├── api.php +│ └── assets/ +│ ├── css/ +│ └── js/ +├── src/ +│ └── App.php +├── vendor/ +└── composer.json # Run docker run -d \ @@ -332,6 +558,8 @@ docker run -d \ kariricode/php-api-stack:latest ``` +--- + ## 🐳 Docker Compose ### Basic Setup @@ -351,9 +579,10 @@ services: - ./app:/var/www/html - ./logs:/var/log environment: - - APP_ENV=production - - PHP_MEMORY_LIMIT=512M - - PHP_FPM_PM_MAX_CHILDREN=100 + APP_ENV: production + PHP_MEMORY_LIMIT: 512M + PHP_FPM_PM_MAX_CHILDREN: 100 + REDIS_HOST: 127.0.0.1 # Using internal Redis healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s @@ -362,14 +591,17 @@ services: restart: unless-stopped ``` -Run: +Commands: + ```bash -docker-compose up -d -docker-compose logs -f -docker-compose down +docker-compose up -d # Start +docker-compose logs -f # View logs +docker-compose ps # Status +docker-compose down # Stop +docker-compose restart app # Restart service ``` -### With Database (Symfony) +### With External Database ```yaml version: '3.8' @@ -384,8 +616,9 @@ services: - ./:/var/www/html - ./logs:/var/log environment: - - APP_ENV=prod - - DATABASE_URL=mysql://symfony:secret@db:3306/symfony + APP_ENV: prod + DATABASE_URL: mysql://symfony:secret@db:3306/symfony + REDIS_HOST: 127.0.0.1 # Internal Redis depends_on: db: condition: service_healthy @@ -400,10 +633,10 @@ services: image: mysql:8.0 container_name: symfony-db environment: - - MYSQL_DATABASE=symfony - - MYSQL_USER=symfony - - MYSQL_PASSWORD=secret - - MYSQL_ROOT_PASSWORD=root + MYSQL_DATABASE: symfony + MYSQL_USER: symfony + MYSQL_PASSWORD: secret + MYSQL_ROOT_PASSWORD: root volumes: - db-data:/var/lib/mysql healthcheck: @@ -423,14 +656,14 @@ networks: driver: bridge ``` -### Production Setup +### Production Configuration ```yaml version: '3.8' services: app: - image: kariricode/php-api-stack:stable + image: kariricode/php-api-stack:1.5.0 # Pin version container_name: prod-app ports: - "80:80" @@ -438,12 +671,14 @@ services: - ./app:/var/www/html:ro # Read-only - ./logs:/var/log environment: - - APP_ENV=production - - APP_DEBUG=false - - PHP_MEMORY_LIMIT=512M - - PHP_FPM_PM=static - - PHP_FPM_PM_MAX_CHILDREN=100 - - PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 + APP_ENV: production + APP_DEBUG: "false" + PHP_MEMORY_LIMIT: 512M + PHP_FPM_PM: static + PHP_FPM_PM_MAX_CHILDREN: 100 + PHP_OPCACHE_VALIDATE_TIMESTAMPS: "0" + PHP_OPCACHE_JIT: tracing + REDIS_HOST: 127.0.0.1 deploy: resources: limits: @@ -464,11 +699,19 @@ services: options: max-size: "10m" max-file: "3" + networks: + - prod-network + +networks: + prod-network: + driver: bridge ``` +--- + ## ☸️ Kubernetes Deployment -### Deployment +### Deployment Manifest Create `k8s/deployment.yaml`: @@ -479,6 +722,7 @@ metadata: name: php-api-stack labels: app: php-api-stack + version: v1.5.0 spec: replicas: 3 selector: @@ -488,20 +732,29 @@ spec: metadata: labels: app: php-api-stack + version: v1.5.0 spec: containers: - name: app - image: kariricode/php-api-stack:stable + image: kariricode/php-api-stack:1.5.0 + imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: http + protocol: TCP env: - name: APP_ENV value: "production" - name: PHP_MEMORY_LIMIT value: "512M" + - name: PHP_FPM_PM + value: "static" - name: PHP_FPM_PM_MAX_CHILDREN value: "100" + - name: PHP_OPCACHE_VALIDATE_TIMESTAMPS + value: "0" + - name: REDIS_HOST + value: "127.0.0.1" # Using internal Redis resources: requests: memory: "512Mi" @@ -513,29 +766,37 @@ spec: httpGet: path: /health port: 80 + scheme: HTTP initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 + successThreshold: 1 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 80 + scheme: HTTP initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 + successThreshold: 1 failureThreshold: 3 volumeMounts: - name: app-volume mountPath: /var/www/html readOnly: true + - name: logs-volume + mountPath: /var/log volumes: - name: app-volume persistentVolumeClaim: claimName: app-pvc + - name: logs-volume + emptyDir: {} ``` -### Service +### Service Manifest Create `k8s/service.yaml`: @@ -544,17 +805,24 @@ apiVersion: v1 kind: Service metadata: name: php-api-stack + labels: + app: php-api-stack spec: - type: LoadBalancer + type: ClusterIP selector: app: php-api-stack ports: - - protocol: TCP + - name: http + protocol: TCP port: 80 targetPort: 80 + sessionAffinity: ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 3600 ``` -### Ingress +### Ingress Manifest Create `k8s/ingress.yaml`: @@ -566,11 +834,13 @@ metadata: annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" spec: tls: - hosts: - api.example.com - secretName: api-tls + secretName: api-tls-secret rules: - host: api.example.com http: @@ -584,24 +854,64 @@ spec: number: 80 ``` -### Deploy +### HorizontalPodAutoscaler + +Create `k8s/hpa.yaml`: + +```yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: php-api-stack-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: php-api-stack + minReplicas: 3 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 +``` + +### Deploy to Kubernetes ```bash -# Apply configurations +# Apply all configurations kubectl apply -f k8s/ -# Check status +# Check deployment status +kubectl get deployments kubectl get pods kubectl get services kubectl get ingress -# Logs +# View logs kubectl logs -f deployment/php-api-stack -# Scale +# Scale manually kubectl scale deployment php-api-stack --replicas=5 + +# Check autoscaling +kubectl get hpa + +# Port-forward for local testing +kubectl port-forward deployment/php-api-stack 8080:80 ``` +--- + ## 🏭 Production Setup ### Resource Limits @@ -616,10 +926,12 @@ docker run -d \ --cpu-shares=1024 \ -p 80:80 \ -v $(pwd)/app:/var/www/html:ro \ - kariricode/php-api-stack:stable + -e APP_ENV=production \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + kariricode/php-api-stack:1.5.0 ``` -### Production Environment +### Production Environment Variables ```bash docker run -d \ @@ -632,22 +944,30 @@ docker run -d \ -e PHP_MEMORY_LIMIT=512M \ -e PHP_FPM_PM=static \ -e PHP_FPM_PM_MAX_CHILDREN=100 \ + -e PHP_OPCACHE_ENABLE=1 \ -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ -e PHP_OPCACHE_REVALIDATE_FREQ=0 \ - kariricode/php-api-stack:stable + -e PHP_OPCACHE_JIT=tracing \ + -e PHP_OPCACHE_JIT_BUFFER_SIZE=256M \ + --restart unless-stopped \ + kariricode/php-api-stack:1.5.0 ``` -### High Availability +### High Availability with Docker Swarm -```yaml -# docker-compose.prod.yml +```bash +# Initialize swarm +docker swarm init + +# Create stack file: docker-stack.yml +cat > docker-stack.yml << 'EOF' version: '3.8' services: app: - image: kariricode/php-api-stack:stable + image: kariricode/php-api-stack:1.5.0 deploy: - replicas: 3 + replicas: 5 update_config: parallelism: 1 delay: 10s @@ -656,6 +976,7 @@ services: condition: on-failure delay: 5s max_attempts: 3 + window: 120s resources: limits: cpus: '2' @@ -666,97 +987,199 @@ services: ports: - "80:80" volumes: - - ./app:/var/www/html:ro + - app-data:/var/www/html:ro environment: - - APP_ENV=production + APP_ENV: production + PHP_FPM_PM: static + PHP_FPM_PM_MAX_CHILDREN: 100 + PHP_OPCACHE_VALIDATE_TIMESTAMPS: 0 healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s timeout: 3s retries: 3 + +volumes: + app-data: + driver: local +EOF + +# Deploy stack +docker stack deploy -c docker-stack.yml prod-stack + +# Check services +docker service ls +docker service ps prod-stack_app + +# Scale +docker service scale prod-stack_app=10 + +# Update image (zero-downtime) +docker service update --image kariricode/php-api-stack:1.5.1 prod-stack_app + +# Remove stack +docker stack rm prod-stack ``` +--- + ## 📊 Monitoring and Health -### Health Checks +### Health Check Endpoints + +#### Simple Health Check (Production) -#### Simple Health Check ```bash -# Basic health +# Basic health status curl http://localhost:8080/health + # Response: healthy -# With details +# With details (HTTP status) curl -v http://localhost:8080/health # HTTP/1.1 200 OK # Content-Type: text/plain +# Content-Length: 7 +# # healthy ``` +**Use case**: Load balancers, Kubernetes liveness probes, Docker healthchecks + #### Comprehensive Health Check + ```bash -# Full health report (test builds only) +# Full system diagnostics curl http://localhost:8080/health.php | jq -# Check specific component -curl -s http://localhost:8080/health.php | jq '.checks.php' -curl -s http://localhost:8080/health.php | jq '.checks.opcache' -curl -s http://localhost:8080/health.php | jq '.checks.redis' +# Response includes: +# - Overall status (healthy/degraded/unhealthy) +# - PHP runtime details +# - OPcache statistics +# - Redis connectivity +# - System resources +# - Application directories ``` -### PHP-FPM Status +**Sample response**: + +```json +{ + "status": "healthy", + "timestamp": "2025-10-24T22:00:00+00:00", + "overall": "✓ All Systems Operational", + "components": { + "php": { + "status": "healthy", + "details": { + "version": "8.4.13", + "memory_limit": "512M", + "max_execution_time": "60" + } + }, + "opcache": { + "status": "healthy", + "details": { + "enabled": true, + "hit_rate": 99.87, + "memory_used": "45.2 MB", + "jit_enabled": true + } + }, + "redis": { + "status": "healthy", + "details": { + "connected": true, + "version": "7.2.11", + "latency_ms": 0.5 + } + } + } +} +``` + +### Checking Components ```bash -# Status page (internal only) -docker exec my-app curl http://localhost/fpm-status +# PHP version and extensions +docker exec my-app php -v +docker exec my-app php -m + +# OPcache status +docker exec my-app php -r "print_r(opcache_get_status());" + +# Redis connectivity +docker exec my-app redis-cli -h 127.0.0.1 ping +# PONG -# Ping -docker exec my-app curl http://localhost/fpm-ping -# Response: pong +# Redis info +docker exec my-app redis-cli -h 127.0.0.1 info server + +# Nginx status +docker exec my-app nginx -t +docker exec my-app nginx -V + +# Process list +docker exec my-app ps aux ``` -### Logs +### Log Access ```bash -# Application logs +# Real-time logs docker logs -f my-app -# Nginx access logs +# Nginx access log docker exec my-app tail -f /var/log/nginx/access.log -# Nginx error logs +# Nginx error log docker exec my-app tail -f /var/log/nginx/error.log -# PHP error logs +# PHP error log docker exec my-app tail -f /var/log/php/error.log -# PHP-FPM logs +# PHP-FPM access log docker exec my-app tail -f /var/log/php/fpm-access.log + +# PHP-FPM slow log docker exec my-app tail -f /var/log/php/fpm-slow.log -# All logs +# Redis log +docker exec my-app tail -f /var/log/redis/redis.log + +# All logs simultaneously docker exec my-app tail -f /var/log/**/*.log ``` -### Metrics +### Metrics and Statistics ```bash -# Container stats +# Container resource usage docker stats my-app -# OPcache stats -docker exec my-app php -r "print_r(opcache_get_status());" +# Container inspect (detailed info) +docker inspect my-app -# Process list -docker exec my-app ps aux +# OPcache statistics +docker exec my-app php -r " +\$status = opcache_get_status(); +echo 'Memory Used: ' . \$status['memory_usage']['used_memory'] / 1024 / 1024 . ' MB' . PHP_EOL; +echo 'Hit Rate: ' . \$status['opcache_statistics']['opcache_hit_rate'] . '%' . PHP_EOL; +echo 'Cached Scripts: ' . \$status['opcache_statistics']['num_cached_scripts'] . PHP_EOL; +" -# Service status -docker exec my-app supervisorctl status +# Redis statistics +docker exec my-app redis-cli -h 127.0.0.1 info stats +docker exec my-app redis-cli -h 127.0.0.1 info memory ``` +--- + ## ⚡ Performance Tuning -### For High Traffic +### High Traffic Configuration + +For applications with heavy load (100+ req/s): ```bash docker run -d \ @@ -768,134 +1191,327 @@ docker run -d \ -e PHP_MEMORY_LIMIT=512M \ -e PHP_FPM_PM=static \ -e PHP_FPM_PM_MAX_CHILDREN=200 \ + -e PHP_OPCACHE_ENABLE=1 \ -e PHP_OPCACHE_MEMORY=512 \ - -e PHP_OPCACHE_MAX_FILES=50000 \ + -e PHP_OPCACHE_MAX_ACCELERATED_FILES=50000 \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + -e PHP_OPCACHE_JIT=tracing \ + -e PHP_OPCACHE_JIT_BUFFER_SIZE=256M \ + -e NGINX_WORKER_PROCESSES=auto \ -e NGINX_WORKER_CONNECTIONS=4096 \ kariricode/php-api-stack:latest ``` -### For Low Latency +### Low Latency Configuration + +For API with strict latency requirements (<50ms): ```bash docker run -d \ --name low-latency-app \ + --memory="1g" \ + --cpus="2" \ -p 80:80 \ -v $(pwd)/app:/var/www/html:ro \ + -e PHP_MEMORY_LIMIT=256M \ + -e PHP_FPM_PM=ondemand \ + -e PHP_FPM_PM_MAX_CHILDREN=50 \ + -e PHP_FPM_PM_PROCESS_IDLE_TIMEOUT=10s \ -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ -e PHP_OPCACHE_JIT=tracing \ -e PHP_OPCACHE_JIT_BUFFER_SIZE=256M \ - -e PHP_FPM_PM=ondemand \ - -e PHP_FPM_PM_MAX_CHILDREN=50 \ + -e NGINX_KEEPALIVE_TIMEOUT=30 \ kariricode/php-api-stack:latest ``` -### For Memory Efficiency +### Memory-Constrained Environment + +For environments with limited memory (<512MB): ```bash docker run -d \ --name memory-efficient-app \ --memory="512m" \ + --memory-swap="512m" \ -p 80:80 \ -v $(pwd)/app:/var/www/html:ro \ - -e PHP_MEMORY_LIMIT=256M \ + -e PHP_MEMORY_LIMIT=128M \ -e PHP_FPM_PM=dynamic \ -e PHP_FPM_PM_MAX_CHILDREN=25 \ - -e PHP_OPCACHE_MEMORY=128 \ + -e PHP_FPM_PM_START_SERVERS=5 \ + -e PHP_FPM_PM_MIN_SPARE_SERVERS=2 \ + -e PHP_FPM_PM_MAX_SPARE_SERVERS=10 \ + -e PHP_OPCACHE_MEMORY=64 \ + -e PHP_OPCACHE_JIT_BUFFER_SIZE=32M \ kariricode/php-api-stack:latest ``` +### Performance Monitoring + +```bash +# Real-time resource monitoring +docker stats my-app --no-stream + +# OPcache hit rate (should be >95%) +docker exec my-app php -r " +\$stats = opcache_get_status()['opcache_statistics']; +echo 'Hit Rate: ' . round(\$stats['opcache_hit_rate'], 2) . '%' . PHP_EOL; +echo 'Misses: ' . \$stats['misses'] . PHP_EOL; +echo 'Hits: ' . \$stats['hits'] . PHP_EOL; +" + +# PHP-FPM pool status (if status page is enabled) +docker exec my-app curl -s http://localhost/fpm-status | jq + +# Request timing (add to Nginx config for detailed timing) +docker exec my-app tail -f /var/log/nginx/access.log | \ + awk '{print $NF}' | \ + awk 'BEGIN{sum=0;count=0} {sum+=$1;count++} END{print "Avg:", sum/count, "Count:", count}' +``` + +--- + ## 🐛 Troubleshooting ### Container Won't Start +**Symptoms**: Container exits immediately after starting + ```bash # Check logs docker logs my-app -# Common issues: -# - Port already in use → Change port: -p 8081:80 -# - Volume permissions → Fix with: chmod -R 755 app/ -# - Memory limits → Increase: --memory="2g" +# Common issues and solutions: + +# 1. Port already in use +# Error: "bind: address already in use" +# Solution: Use different port +docker run -d -p 8081:80 ... kariricode/php-api-stack:latest + +# 2. Volume mount permission issues +# Error: "Permission denied" +# Solution: Fix permissions +chmod -R 755 app/ +chown -R $(id -u):$(id -g) app/ + +# 3. Invalid environment variables +# Error: "Invalid PHP_MEMORY_LIMIT" +# Solution: Check format (e.g., "512M" not "512") + +# 4. Memory limit too low +# Solution: Increase Docker memory limit +docker run -d --memory="1g" ... kariricode/php-api-stack:latest ``` ### 502 Bad Gateway +**Symptoms**: Nginx returns 502 error + ```bash -# Check PHP-FPM -docker exec my-app supervisorctl status php-fpm +# Check if PHP-FPM is running +docker exec my-app ps aux | grep php-fpm + +# Check PHP-FPM socket docker exec my-app ls -la /var/run/php/php-fpm.sock +# Should show: srw-rw---- 1 www-data www-data + +# Test PHP-FPM configuration +docker exec my-app php-fpm -t + +# Check PHP-FPM logs +docker exec my-app tail -50 /var/log/php/fpm-error.log + +# Check Nginx error log +docker exec my-app tail -50 /var/log/nginx/error.log # Restart PHP-FPM -docker exec my-app supervisorctl restart php-fpm +docker exec my-app kill -USR2 $(cat /var/run/php/php-fpm.pid) -# Check logs -docker exec my-app tail -f /var/log/php/fpm-error.log +# If problem persists, restart container +docker restart my-app +``` + +### Redis Connection Issues + +**Symptoms**: Application can't connect to Redis + +#### Standalone Container Mode + +```bash +# Check if Redis is running +docker exec my-app ps aux | grep redis +# Should show: redis-server 0.0.0.0:6379 + +# Verify REDIS_HOST environment variable +docker exec my-app env | grep REDIS_HOST +# Should be: REDIS_HOST=127.0.0.1 + +# Test Redis connection +docker exec my-app redis-cli -h 127.0.0.1 ping +# Should return: PONG + +# Test with password (if configured) +docker exec my-app redis-cli -h 127.0.0.1 -a "your-password" ping + +# Check Redis logs +docker exec my-app tail -f /var/log/redis/redis.log +``` + +#### Docker Compose Mode + +```bash +# Check if redis service is running +docker-compose ps redis + +# Verify REDIS_HOST points to service name +docker-compose exec app env | grep REDIS_HOST +# Should be: REDIS_HOST=redis (service name) + +# Test connection from app container +docker-compose exec app redis-cli -h redis ping +# Should return: PONG + +# Check network connectivity +docker-compose exec app ping -c 3 redis ``` ### Slow Performance +**Symptoms**: Requests take longer than expected + ```bash -# Check OPcache +# 1. Check OPcache hit rate docker exec my-app php -r " \$status = opcache_get_status(); -echo 'Hit Rate: ' . \$status['opcache_statistics']['opcache_hit_rate'] . '%' . PHP_EOL; +\$hit_rate = \$status['opcache_statistics']['opcache_hit_rate']; +echo 'OPcache Hit Rate: ' . round(\$hit_rate, 2) . '%' . PHP_EOL; +if (\$hit_rate < 90) { + echo 'WARNING: Hit rate is low. Increase PHP_OPCACHE_MEMORY' . PHP_EOL; +} " -# If hit rate < 90%, increase memory: -# -e PHP_OPCACHE_MEMORY=512 +# 2. Check resource usage +docker stats my-app --no-stream -# Check resource usage -docker stats my-app +# 3. Check PHP-FPM pool +docker exec my-app cat /var/run/php/php-fpm.pid +docker exec my-app kill -USR1 $(cat /var/run/php/php-fpm.pid) # Get pool status -# Adjust PHP-FPM pool -# -e PHP_FPM_PM_MAX_CHILDREN=100 +# 4. Enable slow log analysis +docker exec my-app tail -f /var/log/php/fpm-slow.log + +# Solutions: +# - Increase OPcache memory: -e PHP_OPCACHE_MEMORY=512 +# - Increase FPM children: -e PHP_FPM_PM_MAX_CHILDREN=100 +# - Add more CPU/memory to container +# - Enable JIT: -e PHP_OPCACHE_JIT=tracing ``` -### Memory Issues +### High Memory Usage + +**Symptoms**: Container uses excessive memory ```bash # Check memory usage -docker exec my-app free -h -docker exec my-app php -r "echo memory_get_usage(true);" +docker stats my-app --no-stream -# Increase PHP memory limit -docker stop my-app -docker rm my-app -docker run -d \ - --name my-app \ - -e PHP_MEMORY_LIMIT=1G \ - ... +# Check PHP memory limit +docker exec my-app php -r "echo ini_get('memory_limit') . PHP_EOL;" + +# Check OPcache memory +docker exec my-app php -r " +\$status = opcache_get_status(); +\$memory = \$status['memory_usage']; +echo 'Used: ' . round(\$memory['used_memory'] / 1024 / 1024, 2) . ' MB' . PHP_EOL; +echo 'Free: ' . round(\$memory['free_memory'] / 1024 / 1024, 2) . ' MB' . PHP_EOL; +echo 'Wasted: ' . round(\$memory['wasted_memory'] / 1024 / 1024, 2) . ' MB' . PHP_EOL; +" + +# Solutions: +# - Reduce PHP memory limit: -e PHP_MEMORY_LIMIT=256M +# - Reduce FPM children: -e PHP_FPM_PM_MAX_CHILDREN=30 +# - Use dynamic PM mode: -e PHP_FPM_PM=dynamic +# - Set container memory limit: --memory="512m" ``` -### Redis Connection Failed +### Permission Issues + +**Symptoms**: Application can't write to directories ```bash -# Check Redis -docker exec my-app redis-cli ping -# Should return: PONG +# Check current ownership +docker exec my-app ls -la /var/www/html -# Restart Redis -docker exec my-app supervisorctl restart redis +# Fix ownership (application directories) +docker exec my-app chown -R www-data:www-data /var/www/html/var +docker exec my-app chown -R www-data:www-data /var/www/html/storage -# Check logs -docker exec my-app tail -f /var/log/redis/redis.log +# Fix permissions +docker exec my-app chmod -R 775 /var/www/html/var +docker exec my-app chmod -R 775 /var/www/html/storage + +# From host (if volume mounted) +sudo chown -R $(id -u):$(id -g) app/var +sudo chmod -R 775 app/var +``` + +### Nginx Configuration Issues + +```bash +# Test Nginx configuration +docker exec my-app nginx -t + +# Reload Nginx (after config changes) +docker exec my-app nginx -s reload + +# Check Nginx error log +docker exec my-app tail -100 /var/log/nginx/error.log + +# View current configuration +docker exec my-app cat /etc/nginx/nginx.conf +docker exec my-app cat /etc/nginx/conf.d/default.conf +``` + +### Debug Mode + +Enable debug mode for troubleshooting: + +```bash +docker run -d \ + --name debug-app \ + -p 8080:80 \ + -v $(pwd)/app:/var/www/html \ + -e APP_ENV=development \ + -e APP_DEBUG=true \ + -e PHP_DISPLAY_ERRORS=On \ + -e PHP_ERROR_REPORTING=E_ALL \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=1 \ + kariricode/php-api-stack:dev # Use dev image for Xdebug ``` +--- + ## 📚 Examples -### Example 1: Simple API +### Example 1: Simple REST API ```bash -# Create simple API +# Create simple API structure mkdir -p my-api/public cat > my-api/public/index.php << 'EOF' 'ok', +header('Access-Control-Allow-Origin: *'); + +$response = [ + 'status' => 'success', 'message' => 'API is running', - 'php_version' => PHP_VERSION -]); + 'timestamp' => time(), + 'php_version' => PHP_VERSION, + 'server' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown' +]; + +echo json_encode($response, JSON_PRETTY_PRINT); EOF # Run @@ -906,19 +1522,23 @@ docker run -d \ kariricode/php-api-stack:latest # Test -curl http://localhost:8080 +curl http://localhost:8080 | jq ``` ### Example 2: With Environment File ```bash # Create .env.production -cat > .env.production << EOF +cat > .env.production << 'EOF' APP_ENV=production APP_DEBUG=false PHP_MEMORY_LIMIT=512M +PHP_FPM_PM=static PHP_FPM_PM_MAX_CHILDREN=100 +PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 DATABASE_URL=mysql://user:pass@db:3306/mydb +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=secure-password EOF # Run with env file @@ -930,58 +1550,135 @@ docker run -d \ kariricode/php-api-stack:latest ``` -### Example 3: With Custom PHP Configuration +### Example 3: Multi-Stage Development ```bash -# Create custom php.ini -cat > custom-php.ini << EOF -memory_limit = 1G -max_execution_time = 300 -upload_max_filesize = 200M -post_max_size = 200M -EOF +# Development +docker run -d \ + --name dev-app \ + -p 8001:80 \ + -v $(pwd)/app:/var/www/html \ + -e APP_ENV=development \ + -e APP_DEBUG=true \ + -e PHP_DISPLAY_ERRORS=On \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=1 \ + kariricode/php-api-stack:dev -# Run with custom config +# Staging docker run -d \ - --name custom-app \ + --name staging-app \ + -p 8002:80 \ + -v $(pwd)/app:/var/www/html:ro \ + -e APP_ENV=staging \ + -e APP_DEBUG=false \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=1 \ + kariricode/php-api-stack:latest + +# Production +docker run -d \ + --name prod-app \ + -p 80:80 \ + -v $(pwd)/app:/var/www/html:ro \ + -e APP_ENV=production \ + -e APP_DEBUG=false \ + -e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + --restart unless-stopped \ + kariricode/php-api-stack:1.5.0 +``` + +### Example 4: Health Check Integration + +```bash +# With Docker healthcheck +docker run -d \ + --name monitored-app \ -p 8080:80 \ - -v $(pwd)/custom-php.ini:/usr/local/etc/php/conf.d/99-custom.ini:ro \ -v $(pwd)/app:/var/www/html \ + --health-cmd="curl -f http://localhost/health || exit 1" \ + --health-interval=30s \ + --health-timeout=3s \ + --health-retries=3 \ + --health-start-period=5s \ kariricode/php-api-stack:latest + +# Check health status +docker inspect --format='{{.State.Health.Status}}' monitored-app ``` +--- + ## 🎓 Best Practices ### Development -- ✅ Use `latest` tag for easier updates -- ✅ Mount code as read-write (`-v $(pwd)/app:/var/www/html`) -- ✅ Enable debug mode (`APP_DEBUG=true`) -- ✅ Use hot reload tools + +- ✅ Use `dev` tag for full debugging capabilities +- ✅ Mount code read-write: `-v $(pwd)/app:/var/www/html` +- ✅ Enable debug mode: `-e APP_DEBUG=true` +- ✅ Enable OPcache timestamp validation: `-e PHP_OPCACHE_VALIDATE_TIMESTAMPS=1` +- ✅ Use local network for multi-container setup +- ✅ Keep dependencies up to date with Composer ### Staging -- ✅ Use specific version tags (e.g., `1.2`) -- ✅ Mirror production config -- ✅ Test with production data volume -- ✅ Run load tests + +- ✅ Use specific version tags (e.g., `1.5`) +- ✅ Mirror production configuration +- ✅ Mount code read-only: `-v $(pwd)/app:/var/www/html:ro` +- ✅ Test with production-like data volumes +- ✅ Run load and performance tests +- ✅ Validate health checks work correctly ### Production -- ✅ Use `stable` or specific version tags -- ✅ Mount code as read-only (`:ro`) -- ✅ Set resource limits (`--memory`, `--cpus`) -- ✅ Use health checks -- ✅ Configure log rotation -- ✅ Set up monitoring -## 📞 Support +- ✅ **Always** pin specific versions: `kariricode/php-api-stack:1.5.0` +- ✅ **Always** mount code read-only: `:ro` +- ✅ Set resource limits: `--memory`, `--cpus` +- ✅ Disable OPcache validation: `-e PHP_OPCACHE_VALIDATE_TIMESTAMPS=0` +- ✅ Use static PM mode for predictable load: `-e PHP_FPM_PM=static` +- ✅ Enable health checks +- ✅ Configure proper logging and monitoring +- ✅ Use `--restart unless-stopped` or `always` +- ✅ Store logs externally: `-v $(pwd)/logs:/var/log` +- ✅ Never use `latest` tag in production +- ✅ Plan for zero-downtime deployments + +### Security + +- ✅ Never run as root (container uses `www-data` by default) +- ✅ Use read-only filesystem where possible +- ✅ Keep secrets in environment variables, not in code +- ✅ Regularly update to latest patch versions +- ✅ Use HTTPS/TLS in production (via reverse proxy) +- ✅ Implement rate limiting at the proxy level +- ✅ Monitor container for vulnerabilities + +--- + +## 📞 Support & Resources + +### Documentation + +- **Main README**: [GitHub README](https://github.com/kariricode/php-api-stack) +- **Docker Compose Guide**: [DOCKER_COMPOSE_GUIDE.md](DOCKER_COMPOSE_GUIDE.md) +- **Testing Guide**: [TESTING.md](TESTING.md) - For maintainers +- **Docker Hub Guide**: [DOCKER_HUB.md](DOCKER_HUB.md) - For publishers + +### Community -- **Documentation**: [GitHub README](https://github.com/kariricode/php-api-stack) - **Issues**: [Report bugs](https://github.com/kariricode/php-api-stack/issues) -- **Docker Hub**: [Image repository](https://hub.docker.com/r/kariricode/php-api-stack) - **Discussions**: [Ask questions](https://github.com/kariricode/php-api-stack/discussions) +- **Docker Hub**: [Image repository](https://hub.docker.com/r/kariricode/php-api-stack) + +### Quick Links + +- [PHP 8.4 Documentation](https://www.php.net/docs.php) +- [Nginx Documentation](https://nginx.org/en/docs/) +- [Redis Documentation](https://redis.io/documentation) +- [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/) +- [Symfony Deployment](https://symfony.com/doc/current/deployment.html) +- [Laravel Deployment](https://laravel.com/docs/deployment) --- -**More Info**: -- [Testing Guide](TESTING.md) - For maintainers -- [Docker Hub Guide](DOCKER_HUB.md) - For publishers -- [README](README.md) - Project overview \ No newline at end of file +**Version**: 1.5.0 +**Last Updated**: 2025-10-24 +**Made with 💚 by [KaririCode](https://kariricode.org)** \ No newline at end of file diff --git a/Makefile b/Makefile index 91aae03..f0df04e 100644 --- a/Makefile +++ b/Makefile @@ -54,277 +54,249 @@ ALPINE_VERSION?=3.21 COMPOSER_VERSION?=2.8.12 SYMFONY_CLI_VERSION?=5.15.1 +DEMO_MODE ?= false +HEALTH_CHECK_INSTALL ?= false + # Common build args block BUILD_ARGS := \ - --build-arg PHP_VERSION=$(PHP_VERSION) \ - --build-arg NGINX_VERSION=$(NGINX_VERSION) \ - --build-arg REDIS_VERSION=$(REDIS_VERSION) \ - --build-arg ALPINE_VERSION=$(ALPINE_VERSION) \ - --build-arg COMPOSER_VERSION=$(COMPOSER_VERSION) \ - --build-arg SYMFONY_CLI_VERSION=$(SYMFONY_CLI_VERSION) \ - --build-arg VERSION=$(VERSION) \ - --build-arg BUILD_DATE="$$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - --build-arg VCS_REF=$$(git rev-parse --short=12 HEAD 2>/dev/null || echo unknown) - -# toggles development-specific build args + --build-arg DEMO_MODE=$(DEMO_MODE) \ + --build-arg HEALTH_CHECK_INSTALL=$(HEALTH_CHECK_INSTALL) \ + --build-arg VERSION=$(VERSION) \ + --build-arg PHP_VERSION=$(PHP_VERSION) \ + --build-arg PHP_CORE_EXTENSIONS=$(PHP_CORE_EXTENSIONS) \ + --build-arg PHP_PECL_EXTENSIONS=$(PHP_PECL_EXTENSIONS) \ + --build-arg NGINX_VERSION=$(NGINX_VERSION) \ + --build-arg REDIS_VERSION=$(REDIS_VERSION) \ + --build-arg ALPINE_VERSION=$(ALPINE_VERSION) \ + --build-arg COMPOSER_VERSION=$(COMPOSER_VERSION) \ + --build-arg PHP_REDIS_VERSION=$(PHP_REDIS_VERSION) \ + --build-arg PHP_APCU_VERSION=$(PHP_APCU_VERSION) \ + --build-arg PHP_UUID_VERSION=$(PHP_UUID_VERSION) \ + --build-arg PHP_IMAGICK_VERSION=$(PHP_IMAGICK_VERSION) \ + --build-arg PHP_AMQP_VERSION=$(PHP_AMQP_VERSION) \ + --build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --build-arg VCS_REF=$(git rev-parse --short=12 HEAD 2>/dev/null || echo unknown) + +# Production build args +PROD_BUILD_ARGS := \ + --build-arg APP_ENV=production \ + --build-arg PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + --build-arg PHP_OPCACHE_MAX_ACCELERATED_FILES=20000 \ + --build-arg PHP_OPCACHE_ENABLE=1 \ + --build-arg PHP_OPCACHE_MEMORY_CONSUMPTION=256 \ + --build-arg XDEBUG_ENABLE=0 \ + --build-arg APP_DEBUG=false \ + --build-arg DEMO_MODE=false \ + --build-arg HEALTH_CHECK_INSTALL=false + + +# Development build args IMAGE_TAG ?= dev -XDEBUG_ENABLE ?= 0 -APP_ENV ?= development -APP_DEBUG ?= true -DEMO_MODE ?= true -HEALTH_CHECK_INSTALL ?= true +XDEBUG_ENABLE ?= 1 +XDEBUG_VERSION ?= 3.4.6 DEV_BUILD_ARGS := \ ---build-arg APP_ENV=$(APP_ENV) \ ---build-arg APP_DEBUG=$(APP_DEBUG) \ ---build-arg DEMO_MODE=$(DEMO_MODE) \ ---build-arg HEALTH_CHECK_INSTALL=$(HEALTH_CHECK_INSTALL) \ ---build-arg ENABLE_XDEBUG=$(XDEBUG_ENABLE) + --build-arg APP_ENV=development \ + --build-arg APP_DEBUG=true \ + --build-arg DEMO_MODE=true \ + --build-arg SYMFONY_CLI_VERSION=$(SYMFONY_CLI_VERSION) \ + --build-arg XDEBUG_VERSION=$(XDEBUG_VERSION) \ + --build-arg HEALTH_CHECK_INSTALL=true \ + --build-arg XDEBUG_ENABLE=$(XDEBUG_ENABLE) -# ============================================================= -# HELP -# ============================================================= .PHONY: help help: ## Show this help message - @echo "$(GREEN)╔══════════════════════════════════════════════════════╗$(NC)" - @echo "$(GREEN)║ PHP API Stack - Docker Build System ║$(NC)" - @echo "$(GREEN)╚══════════════════════════════════════════════════════╝$(NC)" + @echo "$(GREEN)╔═══════════════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ PHP API Stack - Docker Build System ║$(NC)" + @echo "$(GREEN)╚═══════════════════════════════════════════════════╝$(NC)" @echo "$(CYAN)Version: $(VERSION)$(NC)" + @echo "$(CYAN)Architecture: Base → Production | Dev$(NC)" @echo "" - @echo "$(MAGENTA)═══ BUILD TARGETS ═══$(NC)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "^#" | awk 'BEGIN {FS = ":.*?## "}; /^build/ || /^scan/ || /^lint/ {printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' + @echo "$(MAGENTA)━━━ BUILD TARGETS ━━━$(NC)" + @grep -hE '^build.*:.*##' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' @echo "" - @echo "$(MAGENTA)═══ TEST TARGETS ═══$(NC)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "^#" | awk 'BEGIN {FS = ":.*?## "}; /^test/ || /run-test/ || /stop-test/ || /logs-test/ || /shell-test/ {printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' + @echo "$(MAGENTA)━━━ TEST TARGETS ━━━$(NC)" + @grep -hE '^test.*:.*##' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' @echo "" - @echo "$(MAGENTA)═══ RUNTIME TARGETS ═══$(NC)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "^#" | awk 'BEGIN {FS = ":.*?## "}; /^run/ || /^stop$$/ || /^restart/ || /^logs$$/ || /^exec/ || /^shell$$/ {printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' + @echo "$(MAGENTA)━━━ RUNTIME TARGETS ━━━$(NC)" + @grep -hE '^(run|stop|restart|logs|shell).*:.*##' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' @echo "" - @echo "$(MAGENTA)═══ DOCKER COMPOSE TARGETS (from Makefile.compose) ═══$(NC)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "^#" | awk 'BEGIN {FS = ":.*?## "}; /^compose-/ {printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' + @echo "$(MAGENTA)━━━ DOCKER HUB TARGETS ━━━$(NC)" + @grep -hE '^(push|release|publish|tag|hub|docker-).*:.*##' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' @echo "" - @echo "$(MAGENTA)═══ UTILITY TARGETS ═══$(NC)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "^#" | awk 'BEGIN {FS = ":.*?## "}; /version/ || /bump/ || /push/ || /release/ || /clean/ || /info/ || /stats/ {printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' + @echo "$(MAGENTA)━━━ UTILITY TARGETS ━━━$(NC)" + @grep -hE '^(version|bump|clean|info|lint|scan).*:.*##' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf " $(YELLOW)%-22s$(NC) %s\n", $$1, $$2}' @echo "" @echo "$(BLUE)Quick Start Examples:$(NC)" - @echo " $(CYAN)make build$(NC) # Build production image" - @echo " $(CYAN)make build-dev$(NC) # Build dev image (Symfony CLI/Xdebug opt.)" - @echo " $(CYAN)make push$(NC) # Push production image to Docker Hub" - @echo " $(CYAN)make push-dev$(NC) # Push dev image to Docker Hub" - @echo " $(CYAN)make publish-dev$(NC) # Publish dev image to Docker Hub" - @echo " $(CYAN)make run-dev$(NC) # Run dev container" - @echo " $(CYAN)make build-test-image$(NC) # Build test image" - @echo " $(CYAN)make test-quick$(NC) # Quick test of built image" - @echo " $(CYAN)make test$(NC) # Run comprehensive tests" - @echo " $(CYAN)make run$(NC) # Run local container on port $(LOCAL_PORT)" - @echo " $(CYAN)make run-test$(NC) # Run test container on port $(TEST_PORT) with comprehensive health" - @echo " $(CYAN)make release$(NC) # Full release pipeline" - @echo " $(CYAN)make compose-help$(NC) # Show help for Docker Compose commands" - @echo " $(CYAN)make compose-up-all$(NC) # Start all Docker Compose services" - @echo " $(CYAN)make compose-down-v$(NC) # Stop all Docker Compose services" + @echo " $(CYAN)make build$(NC) # Build production image" + @echo " $(CYAN)make build-dev$(NC) # Build dev image with Xdebug" + @echo " $(CYAN)make test$(NC) # Run comprehensive tests" + @echo " $(CYAN)make release-production$(NC) # Full production release pipeline" + @echo " $(CYAN)make publish-dev$(NC) # Build + push dev image" + @echo " $(CYAN)make run$(NC) # Run production container" + @echo " $(CYAN)make run-dev$(NC) # Run dev container" + @echo "" + @echo "$(YELLOW)For Docker Hub commands:$(NC) $(CYAN)make hub-help$(NC)" + @echo "$(YELLOW)For Docker Hub commands:$(NC) $(CYAN)make compose-help$(NC)" # ============================================================= # BUILD TARGETS # ============================================================= .PHONY: build -build: ## Build the Docker image locally (production) +build: ## Build production image (target=production) @echo "$(GREEN)Building production Docker image...$(NC)" @if [ $(HAVE_BUILD_SCRIPT) -eq 1 ]; then \ $(BUILD_SCRIPT) --version=$(VERSION); \ else \ - docker build $(BUILD_ARGS) -t $(FULL_IMAGE):$(VERSION) . && \ - docker tag $(FULL_IMAGE):$(VERSION) $(FULL_IMAGE):latest && \ - echo "Tagged: $(FULL_IMAGE):latest"; \ + docker build --no-cache \ + $(BUILD_ARGS) \ + $(PROD_BUILD_ARGS) \ + --target production \ + -t $(FULL_IMAGE):$(VERSION) \ + -t $(FULL_IMAGE):latest \ + -t $(FULL_IMAGE):$(MAJOR_VERSION) \ + -t $(FULL_IMAGE):$(MINOR_VERSION) \ + $(BUILD_CONTEXT); \ fi - @echo "$(GREEN)✓ Production build complete!$(NC)" - -.PHONY: build-no-cache -build-no-cache: ## Build without using cache (production) - @echo "$(GREEN)Building Docker image (no cache)...$(NC)" - @if [ $(HAVE_BUILD_SCRIPT) -eq 1 ]; then \ - $(BUILD_SCRIPT) --no-cache --version=$(VERSION); \ - else \ - docker build --no-cache $(BUILD_ARGS) -t $(FULL_IMAGE):$(VERSION) . && \ - docker tag $(FULL_IMAGE):$(VERSION) $(FULL_IMAGE):latest; \ - fi - @echo "$(GREEN)✓ Build complete!$(NC)" + @echo "$(GREEN)OK: Production build complete!$(NC)" .PHONY: build-dev -build-dev: ## Build development image (target via BUILD_TARGET, tag via IMAGE_TAG) +build-dev: ## Build development image (target=dev) @echo "$(GREEN)Building development Docker image...$(NC)" - @docker build --no-cache $(BUILD_ARGS) $(DEV_BUILD_ARGS) \ - -t $(FULL_IMAGE):$(IMAGE_TAG) . - @echo "$(GREEN)✓ Dev image built as $(FULL_IMAGE):$(IMAGE_TAG)$(NC)" - -.PHONY: build-test-image -build-test-image: ## Build test image (production base) with comprehensive health check + @docker build \ + --no-cache \ + $(BUILD_ARGS) \ + $(DEV_BUILD_ARGS) \ + --target dev \ + --tag $(FULL_IMAGE):$(IMAGE_TAG) \ + --tag $(FULL_IMAGE):dev \ + . + @echo "$(GREEN)OK: Dev image built as $(FULL_IMAGE):$(IMAGE_TAG)$(NC)" + +.PHONY: build-base +build-base: ## Build base image (target=base) - for debugging only + @echo "$(GREEN)Building base Docker image...$(NC)" + @docker build \ + --no-cache \ + $(BUILD_ARGS) \ + --target base \ + --tag $(FULL_IMAGE):base \ + . + @echo "$(GREEN)OK: Base image built as $(FULL_IMAGE):base$(NC)" + +.PHONY: build-test +build-test: ## Build test image (production with health check) @echo "$(GREEN)Building test image with comprehensive health check...$(NC)" - @docker build $(BUILD_ARGS) --build-arg HEALTH_CHECK_TYPE=comprehensive \ - -t $(FULL_IMAGE):test . - @echo "$(GREEN)✓ Test image built: $(FULL_IMAGE):test$(NC)" + @docker build \ + $(BUILD_ARGS) \ + $(PROD_BUILD_ARGS) \ + --build-arg HEALTH_CHECK_INSTALL=true \ + --target production \ + -t $(FULL_IMAGE):test \ + . + @echo "$(GREEN)OK: Test image built: $(FULL_IMAGE):test$(NC)" + +.PHONY: build-all +build-all: build build-dev ## Build both production and dev images + @echo "$(GREEN)OK: All images built successfully!$(NC)" # ============================================================= -# PUSH & PUBLISH TARGETS +# PUSH & PUBLISH TARGETS (moved to Makefile.dockerhub) # ============================================================= -.PHONY: push -push: ## Push the image to Docker Hub - @echo "$(GREEN)Pushing to Docker Hub...$(NC)" - @echo "Pushing $(FULL_IMAGE):$(VERSION)..." - @docker push $(FULL_IMAGE):$(VERSION) - @docker push $(FULL_IMAGE):latest || true - @docker push $(FULL_IMAGE):$(MAJOR_VERSION) || true - @docker push $(FULL_IMAGE):$(MINOR_VERSION) || true - @echo "$(GREEN)✓ Push complete!$(NC)" - @echo "Available at: https://hub.docker.com/r/$(FULL_IMAGE)" - -.PHONY: push-dev -push-dev: ## Push dev image to Docker Hub (default IMAGE_TAG=dev) - @echo "$(GREEN)Pushing dev image to Docker Hub...$(NC)" - @docker push $(FULL_IMAGE):$(IMAGE_TAG) - @echo "$(GREEN)✓ Pushed: $(FULL_IMAGE):$(IMAGE_TAG)$(NC)" - -.PHONY: publish-dev -publish-dev: build-dev push-dev ## Build + Push dev image - @echo "$(GREEN)✓ Dev image published: $(FULL_IMAGE):$(IMAGE_TAG)$(NC)" +# Use: make push-production, make push-dev, make publish-all +# See: make hub-help for all Docker Hub commands # ============================================================= # RUNTIME TARGETS - RUN # ============================================================= .PHONY: run -run: ## Run local container for demo/testing - @echo "$(GREEN)Starting local container...$(NC)" +run: ## Run production container + @echo "$(GREEN)Starting production container...$(NC)" @docker stop $(LOCAL_CONTAINER) >/dev/null 2>&1 || true @docker rm $(LOCAL_CONTAINER) >/dev/null 2>&1 || true @docker run -d \ --name $(LOCAL_CONTAINER) \ -p $(LOCAL_PORT):80 \ - -e DEMO_MODE=true \ - --env-file .env \ + --env-file $(ENV_FILE) \ + -e APP_ENV=production \ -v $(PWD)/logs:/var/log \ $(FULL_IMAGE):latest - @echo "$(GREEN)✓ Container running at http://localhost:$(LOCAL_PORT)$(NC)" + @echo "$(GREEN)OK: Production container running at http://localhost:$(LOCAL_PORT)$(NC)" @echo "$(CYAN)Container name:$(NC) $(LOCAL_CONTAINER)" - @echo "\n$(YELLOW)⏳ Waiting for services to start...$(NC)"; sleep 5 - @echo "\n$(CYAN)Testing demo page...$(NC)" - @if curl -s http://localhost:$(LOCAL_PORT) | grep -q "PHP API Stack"; then \ - echo "$(GREEN)✓ Demo page is live!$(NC)"; \ - echo "$(CYAN)Visit:$(NC) http://localhost:$(LOCAL_PORT)"; \ - else \ - echo "$(YELLOW)⚠ Page loaded but demo not detected$(NC)"; \ - fi - @echo "\n$(BLUE)Useful commands:$(NC)" - @echo " $(CYAN)make stop$(NC) # Stop container" - @echo " $(CYAN)make logs$(NC) # View logs" - @echo " $(CYAN)make shell$(NC) # Access shell" - @echo " $(CYAN)make run-with-app$(NC) # Run with app mounted" - -.PHONY: run-with-app -run-with-app: ## Run local container with mounted application - @echo "$(GREEN)Starting local container with application mount...$(NC)" - @docker stop $(LOCAL_CONTAINER) >/dev/null 2>&1 || true - @docker rm $(LOCAL_CONTAINER) >/dev/null 2>&1 || true - @if [ ! -d "$(PWD)/app" ]; then \ - echo "$(YELLOW)Creating app directory...$(NC)"; \ - mkdir -p $(PWD)/app/public; \ - fi - @docker run -d \ - --name $(LOCAL_CONTAINER) \ - -p $(LOCAL_PORT):80 \ - -e DEMO_MODE=true \ - --env-file .env \ - -v $(PWD)/app:/var/www/html \ - -v $(PWD)/logs:/var/log \ - $(FULL_IMAGE):latest - @echo "$(GREEN)✓ Container running at http://localhost:$(LOCAL_PORT)$(NC)" - @echo "$(CYAN)Application mounted from:$(NC) $(PWD)/app" - @echo "\n$(BLUE)Next steps:$(NC)" - @echo " 1. Create your $(CYAN)app/public/index.php$(NC)" - @echo " 2. Run $(CYAN)docker exec $(LOCAL_CONTAINER) nginx -s reload$(NC)" - @echo " 3. Visit http://localhost:$(LOCAL_PORT)" .PHONY: run-dev -run-dev: ## Run dev container (port 8001, or override: make run-dev DEV_PORT=9000) +run-dev: ## Run dev container with Xdebug @echo "$(GREEN)Starting dev container...$(NC)" @docker stop $(DEV_CONTAINER) >/dev/null 2>&1 || true @docker rm $(DEV_CONTAINER) >/dev/null 2>&1 || true @if docker ps --format '{{.Ports}}' | grep -q '$(DEV_PORT)->'; then \ - echo "$(RED)✗ Port $(DEV_PORT) is already in use!$(NC)"; \ + echo "$(RED)X Port $(DEV_PORT) is already in use!$(NC)"; \ echo "$(YELLOW)Try another port:$(NC) make run-dev DEV_PORT=9000"; \ exit 1; \ fi @docker run -d \ --name $(DEV_CONTAINER) \ -p $(DEV_PORT):80 \ + -p 9003:9003 \ --env-file $(ENV_FILE) \ + -e APP_ENV=development \ + -e XDEBUG_ENABLE=1 \ -v $(PWD)/logs:/var/log \ $(FULL_IMAGE):$(IMAGE_TAG) - @echo "$(GREEN)✓ Dev container running at http://localhost:$(DEV_PORT)$(NC)" - + @echo "$(GREEN)OK: Dev container running at http://localhost:$(DEV_PORT)$(NC)" + @echo "$(CYAN)Xdebug enabled on port 9003$(NC)" .PHONY: run-test -run-test: build-test-image ## Run test container (comprehensive health) +run-test: build-test ## Run test container @echo "$(GREEN)Starting test container...$(NC)" @docker stop $(TEST_CONTAINER) >/dev/null 2>&1 || true @docker rm $(TEST_CONTAINER) >/dev/null 2>&1 || true @docker run -d \ --name $(TEST_CONTAINER) \ -p $(TEST_PORT):80 \ - --env-file .env \ + --env-file $(ENV_FILE) \ -v $(PWD)/logs:/var/log \ $(FULL_IMAGE):test - @echo "\n$(GREEN)✓ Test container running!$(NC)" - @echo "$(CYAN)════════════════════════════════════════$(NC)" - @echo "$(CYAN)Container:$(NC) $(TEST_CONTAINER)" - @echo "$(CYAN)URL:$(NC) http://localhost:$(TEST_PORT)" - @echo "$(CYAN)Health Check:$(NC) http://localhost:$(TEST_PORT)/health" - @echo "$(CYAN)════════════════════════════════════════$(NC)" - @echo "\n$(YELLOW)⏳ Waiting for services to start...$(NC)"; sleep 5 - @echo "\n$(CYAN)Testing comprehensive health endpoint...$(NC)" - @curl -s http://localhost:$(TEST_PORT)/health || true + @echo "$(GREEN)OK: Test container running at http://localhost:$(TEST_PORT)/health$(NC)" # ============================================================= # RUNTIME TARGETS - STOP & RESTART # ============================================================= .PHONY: stop -stop: ## Stop and remove local container - @echo "$(YELLOW)Stopping local container...$(NC)" +stop: ## Stop production container + @echo "$(YELLOW)Stopping production container...$(NC)" @docker stop $(LOCAL_CONTAINER) >/dev/null 2>&1 || true @docker rm $(LOCAL_CONTAINER) >/dev/null 2>&1 || true - @echo "$(GREEN)✓ Container stopped$(NC)" + @echo "$(GREEN)OK: Container stopped$(NC)" .PHONY: stop-dev -stop-dev: ## Stop and remove dev container +stop-dev: ## Stop dev container @echo "$(YELLOW)Stopping dev container...$(NC)" @docker stop $(DEV_CONTAINER) >/dev/null 2>&1 || true @docker rm $(DEV_CONTAINER) >/dev/null 2>&1 || true - @echo "$(GREEN)✓ Dev container stopped$(NC)" + @echo "$(GREEN)OK: Dev container stopped$(NC)" .PHONY: stop-test -stop-test: ## Stop and remove test container +stop-test: ## Stop test container @echo "$(YELLOW)Stopping test container...$(NC)" @docker stop $(TEST_CONTAINER) >/dev/null 2>&1 || true @docker rm $(TEST_CONTAINER) >/dev/null 2>&1 || true - @echo "$(GREEN)✓ Test container stopped$(NC)" + @echo "$(GREEN)OK: Test container stopped$(NC)" .PHONY: restart -restart: stop run ## Restart the local container +restart: stop run ## Restart production container .PHONY: restart-dev -restart-dev: stop-dev run-dev ## Restart the dev container - -.PHONY: restart-test -restart-test: stop-test run-test ## Restart the test container +restart-dev: stop-dev run-dev ## Restart dev container # ============================================================= # RUNTIME TARGETS - LOGS & SHELL # ============================================================= .PHONY: logs -logs: ## Show logs from local container +logs: ## Show logs from production container @if [ -z "$$(docker ps -a -q -f name=$(LOCAL_CONTAINER))" ]; then \ - echo "$(RED)No container named $(LOCAL_CONTAINER) found!$(NC)"; \ - echo "Run '$(CYAN)make run$(NC)' first."; \ + echo "$(RED)Container $(LOCAL_CONTAINER) not found!$(NC)"; \ else \ - echo "$(GREEN)Showing logs for $(LOCAL_CONTAINER)...$(NC)"; \ docker logs -f $(LOCAL_CONTAINER); \ fi @@ -332,57 +304,31 @@ logs: ## Show logs from local container logs-dev: ## Show logs from dev container @if [ -z "$$(docker ps -a -q -f name=$(DEV_CONTAINER))" ]; then \ echo "$(RED)Dev container not found!$(NC)"; \ - echo "Run '$(CYAN)make run-dev$(NC)' first."; \ else \ - echo "$(GREEN)Showing logs for $(DEV_CONTAINER)...$(NC)"; \ docker logs -f $(DEV_CONTAINER); \ fi -.PHONY: logs-test -logs-test: ## Show logs from test container - @if [ -z "$$(docker ps -a -q -f name=$(TEST_CONTAINER))" ]; then \ - echo "$(RED)Test container not found!$(NC)"; \ - echo "Run '$(CYAN)make run-test$(NC)' first."; \ - else \ - echo "$(GREEN)Showing logs for $(TEST_CONTAINER)...$(NC)"; \ - docker logs -f $(TEST_CONTAINER); \ - fi - .PHONY: shell -shell: exec ## Alias for exec - -.PHONY: exec -exec: ## Execute bash in the running local container +shell: ## Access shell in production container @if [ -z "$$(docker ps -q -f name=$(LOCAL_CONTAINER))" ]; then \ - echo "$(RED)No running container found!$(NC)"; \ - echo "Run '$(CYAN)make run$(NC)' first."; \ + echo "$(RED)Container not running!$(NC)"; \ else \ docker exec -it $(LOCAL_CONTAINER) bash; \ fi .PHONY: shell-dev -shell-dev: ## Execute bash in dev container +shell-dev: ## Access shell in dev container @if [ -z "$$(docker ps -q -f name=$(DEV_CONTAINER))" ]; then \ echo "$(RED)Dev container not running!$(NC)"; \ - echo "Run '$(CYAN)make run-dev$(NC)' first."; \ else \ docker exec -it $(DEV_CONTAINER) bash; \ fi -.PHONY: shell-test -shell-test: ## Execute bash in test container - @if [ -z "$$(docker ps -q -f name=$(TEST_CONTAINER))" ]; then \ - echo "$(RED)Test container not running!$(NC)"; \ - echo "Run '$(CYAN)make run-test$(NC)' first."; \ - else \ - docker exec -it $(TEST_CONTAINER) bash; \ - fi - # ============================================================= # TEST TARGETS # ============================================================= .PHONY: test-quick -test-quick: ## Quick test of built image (versions only) +test-quick: ## Quick version check @echo "$(GREEN)Running quick version tests...$(NC)" @echo "\n$(CYAN)Testing PHP:$(NC)" @docker run --rm --entrypoint php $(FULL_IMAGE):latest -v | head -2 @@ -392,144 +338,16 @@ test-quick: ## Quick test of built image (versions only) @docker run --rm --entrypoint redis-server $(FULL_IMAGE):latest --version @echo "\n$(CYAN)Testing Composer:$(NC)" @docker run --rm --entrypoint composer $(FULL_IMAGE):latest --version --no-ansi | head -1 - @echo "\n$(GREEN)✓ All components verified!$(NC)" + @echo "\n$(GREEN)OK: All components verified!$(NC)" .PHONY: test -test: ## Run comprehensive tests on the production image +test: ## Run comprehensive tests @echo "$(GREEN)Running comprehensive tests...$(NC)" - @echo "\n$(YELLOW)[1/5] Testing component versions...$(NC)" @$(MAKE) test-quick --no-print-directory - @echo "\n$(YELLOW)[2/5] Testing configuration processing...$(NC)" - @docker run --rm $(FULL_IMAGE):latest /usr/local/bin/process-configs - @echo "\n$(YELLOW)[3/5] Testing Nginx configuration...$(NC)" + @echo "\n$(YELLOW)Testing production configuration...$(NC)" @docker run --rm --entrypoint nginx $(FULL_IMAGE):latest -t - @echo "\n$(YELLOW)[4/5] Testing PHP-FPM configuration...$(NC)" @docker run --rm --entrypoint php-fpm $(FULL_IMAGE):latest -t - @echo "\n$(YELLOW)[5/5] Testing health endpoint...$(NC)" - @docker run -d --name $(CI_TEST_CONTAINER) -p $(TEST_PORT):80 $(FULL_IMAGE):latest >/dev/null - @echo "Waiting for container to start on port $(TEST_PORT)..." - @tries=0; while ! curl -s -f -o /dev/null http://localhost:$(TEST_PORT)/health; do \ - sleep 2; \ - tries=$$(($$tries + 1)); \ - if [ $$tries -ge 15 ]; then \ - echo "$(RED)✗ Health check failed: Container did not become healthy in time.$(NC)"; \ - docker logs $(CI_TEST_CONTAINER); \ - docker stop $(CI_TEST_CONTAINER) >/dev/null; \ - docker rm $(CI_TEST_CONTAINER) >/dev/null; \ - exit 1; \ - fi; \ - done - @echo "$(GREEN)✓ Health check passed$(NC)" - @docker stop $(CI_TEST_CONTAINER) >/dev/null - @docker rm $(CI_TEST_CONTAINER) >/dev/null - @echo "\n$(GREEN)✓ All tests complete!$(NC)" - -.PHONY: test-health -test-health: ## Test comprehensive health check endpoint - @if [ -z "$$(docker ps -q -f name=$(TEST_CONTAINER))" ]; then \ - echo "$(RED)Test container not running!$(NC)"; \ - echo "Run '$(CYAN)make run-test$(NC)' first."; \ - exit 1; \ - fi - @curl -s http://localhost:$(TEST_PORT)/health.php | jq '.' || curl -s http://localhost:$(TEST_PORT)/health - -.PHONY: test-health-status -test-health-status: ## Show health check status summary - @if [ -z "$$(docker ps -q -f name=$(TEST_CONTAINER))" ]; then \ - echo "$(RED)Test container not running!$(NC)"; exit 1; \ - fi - @echo "$(GREEN)Health Check Status Summary$(NC)" - @echo "$(CYAN)════════════════════════════════════════$(NC)" - @curl -s http://localhost:$(TEST_PORT)/health.php | jq -r '"Overall: \(.status | ascii_upcase)"' || true - @curl -s http://localhost:$(TEST_PORT)/health.php | jq -r '"Duration: \(.duration_ms // "n/a")ms"' || true - @echo ""; echo "$(CYAN)Component Status:$(NC)"; \ - curl -s http://localhost:$(TEST_PORT)/health.php | jq -r '.checks | to_entries[] | " \(.key | ascii_upcase): \(.value.status)"' || true - -.PHONY: test-health-watch -test-health-watch: ## Watch health check status (updates every 5s) - @if [ -z "$$(docker ps -q -f name=$(TEST_CONTAINER))" ]; then \ - echo "$(RED)Test container not running!$(NC)"; \ - echo "Run '$(CYAN)make run-test$(NC)' first."; \ - exit 1; \ - fi - @echo "$(GREEN)Watching health status (Ctrl+C to stop)...$(NC)"; echo "" - @watch -n 5 "curl -s http://localhost:$(TEST_PORT)/health.php | jq '{overall: .status, duration_ms: .duration_ms, checks: (.checks // {})}'" - -.PHONY: test-structure -test-structure: ## Test container structure and files - @echo "$(GREEN)Testing container structure...$(NC)" - @docker run --rm $(FULL_IMAGE):latest ls -la /var/www/html/public/ || true - @docker run --rm $(FULL_IMAGE):latest ls -la /etc/nginx/ || true - @docker run --rm $(FULL_IMAGE):latest ls -la /usr/local/etc/php/ || true - -# ============================================================= -# VALIDATION TARGETS -# ============================================================= -.PHONY: scan -scan: ## Scan the image for vulnerabilities using Trivy - @echo "$(GREEN)Scanning for vulnerabilities...$(NC)" - @if command -v trivy >/dev/null 2>&1; then \ - trivy image --severity HIGH,CRITICAL $(FULL_IMAGE):latest; \ - else \ - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest image --severity HIGH,CRITICAL $(FULL_IMAGE):latest; \ - fi - -.PHONY: lint -lint: ## Lint the Dockerfile with hadolint - @echo "$(GREEN)Linting Dockerfile...$(NC)" - @if command -v hadolint >/dev/null 2>&1; then \ - hadolint Dockerfile || true; \ - else \ - docker run --rm -i hadolint/hadolint < Dockerfile || true; \ - fi - -# ============================================================= -# RELEASE TARGETS -# ============================================================= -.PHONY: tag-latest -tag-latest: ## Tag current version as latest/major/minor - @docker tag $(FULL_IMAGE):$(VERSION) $(FULL_IMAGE):latest || true - @docker tag $(FULL_IMAGE):$(VERSION) $(FULL_IMAGE):$(MAJOR_VERSION) || true - @docker tag $(FULL_IMAGE):$(VERSION) $(FULL_IMAGE):$(MINOR_VERSION) || true - @echo "$(GREEN)✓ Tags created: latest, $(MAJOR_VERSION), $(MINOR_VERSION)$(NC)" - -.PHONY: release -release: lint build test scan tag-latest push ## Full release pipeline - @echo "$(GREEN)════════════════════════════════════════$(NC)" - @echo "$(GREEN)✅ Release $(VERSION) complete!$(NC)" - @echo "$(GREEN)════════════════════════════════════════$(NC)" - -# ============================================================= -# VERSION MANAGEMENT -# ============================================================= -.PHONY: version -version: ## Display current version - @echo "$(CYAN)Current version:$(NC) $(VERSION)" - @echo "$(CYAN)Docker image:$(NC) $(FULL_IMAGE):$(VERSION)" - -.PHONY: bump-patch -bump-patch: ## Bump patch version (x.x.X) - @VERSION=$$(echo $(VERSION) | awk -F. '{print $$1"."$$2"."$$3+1}'); \ - echo $$VERSION > VERSION; \ - echo "$(GREEN)✓ Version bumped to $$VERSION$(NC)"; \ - git add VERSION 2>/dev/null || true; \ - git commit -m "chore: bump version to $$VERSION" 2>/dev/null || true - -.PHONY: bump-minor -bump-minor: ## Bump minor version (x.X.x) - @VERSION=$$(echo $(VERSION) | awk -F. '{print $$1"."$$2+1".0"}'); \ - echo $$VERSION > VERSION; \ - echo "$(GREEN)✓ Version bumped to $$VERSION$(NC)"; \ - git add VERSION 2>/dev/null || true; \ - git commit -m "chore: bump version to $$VERSION" 2>/dev/null || true - -.PHONY: bump-major -bump-major: ## Bump major version (X.x.x) - @VERSION=$$(echo $(VERSION) | awk -F. '{print $$1+1".0.0"}'); \ - echo $$VERSION > VERSION; \ - echo "$(GREEN)✓ Version bumped to $$VERSION$(NC)"; \ - git add VERSION 2>/dev/null || true; \ - git commit -m "chore: bump version to $$VERSION" 2>/dev/null || true + @echo "$(GREEN)OK: All tests passed!$(NC)" # ============================================================= # CLEANUP TARGETS @@ -537,65 +355,64 @@ bump-major: ## Bump major version (X.x.x) .PHONY: clean clean: ## Remove local images and containers @echo "$(YELLOW)Cleaning up...$(NC)" - @docker stop $(LOCAL_CONTAINER) $(TEST_CONTAINER) >/dev/null 2>&1 || true - @docker rm $(LOCAL_CONTAINER) $(TEST_CONTAINER) >/dev/null 2>&1 || true + @docker stop $(LOCAL_CONTAINER) $(DEV_CONTAINER) $(TEST_CONTAINER) >/dev/null 2>&1 || true + @docker rm $(LOCAL_CONTAINER) $(DEV_CONTAINER) $(TEST_CONTAINER) >/dev/null 2>&1 || true @docker rmi $(FULL_IMAGE):$(VERSION) >/dev/null 2>&1 || true @docker rmi $(FULL_IMAGE):latest >/dev/null 2>&1 || true + @docker rmi $(FULL_IMAGE):dev >/dev/null 2>&1 || true @docker rmi $(FULL_IMAGE):test >/dev/null 2>&1 || true - @echo "$(GREEN)✓ Cleanup complete$(NC)" - -.PHONY: clean-all -clean-all: clean ## Deep clean including volumes and build cache - @echo "$(YELLOW)Deep cleaning...$(NC)" - @docker volume prune -f 2>/dev/null || true - @docker builder prune -f 2>/dev/null || true - @echo "$(GREEN)✓ Deep cleanup complete$(NC)" + @echo "$(GREEN)OK: Cleanup complete$(NC)" # ============================================================= # INFORMATION TARGETS # ============================================================= .PHONY: info -info: ## Show image information - @echo "$(CYAN)════════════════════════════════════════$(NC)" - @echo "$(GREEN)PHP API Stack - Image Information$(NC)" - @echo "$(CYAN)════════════════════════════════════════$(NC)" - @echo " $(CYAN)Hub User:$(NC) $(DOCKER_HUB_USER)" - @echo " $(CYAN)Image:$(NC) $(IMAGE_NAME)" - @echo " $(CYAN)Version:$(NC) $(VERSION)" - @echo " $(CYAN)Full path:$(NC) $(FULL_IMAGE):$(VERSION)" - @echo "\n$(GREEN)Container names:$(NC)" - @echo " $(CYAN)Local:$(NC) $(LOCAL_CONTAINER) (Port: $(LOCAL_PORT))" - @echo " $(CYAN)Test:$(NC) $(TEST_CONTAINER) (Port: $(TEST_PORT))" - @echo "\n$(GREEN)Available tags:$(NC)" - @echo " - $(FULL_IMAGE):$(VERSION)" - @echo " - $(FULL_IMAGE):$(MAJOR_VERSION)" - @echo " - $(FULL_IMAGE):$(MINOR_VERSION)" - @echo " - $(FULL_IMAGE):latest" - @echo " - $(FULL_IMAGE):test" +info: ## Show build information + @echo "$(CYAN)╔══════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ PHP API Stack - Build Information ║$(NC)" + @echo "$(CYAN)╚══════════════════════════════════════════╝$(NC)" @echo "" - @if docker images $(FULL_IMAGE) --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" | grep -q $(IMAGE_NAME); then \ - echo "$(GREEN)Local images:$(NC)"; \ - docker images $(FULL_IMAGE) --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"; \ - else \ - echo "$(YELLOW)No local images found. Run 'make build' to create one.$(NC)"; \ - fi - -.PHONY: stats -stats: ## Show container resource usage - @if [ -z "$$(docker ps -q -f name=$(LOCAL_CONTAINER))" ]; then \ - echo "$(YELLOW)Local container not running. Checking test container...$(NC)"; \ - if [ -z "$$(docker ps -q -f name=$(TEST_CONTAINER))" ]; then \ - echo "$(RED)No running containers found!$(NC)"; \ - else \ - echo "$(GREEN)Test container resource usage:$(NC)"; \ - docker stats $(TEST_CONTAINER) --no-stream; \ - fi; \ - else \ - echo "$(GREEN)Local container resource usage:$(NC)"; \ - docker stats $(LOCAL_CONTAINER) --no-stream; \ - fi - + @echo "$(CYAN)Architecture:$(NC)" + @echo " Base -> Production | Dev" + @echo "" + @echo "$(CYAN)Available Stages:$(NC)" + @echo " • base (foundation layer)" + @echo " • production (optimized for production)" + @echo " • dev (with Xdebug + Symfony CLI)" + @echo "" + @echo "$(CYAN)Version:$(NC) $(VERSION)" + @echo "$(CYAN)Full Image:$(NC) $(FULL_IMAGE)" + @echo "$(CYAN)PHP Version:$(NC) $(PHP_VERSION)" + @echo "$(CYAN)Nginx:$(NC) $(NGINX_VERSION)" + @echo "$(CYAN)Redis:$(NC) $(REDIS_VERSION)" + + +.PHONY: info-versions +info-versions: ## Show detailed version information + @echo "$(CYAN)╔═══════════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ Component Versions - PHP API Stack ║$(NC)" + @echo "$(CYAN)╚═══════════════════════════════════════════════╝$(NC)" + @echo "" + @echo "$(CYAN)Base Components:$(NC)" + @echo " PHP: $(PHP_VERSION)" + @echo " Nginx: $(NGINX_VERSION)" + @echo " Redis Server: $(REDIS_VERSION)" + @echo " Alpine: $(ALPINE_VERSION)" + @echo " Composer: $(COMPOSER_VERSION)" + @echo "" + @echo "$(CYAN)PECL Extensions:$(NC)" + @echo " redis: $(PHP_REDIS_VERSION)" + @echo " apcu: $(PHP_APCU_VERSION)" + @echo " uuid: $(PHP_UUID_VERSION)" + @echo " imagick: $(PHP_IMAGICK_VERSION)" + @echo " amqp: $(PHP_AMQP_VERSION)" + @echo "" + @echo "$(CYAN)Dev Tools:$(NC)" + @echo " Symfony CLI: $(SYMFONY_CLI_VERSION)" + @echo " Xdebug: $(XDEBUG_VERSION)" + # ============================================================= -# DOCKER-COMPOSE TARGETS (optional include) +# INCLUDE ADDITIONAL MAKEFILES # ============================================================= +-include Makefile.dockerhub -include Makefile.compose \ No newline at end of file diff --git a/Makefile.dockerhub b/Makefile.dockerhub new file mode 100644 index 0000000..1ec3840 --- /dev/null +++ b/Makefile.dockerhub @@ -0,0 +1,397 @@ +# Makefile.dockerhub - Docker Hub Publication Commands +# Include in main Makefile with: -include Makefile.dockerhub + +# ============================================================= +# DOCKER HUB AUTHENTICATION +# ============================================================= +.PHONY: docker-login +docker-login: ## Login to Docker Hub + @echo "$(GREEN)Logging in to Docker Hub...$(NC)" + @if docker info 2>/dev/null | grep -q "Username"; then \ + echo "$(CYAN)Already logged in$(NC)"; \ + else \ + docker login -u $(DOCKER_HUB_USER); \ + fi + +.PHONY: docker-logout +docker-logout: ## Logout from Docker Hub + @echo "$(YELLOW)Logging out from Docker Hub...$(NC)" + @docker logout + @echo "$(GREEN)OK: Logged out$(NC)" + +# ============================================================= +# VERSION MANAGEMENT +# ============================================================= +.PHONY: version +version: ## Display current version + @echo "$(CYAN)Current version:$(NC) $(VERSION)" + @echo "$(CYAN)Docker image:$(NC) $(FULL_IMAGE):$(VERSION)" + @echo "$(CYAN)Major:$(NC) $(MAJOR_VERSION)" + @echo "$(CYAN)Minor:$(NC) $(MINOR_VERSION)" + +.PHONY: bump-patch +bump-patch: ## Bump patch version (x.x.X) + @NEW_VERSION=$$(echo $(VERSION) | awk -F. '{print $$1"."$$2"."$$3+1}'); \ + echo $$NEW_VERSION > VERSION; \ + echo "$(GREEN)OK: Version bumped to $$NEW_VERSION$(NC)"; \ + git add VERSION 2>/dev/null || true; \ + git commit -m "chore: bump version to $$NEW_VERSION" 2>/dev/null || true + +.PHONY: bump-minor +bump-minor: ## Bump minor version (x.X.0) + @NEW_VERSION=$$(echo $(VERSION) | awk -F. '{print $$1"."$$2+1".0"}'); \ + echo $$NEW_VERSION > VERSION; \ + echo "$(GREEN)OK: Version bumped to $$NEW_VERSION$(NC)"; \ + git add VERSION 2>/dev/null || true; \ + git commit -m "chore: bump version to $$NEW_VERSION" 2>/dev/null || true + +.PHONY: bump-major +bump-major: ## Bump major version (X.0.0) + @NEW_VERSION=$$(echo $(VERSION) | awk -F. '{print $$1+1".0.0"}'); \ + echo $$NEW_VERSION > VERSION; \ + echo "$(GREEN)OK: Version bumped to $$NEW_VERSION$(NC)"; \ + git add VERSION 2>/dev/null || true; \ + git commit -m "chore: bump version to $$NEW_VERSION" 2>/dev/null || true + +# ============================================================= +# TAGGING +# ============================================================= +.PHONY: tag-production +tag-production: ## Tag production image with version tags + @echo "$(GREEN)Tagging production image...$(NC)" + @docker tag $(FULL_IMAGE):latest $(FULL_IMAGE):$(VERSION) + @docker tag $(FULL_IMAGE):latest $(FULL_IMAGE):$(MAJOR_VERSION) + @docker tag $(FULL_IMAGE):latest $(FULL_IMAGE):$(MINOR_VERSION) + @echo "$(GREEN)OK: Tags created:$(NC)" + @echo " • $(VERSION)" + @echo " • $(MAJOR_VERSION)" + @echo " • $(MINOR_VERSION)" + @echo " • latest" + +.PHONY: tag-dev +tag-dev: ## Tag dev image + @echo "$(GREEN)Tagging dev image...$(NC)" + @echo "$(GREEN)OK: Tag created:$(NC)" + @echo " • dev" + +.PHONY: tag-all +tag-all: tag-production tag-dev ## Tag all images + +# ============================================================= +# PUSH TO DOCKER HUB +# ============================================================= +.PHONY: push-production +push-production: docker-login ## Push production image to Docker Hub + @echo "$(GREEN)Pushing production images to Docker Hub...$(NC)" + @echo "" + @echo "$(CYAN)Pushing $(FULL_IMAGE):$(VERSION)...$(NC)" + @docker push $(FULL_IMAGE):$(VERSION) + @echo "$(CYAN)Pushing $(FULL_IMAGE):latest...$(NC)" + @docker push $(FULL_IMAGE):latest + @echo "$(CYAN)Pushing $(FULL_IMAGE):$(MAJOR_VERSION)...$(NC)" + @docker push $(FULL_IMAGE):$(MAJOR_VERSION) + @echo "$(CYAN)Pushing $(FULL_IMAGE):$(MINOR_VERSION)...$(NC)" + @docker push $(FULL_IMAGE):$(MINOR_VERSION) + @echo "" + @echo "$(GREEN)OK: Production images pushed successfully!$(NC)" + @echo "$(CYAN)Available at: https://hub.docker.com/r/$(FULL_IMAGE)$(NC)" + +.PHONY: push-dev +push-dev: docker-login ## Push dev image to Docker Hub + @echo "$(GREEN)Pushing dev image to Docker Hub...$(NC)" + @echo "" + @echo "$(CYAN)Pushing $(FULL_IMAGE):dev...$(NC)" + @docker push $(FULL_IMAGE):dev + @echo "" + @echo "$(GREEN)OK: Dev image pushed successfully!$(NC)" + +.PHONY: push-base +push-base: docker-login ## Push base image to Docker Hub + @echo "$(GREEN)Pushing base image to Docker Hub...$(NC)" + @docker push $(FULL_IMAGE):base + @echo "$(GREEN)OK: Base image pushed!$(NC)" + +.PHONY: push +push: push-production ## Alias for push-production + +.PHONY: push-all +push-all: push-production push-dev ## Push all images (production + dev) + @echo "" + @echo "$(GREEN)==========================================$(NC)" + @echo "$(GREEN)OK: All images pushed successfully!$(NC)" + @echo "$(GREEN)==========================================$(NC)" + +# ============================================================= +# VALIDATION & SECURITY +# ============================================================= +.PHONY: lint +lint: ## Lint Dockerfile with hadolint + @echo "$(GREEN)Linting Dockerfile...$(NC)" + @if command -v hadolint >/dev/null 2>&1; then \ + hadolint Dockerfile || true; \ + else \ + docker run --rm -i hadolint/hadolint < Dockerfile || true; \ + fi + @echo "$(GREEN)OK: Lint complete$(NC)" + +.PHONY: scan +scan: ## Scan image for vulnerabilities (Trivy) + @echo "$(GREEN)Scanning for vulnerabilities...$(NC)" + @if command -v trivy >/dev/null 2>&1; then \ + trivy image --severity HIGH,CRITICAL $(FULL_IMAGE):latest; \ + else \ + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + aquasec/trivy:latest image --severity HIGH,CRITICAL $(FULL_IMAGE):latest; \ + fi + +.PHONY: scan-dev +scan-dev: ## Scan dev image for vulnerabilities + @echo "$(GREEN)Scanning dev image...$(NC)" + @if command -v trivy >/dev/null 2>&1; then \ + trivy image --severity HIGH,CRITICAL $(FULL_IMAGE):dev; \ + else \ + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + aquasec/trivy:latest image --severity HIGH,CRITICAL $(FULL_IMAGE):dev; \ + fi + +.PHONY: validate-images +validate-images: ## Validate all images exist locally + @echo "$(GREEN)Validating images...$(NC)" + @MISSING=0; \ + for tag in latest $(VERSION) $(MAJOR_VERSION) $(MINOR_VERSION) dev; do \ + if docker image inspect $(FULL_IMAGE):$$tag >/dev/null 2>&1; then \ + echo "$(GREEN)✓ $(FULL_IMAGE):$$tag$(NC)"; \ + else \ + echo "$(RED)✗ $(FULL_IMAGE):$$tag (missing)$(NC)"; \ + MISSING=$$((MISSING + 1)); \ + fi; \ + done; \ + if [ $$MISSING -gt 0 ]; then \ + echo ""; \ + echo "$(YELLOW)WARNING: $$MISSING image(s) missing. Run 'make build-all' first.$(NC)"; \ + exit 1; \ + fi; \ + echo "$(GREEN)OK: All images validated$(NC)" + +# ============================================================= +# PUBLISH (BUILD + PUSH) +# ============================================================= +.PHONY: publish-production +publish-production: build tag-production push-production ## Build, tag and push production + @echo "$(GREEN)OK: Production published: $(VERSION)$(NC)" + +.PHONY: publish-dev +publish-dev: build-dev tag-dev push-dev ## Build, tag and push dev + @echo "$(GREEN)OK: Dev published: dev$(NC)" + +.PHONY: publish-all +publish-all: build-all tag-all push-all ## Build, tag and push all images + @echo "$(GREEN)OK: All images published$(NC)" + +# ============================================================= +# RELEASE PIPELINE +# ============================================================= +.PHONY: pre-release-check +pre-release-check: ## Pre-release validation checks + @echo "$(GREEN)Running pre-release checks...$(NC)" + @echo "" + @echo "$(CYAN)[1/5] Checking Git status...$(NC)" + @if [ -n "$$(git status --porcelain 2>/dev/null)" ]; then \ + echo "$(RED)✗ Uncommitted changes detected$(NC)"; \ + git status --short; \ + exit 1; \ + fi + @echo "$(GREEN)✓ Git working directory clean$(NC)" + @echo "" + @echo "$(CYAN)[2/5] Checking Docker daemon...$(NC)" + @docker info >/dev/null 2>&1 || (echo "$(RED)✗ Docker daemon not running$(NC)" && exit 1) + @echo "$(GREEN)✓ Docker daemon running$(NC)" + @echo "" + @echo "$(CYAN)[3/5] Checking Docker Hub authentication...$(NC)" + @if ! docker info 2>/dev/null | grep -q "Username"; then \ + echo "$(YELLOW)⚠ Not logged in to Docker Hub$(NC)"; \ + $(MAKE) docker-login; \ + else \ + echo "$(GREEN)✓ Logged in to Docker Hub$(NC)"; \ + fi + @echo "" + @echo "$(CYAN)[4/5] Checking VERSION file...$(NC)" + @if [ ! -f VERSION ]; then \ + echo "$(RED)✗ VERSION file not found$(NC)"; \ + exit 1; \ + fi + @echo "$(GREEN)✓ VERSION file exists: $(VERSION)$(NC)" + @echo "" + @echo "$(CYAN)[5/5] Linting Dockerfile...$(NC)" + @$(MAKE) lint --no-print-directory + @echo "" + @echo "$(GREEN)✓ All pre-release checks passed!$(NC)" + +.PHONY: release-production +release-production: pre-release-check build test scan tag-production push-production ## Full production release pipeline + @echo "" + @echo "$(GREEN)╔═══════════════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ OK: Production Release Complete! ║$(NC)" + @echo "$(GREEN)╚═══════════════════════════════════════════════════╝$(NC)" + @echo "" + @echo "$(CYAN)Version:$(NC) $(VERSION)" + @echo "$(CYAN)Image:$(NC) $(FULL_IMAGE):$(VERSION)" + @echo "$(CYAN)Docker Hub:$(NC) https://hub.docker.com/r/$(FULL_IMAGE)" + @echo "" + @echo "$(YELLOW)Available tags:$(NC)" + @echo " • $(FULL_IMAGE):$(VERSION)" + @echo " • $(FULL_IMAGE):$(MAJOR_VERSION)" + @echo " • $(FULL_IMAGE):$(MINOR_VERSION)" + @echo " • $(FULL_IMAGE):latest" + @echo "" + @echo "$(CYAN)Pull command:$(NC)" + @echo " docker pull $(FULL_IMAGE):latest" + @echo "" + +.PHONY: release-dev +release-dev: build-dev tag-dev push-dev ## Release dev image + @echo "" + @echo "$(GREEN)OK: Dev Release Complete: dev$(NC)" + +.PHONY: release-all +release-all: pre-release-check build-all test scan tag-all push-all ## Full release (production + dev) + @echo "" + @echo "$(GREEN)╔═══════════════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ OK: Full Release Complete! ║$(NC)" + @echo "$(GREEN)╚═══════════════════════════════════════════════════╝$(NC)" + @echo "" + @echo "$(CYAN)Version:$(NC) $(VERSION)" + @echo "" + @echo "$(YELLOW)Production tags:$(NC)" + @echo " • $(FULL_IMAGE):$(VERSION)" + @echo " • $(FULL_IMAGE):latest" + @echo "" + @echo "$(YELLOW)Development tag:$(NC)" + @echo " • $(FULL_IMAGE):dev" + +.PHONY: release +release: release-production ## Alias for release-production + +# ============================================================= +# DOCKER HUB UTILITIES +# ============================================================= +.PHONY: hub-check +hub-check: ## Check if image exists on Docker Hub + @echo "$(GREEN)Checking Docker Hub...$(NC)" + @printf "$(CYAN)Checking $(FULL_IMAGE):latest...$(NC) "; \ + if docker manifest inspect $(FULL_IMAGE):latest >/dev/null 2>&1; then \ + echo "$(GREEN)✓ exists$(NC)"; \ + else \ + echo "$(YELLOW)✗ not found$(NC)"; \ + fi + @printf "$(CYAN)Checking $(FULL_IMAGE):$(VERSION)...$(NC) "; \ + if docker manifest inspect $(FULL_IMAGE):$(VERSION) >/dev/null 2>&1; then \ + echo "$(GREEN)✓ exists$(NC)"; \ + else \ + echo "$(YELLOW)✗ not found$(NC)"; \ + fi + @printf "$(CYAN)Checking $(FULL_IMAGE):dev...$(NC) "; \ + if docker manifest inspect $(FULL_IMAGE):dev >/dev/null 2>&1; then \ + echo "$(GREEN)✓ exists$(NC)"; \ + else \ + echo "$(YELLOW)✗ not found$(NC)"; \ + fi + +.PHONY: hub-tags +hub-tags: ## List all tags on Docker Hub + @echo "$(GREEN)Fetching tags from Docker Hub...$(NC)" + @curl -s https://registry.hub.docker.com/v2/repositories/$(FULL_IMAGE)/tags/ | \ + jq -r '.results[].name' | sort -V || \ + echo "$(YELLOW)Install jq for better output: apt install jq$(NC)" + +.PHONY: hub-info +hub-info: ## Show Docker Hub repository info + @echo "$(CYAN)╔═══════════════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ Docker Hub Repository Information ║$(NC)" + @echo "$(CYAN)╚═══════════════════════════════════════════════════╝$(NC)" + @echo "" + @echo "$(CYAN)Repository:$(NC) $(FULL_IMAGE)" + @echo "$(CYAN)URL:$(NC) https://hub.docker.com/r/$(FULL_IMAGE)" + @echo "$(CYAN)Current Ver:$(NC) $(VERSION)" + @echo "" + @echo "$(YELLOW)Expected Production Tags:$(NC)" + @echo " • latest" + @echo " • $(VERSION)" + @echo " • $(MAJOR_VERSION)" + @echo " • $(MINOR_VERSION)" + @echo "" + @echo "$(YELLOW)Expected Dev Tag:$(NC)" + @echo " • dev" + @echo "" + @$(MAKE) hub-check --no-print-directory + +.PHONY: hub-clean +hub-clean: ## Remove old local tags (keeps latest, current version, dev) + @echo "$(YELLOW)Cleaning old local tags...$(NC)" + @echo "$(CYAN)Keeping: latest, $(VERSION), dev$(NC)" + @docker images $(FULL_IMAGE) --format "{{.Tag}}" | \ + grep -v -E "^(latest|$(VERSION)|dev|$(MAJOR_VERSION)|$(MINOR_VERSION))$$" | \ + xargs -I {} sh -c 'echo "Removing $(FULL_IMAGE):{}" && docker rmi $(FULL_IMAGE):{} 2>/dev/null' || true + @echo "$(GREEN)OK: Cleanup complete$(NC)" + +# ============================================================= +# HELP FOR DOCKER HUB COMMANDS +# ============================================================= +.PHONY: hub-help +hub-help: ## Show Docker Hub specific help + @echo "$(GREEN)╔═══════════════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ Docker Hub Commands - Quick Reference ║$(NC)" + @echo "$(GREEN)╚═══════════════════════════════════════════════════╝$(NC)" + @echo "" + @echo "$(MAGENTA)=== AUTHENTICATION ===$(NC)" + @echo " $(YELLOW)make docker-login$(NC) Login to Docker Hub" + @echo " $(YELLOW)make docker-logout$(NC) Logout from Docker Hub" + @echo "" + @echo "$(MAGENTA)=== VERSION MANAGEMENT ===$(NC)" + @echo " $(YELLOW)make version$(NC) Show current version" + @echo " $(YELLOW)make bump-patch$(NC) Bump patch version (x.x.X)" + @echo " $(YELLOW)make bump-minor$(NC) Bump minor version (x.X.0)" + @echo " $(YELLOW)make bump-major$(NC) Bump major version (X.0.0)" + @echo "" + @echo "$(MAGENTA)=== TAGGING ===$(NC)" + @echo " $(YELLOW)make tag-production$(NC) Tag production image" + @echo " $(YELLOW)make tag-dev$(NC) Tag dev image" + @echo " $(YELLOW)make tag-all$(NC) Tag all images" + @echo "" + @echo "$(MAGENTA)=== PUSH TO HUB ===$(NC)" + @echo " $(YELLOW)make push-production$(NC) Push production images" + @echo " $(YELLOW)make push-dev$(NC) Push dev image" + @echo " $(YELLOW)make push-all$(NC) Push all images" + @echo "" + @echo "$(MAGENTA)=== COMPLETE WORKFLOWS ===$(NC)" + @echo " $(YELLOW)make publish-production$(NC) Build + Tag + Push production" + @echo " $(YELLOW)make publish-dev$(NC) Build + Tag + Push dev" + @echo " $(YELLOW)make publish-all$(NC) Build + Tag + Push all" + @echo "" + @echo "$(MAGENTA)=== RELEASE PIPELINE ===$(NC)" + @echo " $(YELLOW)make release-production$(NC) Full production release" + @echo " $(YELLOW)make release-dev$(NC) Release dev image" + @echo " $(YELLOW)make release-all$(NC) Full release (prod + dev)" + @echo " $(YELLOW)make release$(NC) Alias for release-production" + @echo "" + @echo "$(MAGENTA)=== VALIDATION ===$(NC)" + @echo " $(YELLOW)make lint$(NC) Lint Dockerfile" + @echo " $(YELLOW)make scan$(NC) Scan for vulnerabilities" + @echo " $(YELLOW)make validate-images$(NC) Check local images exist" + @echo " $(YELLOW)make pre-release-check$(NC) Pre-release validation" + @echo "" + @echo "$(MAGENTA)=== HUB UTILITIES ===$(NC)" + @echo " $(YELLOW)make hub-check$(NC) Check if images exist on Hub" + @echo " $(YELLOW)make hub-tags$(NC) List all Hub tags" + @echo " $(YELLOW)make hub-info$(NC) Show repository info" + @echo " $(YELLOW)make hub-clean$(NC) Remove old local tags" + @echo "" + @echo "$(BLUE)Examples:$(NC)" + @echo " $(CYAN)# Quick release$(NC)" + @echo " make release-production" + @echo "" + @echo " $(CYAN)# Version bump + release$(NC)" + @echo " make bump-patch && make release-production" + @echo "" + @echo " $(CYAN)# Build and publish dev$(NC)" + @echo " make publish-dev" \ No newline at end of file diff --git a/README.md b/README.md index 1995665..5cc69fc 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Docker Pulls](https://img.shields.io/docker/pulls/kariricode/php-api-stack)](https://hub.docker.com/r/kariricode/php-api-stack) [![Docker Image Size](https://img.shields.io/docker/image-size/kariricode/php-api-stack/latest)](https://hub.docker.com/r/kariricode/php-api-stack) +[![Docker Image Version](https://img.shields.io/docker/v/kariricode/php-api-stack?sort=semver)](https://hub.docker.com/r/kariricode/php-api-stack) [![License](https://img.shields.io/github/license/kariricode/php-api-stack)](LICENSE) [![Build Status](https://img.shields.io/github/actions/workflow/status/kariricode/php-api-stack/build.yml)](https://github.com/kariricode/php-api-stack/actions) @@ -22,26 +23,26 @@ - 🔴 **Redis 7.2** for caching and session management - 🎯 **Production-ready** with security hardening and performance tuning - 📊 **Comprehensive health checks** for monitoring and orchestration -- 🛠️ **Developer-friendly** with extensive Make targets and examples +- 🛠️ **Developer-friendly** with extensive Make targets (50+) and examples - 🔒 **Security-first** with rate limiting, headers, and vulnerability scanning - 📦 **Multi-platform** support (amd64, arm64) - 🎭 **Flexible deployment** via Docker or Docker Compose with profiles +- 🧰 **Three specialized Makefiles** for building, Docker Hub, and Compose operations --- ## 🚀 Quick Start -### Option 1: Docker Run (Simple) +### Option 1: Docker Run (Simplest) ```bash -# Pull the image +# Pull and run with demo page docker pull kariricode/php-api-stack:latest - -# Run with demo page docker run -d -p 8080:80 --name my-api kariricode/php-api-stack:latest # Access the demo -open http://localhost:8080 +curl http://localhost:8080 +# or open http://localhost:8080 in browser ``` ### Option 2: With Your Application @@ -50,223 +51,338 @@ open http://localhost:8080 docker run -d \ -p 8080:80 \ -v $(pwd)/app:/var/www/html \ - --env-file .env \ + -v $(pwd)/.env:/var/www/html/.env:ro \ + -e APP_ENV=production \ --name my-api \ kariricode/php-api-stack:latest ``` -### Option 3: Docker Compose (Recommended for Development) +### Option 3: Docker Compose (Recommended) ```bash -# Copy example files +# Setup cp .env.example .env cp docker-compose.example.yml docker-compose.yml -# Start with profiles +# Start base services +make compose-up + +# Start with database + monitoring make compose-up PROFILES="db,monitoring" # Or start everything make compose-up-all + +# Access +open http://localhost:8089 # Application +open http://localhost:8089/health # Health check +open http://localhost:3000 # Grafana (admin/password) ``` **📖 See [DOCKER_COMPOSE_GUIDE.md](DOCKER_COMPOSE_GUIDE.md) for complete Docker Compose documentation** -✅ **The stack is ready with demo page, health checks, and all services running.** - --- ## 📚 Documentation | Document | Audience | Description | |----------|----------|-------------| -| **[IMAGE_USAGE_GUIDE.md](IMAGE_USAGE_GUIDE.md)** | End Users | How to use the published image | -| **[DOCKER_COMPOSE_GUIDE.md](DOCKER_COMPOSE_GUIDE.md)** | Developers | Complete Docker Compose setup guide | -| **[TESTING.md](TESTING.md)** | Maintainers | Complete testing guide | -| **[DOCKER_HUB.md](DOCKER_HUB.md)** | Publishers | How to publish to Docker Hub | +| **[IMAGE_USAGE_GUIDE.md](IMAGE_USAGE_GUIDE.md)** | End Users | How to use the published Docker image | +| **[DOCKER_COMPOSE_GUIDE.md](DOCKER_COMPOSE_GUIDE.md)** | Developers | Complete Docker Compose orchestration guide | +| **[TESTING.md](TESTING.md)** | Maintainers | Comprehensive testing procedures | +| **[DOCKER_HUB.md](DOCKER_HUB.md)** | Publishers | Docker Hub publication workflow | --- ## 🗃️ Architecture ``` -Client → Nginx (port 80) → PHP-FPM (Unix socket) → PHP Application - ↓ ↓ - FastCGI Cache Redis (sessions/cache) -``` - -**Container Management**: Services managed by custom entrypoint with health monitoring + ┌─────────────────────────────────┐ + │ Client Request │ + └─────────────┬───────────────────┘ + │ + ┌─────────────▼───────────────────┐ + │ Nginx (port 80) │ + │ • FastCGI Cache │ + │ • Rate Limiting │ + │ • Security Headers │ + └─────────────┬───────────────────┘ + │ Unix Socket + ┌─────────────▼───────────────────┐ + │ PHP-FPM 8.4 │ + │ • OPcache + JIT │ + │ • Pool Manager (60 children) │ + └─────────────┬───────────────────┘ + │ + ┌─────────────▼───────────────────┐ + │ PHP Application │ + │ • Framework (Symfony/Laravel) │ + │ • Business Logic │ + └─────────────┬───────────────────┘ + │ + ┌─────────────▼───────────────────┐ + │ Redis (sessions/cache) │ + │ • AOF Persistence │ + │ • LRU Eviction │ + └─────────────────────────────────┘ +``` + +**Container Management**: All services orchestrated by custom entrypoint with health monitoring and graceful shutdown --- ## 📦 Stack Components -| Component | Version | Purpose | -|-----------|---------|---------| -| **PHP-FPM** | 8.4 | PHP processing with optimized pool | -| **Nginx** | 1.27.3 | High-performance web server | -| **Redis** | 7.2 | Cache and session management | -| **Composer** | 2.8.12 | PHP dependency manager | -| **Symfony CLI** | 5.15.1 | Symfony tools (dev build only) | +| Component | Version | Purpose | Configuration | +|-----------|---------|---------|--------------| +| **PHP-FPM** | 8.4.13 | PHP processing | Optimized pool, OPcache JIT | +| **Nginx** | 1.27.3 | Web server | FastCGI cache, rate limiting | +| **Redis** | 7.2.11 | Cache & sessions | AOF persistence, LRU eviction | +| **Composer** | 2.8.12 | Dependency manager | Included in production | +| **Symfony CLI** | 5.15.1 | Symfony tools | Dev image only | +| **Xdebug** | 3.4.6 | PHP debugger | Dev image only (optional) | --- ## 📌 PHP Extensions -### Core Extensions (Installed) +### Core Extensions (Pre-installed) ``` pdo, pdo_mysql, opcache, intl, zip, bcmath, gd, mbstring, xml, sockets ``` -### PECL Extensions (Installed) +### PECL Extensions (Pre-installed) ``` -redis, apcu, uuid +redis (6.1.0), apcu (5.1.24), uuid (1.2.1), imagick (3.7.0), amqp (2.1.2) ``` -### Built-in Extensions (Auto-available) +### Built-in Extensions (Always Available) ``` -json, curl, fileinfo, ctype, iconv, session, tokenizer, filter +json, curl, fileinfo, ctype, iconv, session, tokenizer, filter, hash, openssl ``` -**Add more?** Edit `.env` and rebuild: +### Adding Custom Extensions + +Edit `.env` before building: ```bash -PHP_CORE_EXTENSIONS="... newext" +# Add your extension to the list +PHP_CORE_EXTENSIONS="pdo pdo_mysql opcache intl zip bcmath gd mbstring xml sockets mysqli" +PHP_PECL_EXTENSIONS="redis apcu uuid xdebug" + +# Rebuild make build ``` +**Note**: For production images, extensions are optimized and loaded automatically. + --- ## ⚙️ Configuration -All configurations via `.env` file: +All configuration is managed via `.env` file with extensive customization options: -```bash -# PHP Performance -PHP_MEMORY_LIMIT=256M -PHP_OPCACHE_MEMORY=256 -PHP_OPCACHE_JIT=tracing -PHP_FPM_PM_MAX_CHILDREN=60 +### Essential Variables +```bash # Environment -APP_ENV=production -APP_DEBUG=false -PHP_DISPLAY_ERRORS=Off +APP_ENV=production # production|development|test +APP_DEBUG=false # Enable debug mode (dev only) + +# PHP Performance +PHP_MEMORY_LIMIT=256M # Memory limit per request +PHP_MAX_EXECUTION_TIME=60 # Script timeout +PHP_OPCACHE_MEMORY=256 # OPcache memory (MB) +PHP_OPCACHE_JIT=tracing # JIT mode: off|tracing|function +PHP_OPCACHE_ENABLE=1 # Enable OPcache (always 1 in prod) + +# PHP-FPM Pool +PHP_FPM_PM=dynamic # Process manager mode +PHP_FPM_PM_MAX_CHILDREN=60 # Max child processes +PHP_FPM_PM_START_SERVERS=10 # Initial servers +PHP_FPM_PM_MIN_SPARE_SERVERS=5 # Min idle servers +PHP_FPM_PM_MAX_SPARE_SERVERS=20 # Max idle servers + +# Redis +REDIS_HOST=127.0.0.1 # Redis host (standalone mode) +REDIS_PASSWORD=your-secure-password # Redis authentication +REDIS_DATABASES=16 # Number of databases +REDIS_MAXMEMORY=256mb # Max memory usage +REDIS_MAXMEMORY_POLICY=allkeys-lru # Eviction policy + +# Nginx +NGINX_WORKER_PROCESSES=auto # Worker processes (auto = CPU cores) +NGINX_WORKER_CONNECTIONS=2048 # Connections per worker +NGINX_KEEPALIVE_TIMEOUT=65 # Keep-alive timeout +NGINX_CLIENT_MAX_BODY_SIZE=20M # Max upload size +``` + +### Development Mode -# Extensions -PHP_CORE_EXTENSIONS="pdo pdo_mysql opcache..." -PHP_PECL_EXTENSIONS="redis apcu uuid" +```bash +APP_ENV=development +APP_DEBUG=true +PHP_DISPLAY_ERRORS=On +PHP_OPCACHE_VALIDATE_TIMESTAMPS=1 # Reload PHP files without restart +XDEBUG_ENABLE=1 # Enable Xdebug ``` -**Full reference**: See `.env.example` +**Full reference**: Copy `.env.example` and customize for your needs. --- ## 🧰 Makefile Commands -The project includes a comprehensive Makefile with **logically organized targets by similarity**. Run `make help` for the complete list. +The project includes **three specialized Makefiles** for different purposes: + +### 📁 Makefile Organization + +| Makefile | Purpose | Commands | +|----------|---------|----------| +| **Makefile** | Build, test, run containers | 40+ commands | +| **Makefile.dockerhub** | Docker Hub operations | Version management, tagging, push, release | +| **Makefile.compose** | Docker Compose orchestration | Multi-service management | + +Run `make help` for the main menu, or specialized help: +- `make hub-help` - Docker Hub commands +- `make compose-help` - Docker Compose commands + +--- ### 🏗️ Build Targets ```bash -make build # Build production image -make build-dev # Build dev image (with Symfony CLI, optional Xdebug) -make build-no-cache # Build without cache -make build-test-image # Build test image with comprehensive health check +make build # Build production image (optimized) +make build-dev # Build dev image (Symfony CLI + optional Xdebug) +make build-base # Build base layer only (for debugging) +make build-test # Build test image with comprehensive health check +make build-all # Build both production and dev images make lint # Lint Dockerfile with hadolint -make scan # Scan for vulnerabilities with Trivy +make scan # Security scan with Trivy +make scan-dev # Scan dev image for vulnerabilities ``` ### 🚀 Runtime - Run Containers ```bash -make run # Run local container (port 8080) -make run-with-app # Run with mounted application -make run-dev # Run development container (port 8001, Xdebug ready) -make run-test # Run test container with comprehensive health checks +make run # Run production container (port 8080) +make run-dev # Run dev container (port 8001, Xdebug on 9003) +make run-test # Run test container with health monitoring ``` ### ⏹️ Runtime - Stop & Restart ```bash -make stop # Stop and remove local container -make stop-dev # Stop and remove dev container -make stop-test # Stop and remove test container -make restart # Restart local container -make restart-dev # Restart development container -make restart-test # Restart test container +make stop # Stop production container +make stop-dev # Stop dev container +make stop-test # Stop test container +make restart # Restart production container +make restart-dev # Restart dev container ``` ### 📋 Runtime - Logs & Shell ```bash -make logs # View local container logs (follow) -make logs-dev # View development container logs -make logs-test # View test container logs -make shell # Access local container shell (alias: exec) -make shell-dev # Access development container shell -make shell-test # Access test container shell -make stats # Show container resource usage +make logs # Follow production logs +make logs-dev # Follow dev logs +make shell # Open shell in production container +make shell-dev # Open shell in dev container ``` ### 🧪 Test Targets ```bash -make test # Run full test suite (versions, config, health) +make test # Run comprehensive test suite make test-quick # Quick component version checks -make test-structure # Test container structure and directories -make test-health # Test comprehensive health check endpoint -make test-health-status # Show health check status summary -make test-health-watch # Live health monitoring (updates every 5s) +make test-structure # Validate container structure +make test-health # Test health check endpoint ``` -### 📤 Push & Publish Targets +--- + +### 🐳 Docker Hub Commands + +These commands are in **Makefile.dockerhub**. Run `make hub-help` for complete list. + +#### Version Management ```bash -make push # Push all tags to Docker Hub -make push-dev # Push dev image with custom tag (IMAGE_TAG=xxx) -make publish-dev # Build and push dev image (build-dev + push-dev) +make version # Show current version +make bump-patch # Bump patch (1.5.0 → 1.5.1) +make bump-minor # Bump minor (1.5.0 → 1.6.0) +make bump-major # Bump major (1.5.0 → 2.0.0) ``` -### 🏷️ Release & Versioning +#### Tagging & Push ```bash -make tag-latest # Tag current version as latest/major/minor -make release # Full release pipeline (lint+build+test+scan+push) -make version # Display current version -make bump-patch # Bump patch version (x.x.X) -make bump-minor # Bump minor version (x.X.x) -make bump-major # Bump major version (X.x.x) +make tag-production # Tag production image (latest, 1, 1.5, 1.5.0) +make tag-dev # Tag dev image (dev only) +make push-production # Push all production tags +make push-dev # Push dev tag +make push-all # Push all images ``` -### 🧹 Cleanup Targets +#### Complete Workflows ```bash -make clean # Remove local images and containers -make clean-all # Deep clean (volumes + build cache) +make publish-production # Build + Tag + Push production +make publish-dev # Build + Tag + Push dev +make release-production # Full release: pre-check + build + test + scan + push +make release-all # Release both production and dev ``` -### ℹ️ Information Targets +#### Hub Utilities + +```bash +make hub-check # Check if images exist on Docker Hub +make hub-tags # List all published tags +make hub-info # Show repository information +make hub-clean # Remove old local tags +make pre-release-check # Validate before release (git, docker, auth) +``` +**Example Release Workflow:** ```bash -make info # Show image information (tags, sizes) +make bump-minor # 1.5.0 → 1.6.0 +make release-production # Full automated release ``` -### 🐳 Docker Compose Targets +--- + +### 🎭 Docker Compose Commands + +These commands are in **Makefile.compose**. Run `make compose-help` for complete list. + +#### Lifecycle -For complete infrastructure setup with databases, load balancers, and monitoring: +```bash +make compose-up # Start services (respects PROFILES) +make compose-up-all # Start with all profiles +make compose-down-v # Stop and remove volumes +make compose-down-all # Stop everything + all profiles +make compose-restart # Restart services +make compose-ps # Show service status +``` + +#### Logs & Shell ```bash -make compose-help # Show Docker Compose help -make compose-up # Start services (respects PROFILES) -make compose-up-all # Start with all profiles (loadbalancer,monitoring) -make compose-down-v # Stop and remove volumes -make compose-down-all # Stop everything including all profiles -make compose-logs # Tail logs for active services -make compose-logs-all # Tail logs for all services -make compose-ps # Show container status -make compose-shell # Access service shell (default: php-api-stack) +make compose-logs # Tail logs (all active services) +make compose-logs-all # Tail logs (base + all profiles) +make compose-logs-svc # Tail logs for specific services +make compose-shell # Open shell in service +make compose-exec # Execute command in service ``` -**Profile Examples:** +#### Utilities + +```bash +make compose-config # Show resolved config +make compose-health # Test health endpoint +make compose-open # Open app/Prometheus/Grafana in browser +``` + +**Example with Profiles:** ```bash # Start with database make compose-up PROFILES="db" @@ -274,142 +390,163 @@ make compose-up PROFILES="db" # Start with monitoring make compose-up PROFILES="monitoring" -# Start with multiple profiles -make compose-up PROFILES="db,monitoring" - # Start specific services make compose-up-svc SERVICES="php-api-stack mysql" -# View logs for specific service +# View specific logs make compose-logs-svc SERVICES="php-api-stack" ``` +--- + ### Quick Workflow Examples **Development Workflow:** ```bash make build-dev # Build dev image -make run-dev # Start dev container (with Xdebug) -make logs-dev # View dev logs -make shell-dev # Access dev container -make restart-dev # Restart dev container -make stop-dev # Stop dev container +make run-dev # Start dev (port 8001, Xdebug ready) +make logs-dev # View logs +make shell-dev # Access container +curl http://localhost:8001/health ``` **Testing Workflow:** ```bash -make build-test-image # Build test image +make build-test # Build with comprehensive health check make run-test # Start test container -make test-health-watch # Monitor health in real-time -make logs-test # View test logs -make stop-test # Stop test container +make test-health # Test health endpoint +make logs-test # View logs ``` **Release Workflow:** ```bash make lint # Lint Dockerfile -make build # Build production image +make build-all # Build production + dev make test # Run tests make scan # Security scan make bump-patch # Bump version -make release # Full release pipeline +make release-all # Full release pipeline +make hub-check # Verify on Docker Hub +``` + +**Docker Compose Workflow:** +```bash +make compose-up-all # Start all services +make compose-ps # Check status +make compose-logs # View logs +make compose-open # Open in browser +make compose-down-all # Stop everything ``` --- ## 🐳 Docker Compose -The project includes a complete `docker-compose.example.yml` with multiple service profiles: +Complete orchestration with databases, load balancing, and monitoring. ### Available Profiles -- **Base** (always active): `php-api-stack` - The main application container -- **db**: MySQL database with optimized configuration -- **loadbalancer**: Nginx load balancer for scaling -- **monitoring**: Prometheus + Grafana + cAdvisor stack +| Profile | Services | Purpose | +|---------|----------|---------| +| **Base** | php-api-stack | Main application (always active) | +| **db** | mysql, redis-external | Databases with persistence | +| **loadbalancer** | nginx-lb | Nginx load balancer for scaling | +| **monitoring** | prometheus, grafana, cadvisor | Full monitoring stack | ### Quick Start ```bash -# 1. Setup environment +# 1. Setup cp .env.example .env cp docker-compose.example.yml docker-compose.yml -# 2. Start base services +# 2. Start base make compose-up -# 3. Start with database -make compose-up PROFILES="db" +# 3. Start with profiles +make compose-up PROFILES="db,monitoring" -# 4. Start with monitoring -make compose-up PROFILES="monitoring" +# 4. Or start everything +make compose-up-all -# 5. Start everything -make compose-up-all # Equivalent to PROFILES="loadbalancer,monitoring" +# 5. Access services +make compose-open # Opens app, Prometheus, Grafana +``` -# 6. View services -make compose-ps +### Service URLs -# 7. View logs -make compose-logs +| Service | URL | Credentials | +|---------|-----|-------------| +| **Application** | http://localhost:8089 | - | +| **Health Check** | http://localhost:8089/health | - | +| **Prometheus** | http://localhost:9091 | - | +| **Grafana** | http://localhost:3000 | admin / HmlGrafana_7uV4mRp | +| **MySQL** | localhost:3307 | root / HmlMysql_9tQ2wRx | -# 8. Access application -open http://localhost:8089 +### Management -# 9. Access monitoring (if enabled) -open http://localhost:3000 # Grafana (admin/HmlGrafana_7uV4mRp) -open http://localhost:9091 # Prometheus -``` +```bash +# View status +make compose-ps -### Service URLs +# View logs +make compose-logs -When all profiles are active: +# Restart services +make compose-restart -| Service | URL | Description | -|---------|-----|-------------| -| Application | http://localhost:8089 | Main PHP application | -| Health Check | http://localhost:8089/health.php | Comprehensive health endpoint | -| Prometheus | http://localhost:9091 | Metrics collection | -| Grafana | http://localhost:3000 | Monitoring dashboards | -| MySQL | localhost:3307 | Database (if db profile enabled) | +# Scale application +docker compose up -d --scale php-api-stack=3 -**📖 Complete guide with examples**: [DOCKER_COMPOSE_GUIDE.md](DOCKER_COMPOSE_GUIDE.md) +# Stop everything +make compose-down-all +``` + +**📖 Complete guide**: [DOCKER_COMPOSE_GUIDE.md](DOCKER_COMPOSE_GUIDE.md) --- ## 🛠️ Development Workflow -### For Maintainers (GitHub) +### For Maintainers ```bash -# Clone and build +# 1. Clone and setup git clone https://github.com/kariricode/php-api-stack.git cd php-api-stack -make build +cp .env.example .env + +# 2. Build +make build-all -# Run tests +# 3. Test make test +make scan -# Run with comprehensive health check -make run-test -make test-health +# 4. Run dev +make run-dev +curl http://localhost:8001/health -# Release +# 5. Release make bump-patch -make release +make release-production ``` **Complete guide**: [TESTING.md](TESTING.md) -### For Publishers (Docker Hub) +### For Publishers ```bash -# Build and push -make build -make push +# Quick release +make release-production -# Or full release -make release # lint + build + test + scan + push +# Or step by step +make lint +make build +make test +make scan +make bump-patch +make push-production ``` **Complete guide**: [DOCKER_HUB.md](DOCKER_HUB.md) @@ -417,12 +554,9 @@ make release # lint + build + test + scan + push ### For End Users ```bash -# Pull image +# Pull and run docker pull kariricode/php-api-stack:latest - -# Run with your app -docker run -d \ - -p 8080:80 \ +docker run -d -p 8080:80 \ -v $(pwd)/app:/var/www/html \ kariricode/php-api-stack:latest ``` @@ -433,157 +567,438 @@ docker run -d \ ## 🏥 Health Check System -The stack includes **two health check implementations**: +Two health check implementations for different needs: + +### 1. Simple Health Check (Production) + +Lightweight endpoint for load balancers and orchestrators: -### Simple Health Check (Production) ```bash curl http://localhost:8080/health -# {"status":"healthy","timestamp":"2025-10-17T14:30:00+00:00"} ``` -### Comprehensive Health Check (Testing/Monitoring) +**Response:** +```json +{ + "status": "healthy", + "timestamp": "2025-10-24T22:00:00+00:00" +} +``` + +### 2. Comprehensive Health Check (Monitoring) + +Detailed system diagnostics with component-level checks: + ```bash curl http://localhost:8080/health.php | jq ``` **Features:** -- ✅ PHP Runtime validation (version, memory, SAPI) -- ✅ PHP Extensions check (required + optional) -- ✅ OPcache performance (memory, hit rate, JIT status) -- ✅ Redis connectivity (latency, stats, memory) -- ✅ System resources (disk, CPU load, memory) -- ✅ Application directories (permissions, accessibility) - -**Architecture**: SOLID principles, Design Patterns (Strategy, Template Method, Facade) - -**Build with comprehensive health**: +- ✅ **PHP Runtime**: Version, memory, SAPI, configuration +- ✅ **PHP Extensions**: Required and optional extensions validation +- ✅ **OPcache**: Hit rate, memory usage, JIT status, cached scripts +- ✅ **Redis**: Connectivity, latency, stats, memory usage, persistence +- ✅ **System Resources**: Disk space, CPU load, memory usage +- ✅ **Application**: Directory permissions, accessibility checks + +**Response Structure:** +```json +{ + "status": "healthy|degraded|unhealthy", + "timestamp": "2025-10-24T22:00:00+00:00", + "overall": "✓ All Systems Operational", + "components": { + "php": { "status": "healthy", "details": {...} }, + "opcache": { "status": "healthy", "details": {...} }, + "redis": { "status": "healthy", "details": {...} }, + "system": { "status": "healthy", "details": {...} }, + "application": { "status": "healthy", "details": {...} } + }, + "stack_info": { + "docker_image": "kariricode/php-api-stack", + "version": "1.5.0", + "php_version": "8.4.13" + } +} +``` + +### Using Health Checks + +**With Docker:** +```dockerfile +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost/health || exit 1 +``` + +**With Docker Compose:** +```yaml +services: + app: + image: kariricode/php-api-stack:latest + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 3s + retries: 3 +``` + +**With Kubernetes:** +```yaml +livenessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 5 + periodSeconds: 30 + +readinessProbe: + httpGet: + path: /health.php + port: 80 + initialDelaySeconds: 10 + periodSeconds: 10 +``` + +**Makefile Commands:** ```bash -make build-test-image -make run-test -make test-health +make test-health # Test comprehensive health check +make test-health-status # Show health summary +make test-health-watch # Live monitoring (updates every 5s) ``` +**Architecture**: Built with SOLID principles using Strategy, Template Method, and Facade patterns. + --- ## 🔐 Security Features -- ✅ Security headers (X-Frame-Options, CSP, HSTS) -- ✅ Rate limiting (general: 10r/s, API: 100r/s) -- ✅ Disabled dangerous PHP functions -- ✅ Open basedir restrictions -- ✅ Hidden server tokens +Production-hardened security configuration: + +### Nginx Security + +- ✅ **Security Headers**: X-Frame-Options, X-Content-Type-Options, CSP, HSTS +- ✅ **Rate Limiting**: + - General: 10 req/s per IP + - API endpoints: 100 req/s per IP +- ✅ **Hidden Tokens**: Server version and tokens hidden +- ✅ **Request Filtering**: Protection against common attacks + +### PHP Security + +- ✅ **Disabled Functions**: `exec`, `shell_exec`, `system`, `passthru`, etc. +- ✅ **Open Basedir**: Restricted to `/var/www/html` and `/tmp` +- ✅ **Expose PHP**: Off (version hidden) +- ✅ **File Uploads**: Configurable with size limits +- ✅ **Session Security**: Secure cookies, HTTP-only + +### Redis Security + +- ✅ **Authentication**: Password-protected (configurable) +- ✅ **Bind Address**: Internal only (127.0.0.1 or network) +- ✅ **Command Renaming**: Dangerous commands can be disabled +- ✅ **Persistence**: AOF with fsync control + +### Container Security + +- ✅ **Non-root User**: Application runs as `www-data` +- ✅ **Read-only Filesystem**: Where possible +- ✅ **Resource Limits**: CPU and memory constraints +- ✅ **Security Scanning**: Automated Trivy scans + +**Scan for vulnerabilities:** +```bash +make scan # Scan production image +make scan-dev # Scan dev image +``` --- ## 🛠 Troubleshooting -### Container won't start +### Container Won't Start + ```bash +# Check logs docker logs + +# Test structure make test-structure + +# Check entrypoint +docker run --rm kariricode/php-api-stack:latest cat /entrypoint.sh ``` ### 502 Bad Gateway + ```bash # Check PHP-FPM socket docker exec ls -la /var/run/php/php-fpm.sock +# Check PHP-FPM status +docker exec php-fpm -t + # Check logs make logs ``` +### Redis Connection Issues + +**Standalone Mode:** +```bash +# Should use 127.0.0.1 +docker exec env | grep REDIS_HOST +# REDIS_HOST=127.0.0.1 + +# Test connection +docker exec redis-cli -h 127.0.0.1 -a "$REDIS_PASSWORD" ping +``` + +**Docker Compose Mode:** +```bash +# Should use 'redis' (service name) +docker compose exec php-api-stack env | grep REDIS_HOST +# REDIS_HOST=redis + +# Test connection +docker compose exec php-api-stack redis-cli -h redis -a "$REDIS_PASSWORD" ping +``` + ### Poor Performance + ```bash +# Check container resources docker stats + +# Check OPcache status docker exec php -r "print_r(opcache_get_status());" + +# Check PHP-FPM pool +docker exec cat /var/run/php/php-fpm.pid +docker exec kill -USR2 $(cat /var/run/php/php-fpm.pid) ``` -**Full troubleshooting**: [IMAGE_USAGE_GUIDE.md](IMAGE_USAGE_GUIDE.md) +### Permission Issues + +```bash +# Check ownership +docker exec ls -la /var/www/html + +# Fix permissions +docker exec chown -R www-data:www-data /var/www/html +``` + +**Full troubleshooting guide**: [IMAGE_USAGE_GUIDE.md](IMAGE_USAGE_GUIDE.md#troubleshooting) --- -## 📖 Documentation Reference +## 📖 External References - [PHP 8.4 Documentation](https://www.php.net/docs.php) - [Nginx Documentation](https://nginx.org/en/docs/) - [Redis Documentation](https://redis.io/documentation) +- [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/) - [Symfony Best Practices](https://symfony.com/doc/current/best_practices.html) -- [Docker Compose](https://docs.docker.com/compose/) +- [Laravel Deployment](https://laravel.com/docs/deployment) - [Twelve-Factor App](https://12factor.net/) --- ## 🤝 Contributing -Contributions are welcome! Please: +Contributions are welcome! Please follow these guidelines: + +### Getting Started 1. Fork the repository -2. Create feature branch (`git checkout -b feature/amazing`) -3. Commit changes (`git commit -m 'Add amazing feature'`) -4. Push to branch (`git push origin feature/amazing`) -5. Open Pull Request +2. Create a feature branch: `git checkout -b feature/amazing-feature` +3. Make your changes +4. Run tests: `make test` +5. Lint: `make lint` +6. Commit: `git commit -m 'feat: add amazing feature'` +7. Push: `git push origin feature/amazing-feature` +8. Open a Pull Request + +### Standards + +- **Code Style**: Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) for PHP code +- **Commit Messages**: Use [Conventional Commits](https://www.conventionalcommits.org/) + - `feat:` New features + - `fix:` Bug fixes + - `docs:` Documentation changes + - `refactor:` Code refactoring + - `test:` Test additions/changes + - `chore:` Build/tooling changes +- **Testing**: Add tests for new features +- **Documentation**: Update relevant documentation + +### Before Submitting -**Standards:** -- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) for PHP -- Use [Conventional Commits](https://www.conventionalcommits.org/) -- Add tests for new features -- Update documentation +```bash +make lint # Lint Dockerfile +make build-all # Build all images +make test # Run tests +make scan # Security scan +``` --- ## 📄 License -This project is licensed under the MIT License. See [LICENSE](LICENSE) file. +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +**TL;DR**: You can use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software. --- -## 🌟 Support +## 🌟 Support & Community + +### Get Help - **Issues**: [GitHub Issues](https://github.com/kariricode/php-api-stack/issues) - **Discussions**: [GitHub Discussions](https://github.com/kariricode/php-api-stack/discussions) - **Docker Hub**: [kariricode/php-api-stack](https://hub.docker.com/r/kariricode/php-api-stack) +### Report Bugs + +Found a bug? Please open an issue with: +- Docker image version +- Steps to reproduce +- Expected vs actual behavior +- Relevant logs + +### Request Features + +Have an idea? Open a discussion or issue with: +- Use case description +- Proposed solution +- Any alternative solutions considered + --- -## 🧭 Roadmap & Contributing +## 🧭 Roadmap -Feature requests and PRs are welcome in the source repository: +### Current Focus (v1.5.x) -* GitHub: [https://github.com/kariricode/php-api-stack](https://github.com/kariricode/php-api-stack) +- ✅ Comprehensive Makefile system with 50+ commands +- ✅ Docker Hub automation and versioning +- ✅ Docker Compose orchestration with profiles +- ✅ Advanced health check system +- 🔄 Performance benchmarking suite +- 🔄 Automated CI/CD workflows -For broader ecosystem projects, visit: +### Future Plans (v1.6+) -* KaririCode Framework: [https://github.com/KaririCode-Framework](https://github.com/KaririCode-Framework) +- 📋 Multi-stage build optimization +- 📋 Additional PECL extensions (gRPC, protobuf) +- 📋 ARM64 native builds +- 📋 Kubernetes manifests and Helm charts +- 📋 Additional monitoring integrations (Datadog, New Relic) +- 📋 Development containers (devcontainer.json) + +### Long-term Vision (v2.0+) + +- 📋 PHP 8.5 support +- 📋 Alternative web servers (Caddy, FrankenPHP) +- 📋 WebAssembly integration +- 📋 Enhanced security profiles + +--- + +## 📚 Related Projects + +This image is part of the **KaririCode** ecosystem: + +### KaririCode Framework + +Modern PHP framework with advanced features: + +- **Repository**: [KaririCode-Framework](https://github.com/KaririCode-Framework) +- **Features**: ARFA architecture, DI container, Router, Auth, etc. +- **Components**: 30+ independent packages +- **Documentation**: Comprehensive guides and examples + +### KaririCode DevKit + +Development environment automation: + +- **Repository**: [kariricode/devkit](https://github.com/kariricode/devkit) +- **Features**: Docker + Compose, quality tools, CI/CD +- **Setup Time**: 2-3 minutes +- **Integration**: Uses this Docker image --- ## 📝 Changelog -**1.4.3** (Latest) +### **v1.5.0** (2025-10-24) + +**Docker Hub Integration:** +- ✨ Fixed `hub-check` command display bug (showing 'ag' instead of tag names) +- ✨ Simplified dev tagging strategy: only `dev` tag (removed `dev-X.Y.Z`) +- ✨ Fixed `bump-patch/bump-minor/bump-major` dollar sign escaping +- ✨ Improved `hub-check` output with checkmark indicators (✓/✗) +- ✨ Added comprehensive Docker Hub utilities (`hub-tags`, `hub-info`, `hub-clean`) + +**Breaking Changes:** +- ⚠️ Dev versioned tags (`dev-X.Y.Z`) are no longer created or pushed to Docker Hub + +### **v1.4.5** (2025-10-24) -* ✨ **Makefile refactored** with semantic grouping by similarity (Build, Push, Runtime, Test, Validation, Release, etc.) -* 🎯 **Enhanced development workflow** with dedicated dev container targets (`run-dev`, `stop-dev`, `restart-dev`, `logs-dev`, `shell-dev`) -* 🧪 **Improved test targets** with health monitoring (`test-health-status`, `test-health-watch`) -* 📊 **Better organization** of 50+ Make targets into logical categories -* 📚 **Updated documentation** reflecting new command structure -* 🔧 **Zero breaking changes** - all existing targets work identically +**Build System:** +- ✨ Fixed PHP extension quoting in Makefile (`PHP_CORE_EXTENSIONS`, `PHP_PECL_EXTENSIONS`) +- ✨ Secure `.env` parsing in `build-from-env.sh` to prevent command execution +- ✨ Proper escaping for build args with spaces -**1.2.1** +**Redis Integration:** +- ✨ Automatic `REDIS_HOST` override to `127.0.0.1` for standalone containers +- ✨ Smart DNS fallback in health.php for docker-compose vs standalone modes +- ✨ Documentation improvements for `REDIS_HOST` behavior -* Added comprehensive Makefile with Docker Compose integration -* Added Docker Compose example with multiple profiles (db, loadbalancer, monitoring) -* Improved documentation structure with dedicated guides -* Enhanced health check system with monitoring capabilities +**Dockerfile Fixes:** +- 🐛 Fixed OPcache validation check (Zend extension vs regular extension) +- 🐛 Added `util-linux` runtime dependency for UUID extension +- 🐛 Fixed SC1075 shellcheck error (`else if` → `elif`) +- ✨ Improved extension loading verification -**1.2.0** +### **v1.4.3** -* PHP 8.4, Nginx 1.27.3, Redis 7.2 -* Socket-based PHP-FPM; OPcache + JIT optimized -* `/health.php` endpoint; improved entrypoint & config processor -* Extensive env-var configuration for Nginx/PHP/Redis +**Makefile Refactoring:** +- ✨ Semantic grouping by similarity (Build, Push, Runtime, Test, Validation, Release) +- ✨ Enhanced development workflow with dedicated dev targets +- ✨ Improved test targets with health monitoring +- ✨ Better organization of 50+ Make targets +- 📚 Updated documentation -> Full release notes are available in the GitHub repository. +### **v1.2.1** + +- ✨ Added comprehensive Makefile with Docker Compose integration +- ✨ Added Docker Compose example with multiple profiles +- ✨ Improved documentation structure +- ✨ Enhanced health check system + +### **v1.2.0** + +- ✨ PHP 8.4, Nginx 1.27.3, Redis 7.2 +- ✨ Socket-based PHP-FPM communication +- ✨ OPcache + JIT optimization +- ✨ `/health.php` comprehensive endpoint +- ✨ Improved entrypoint and config processor +- ✨ Extensive environment variable configuration + +### **v1.0.0** + +- 🎉 Initial release +- ✨ PHP 8.3, Nginx 1.25, Redis 7.0 +- ✨ Basic production configuration +- ✨ Docker and Docker Compose support --- -**Made with 💚 by KaririCode** – [https://kariricode.org/](https://kariricode.org/) \ No newline at end of file +
+ +**Made with 💚 by [KaririCode](https://kariricode.org)** + +[![KaririCode](https://img.shields.io/badge/KaririCode-Framework-green)](https://kariricode.org) +[![GitHub](https://img.shields.io/badge/GitHub-KaririCode-black)](https://github.com/KaririCode-Framework) + +
\ No newline at end of file diff --git a/VERSION b/VERSION index e516bb9..bc80560 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.5 +1.5.0 diff --git a/app/public/health.php b/app/public/health.php deleted file mode 100644 index 627a138..0000000 --- a/app/public/health.php +++ /dev/null @@ -1,3 +0,0 @@ -"healthy","timestamp"=>date("c")]); diff --git a/app/public/index.php b/app/public/index.php deleted file mode 100644 index 3a499df..0000000 --- a/app/public/index.php +++ /dev/null @@ -1,602 +0,0 @@ -performCheck(); - } catch (\Throwable $e) { - return new StatusResult( - healthy: false, - message: 'Error: ' . $e->getMessage(), - details: [] - ); - } - } -} - -/** - * PHP Runtime Check - */ -final class PhpCheck extends AbstractComponentCheck -{ - public function getName(): string - { - return 'PHP'; - } - - protected function performCheck(): StatusResult - { - $version = PHP_VERSION; - $extensions = get_loaded_extensions(); - - return new StatusResult( - healthy: true, - message: "PHP {$version} with " . count($extensions) . " extensions", - details: [ - 'version' => $version, - 'sapi' => PHP_SAPI, - 'extensions_count' => count($extensions), - 'memory_limit' => ini_get('memory_limit'), - 'max_execution_time' => ini_get('max_execution_time') . 's', - ] - ); - } -} - -/** - * OPcache Check - */ -final class OpcacheCheck extends AbstractComponentCheck -{ - public function getName(): string - { - return 'OPcache'; - } - - protected function performCheck(): StatusResult - { - if (!function_exists('opcache_get_status')) { - return new StatusResult( - healthy: false, - message: 'OPcache extension not available', - details: [] - ); - } - - $status = @opcache_get_status(false); - - if ($status === false) { - return new StatusResult( - healthy: false, - message: 'OPcache is disabled', - details: [] - ); - } - - $memoryUsed = $status['memory_usage']['used_memory'] ?? 0; - $memoryFree = $status['memory_usage']['free_memory'] ?? 0; - $memoryTotal = $memoryUsed + $memoryFree; - $usagePercent = $memoryTotal > 0 ? round(($memoryUsed / $memoryTotal) * 100, 1) : 0; - - return new StatusResult( - healthy: true, - message: "OPcache enabled ({$usagePercent}% memory used)", - details: [ - 'memory_used' => $this->formatBytes($memoryUsed), - 'cached_scripts' => $status['opcache_statistics']['num_cached_scripts'] ?? 0, - 'jit_enabled' => ($status['jit']['enabled'] ?? false) ? 'Yes' : 'No', - ] - ); - } - - private function formatBytes(int $bytes): string - { - $units = ['B', 'KB', 'MB', 'GB']; - $bytes = max($bytes, 0); - $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); - $pow = min($pow, count($units) - 1); - $bytes /= (1 << (10 * $pow)); - return round($bytes, 2) . ' ' . $units[$pow]; - } -} - -/** - * Redis Connectivity Check - */ -final class RedisCheck extends AbstractComponentCheck -{ - public function getName(): string - { - return 'Redis'; - } - - protected function performCheck(): StatusResult - { - if (!extension_loaded('redis')) { - return new StatusResult( - healthy: false, - message: 'Redis extension not installed', - details: [] - ); - } - - $redis = new \Redis(); - - try { - $connected = @$redis->connect('127.0.0.1', 6379, 1.0); - - if (!$connected) { - return new StatusResult( - healthy: false, - message: 'Cannot connect to Redis server', - details: [] - ); - } - - $pong = $redis->ping(); - - if ($pong !== true && $pong !== '+PONG') { - return new StatusResult( - healthy: false, - message: 'Redis ping failed', - details: [] - ); - } - - $info = $redis->info(); - - return new StatusResult( - healthy: true, - message: 'Redis ' . ($info['redis_version'] ?? 'unknown') . ' connected', - details: [ - 'version' => $info['redis_version'] ?? 'unknown', - 'uptime' => $this->formatUptime((int)($info['uptime_in_seconds'] ?? 0)), - 'memory' => $info['used_memory_human'] ?? 'unknown', - ] - ); - } catch (\Throwable $e) { - return new StatusResult( - healthy: false, - message: 'Redis error: ' . $e->getMessage(), - details: [] - ); - } finally { - @$redis->close(); - } - } - - private function formatUptime(int $seconds): string - { - if ($seconds < 60) return "{$seconds}s"; - if ($seconds < 3600) return floor($seconds / 60) . "m"; - if ($seconds < 86400) return floor($seconds / 3600) . "h"; - return floor($seconds / 86400) . "d"; - } -} - -/** - * Status Dashboard (Facade Pattern) - */ -final class StatusDashboard -{ - /** @var ComponentCheckInterface[] */ - private array $checks = []; - - public function addCheck(ComponentCheckInterface $check): self - { - $this->checks[] = $check; - return $this; - } - - public function runAll(): array - { - $results = []; - $allHealthy = true; - - foreach ($this->checks as $check) { - $result = $check->check(); - $results[$check->getName()] = $result; - - if (!$result->healthy) { - $allHealthy = false; - } - } - - return [ - 'overall_healthy' => $allHealthy, - 'checks' => $results, - ]; - } -} - -// Run status checks -$dashboard = new StatusDashboard(); -$dashboard - ->addCheck(new PhpCheck()) - ->addCheck(new OpcacheCheck()) - ->addCheck(new RedisCheck()); - -$status = $dashboard->runAll(); - -// Collect system information -$stackInfo = [ - 'image' => 'kariricode/php-api-stack', - 'version' => getenv('STACK_VERSION') ?: '1.2.1', - 'php_version' => PHP_VERSION, - 'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'nginx', - 'document_root' => $_SERVER['DOCUMENT_ROOT'] ?? '/var/www/html/public', -]; - -?> - - - - - - - PHP API Stack - Ready for Development - - - - -
-
-

🚀 PHP API Stack

-

Production-ready development environment

- Version -
- -
-
-

⚠️ Demo Mode Active

-

- This is a placeholder page shown when no application is mounted. - Mount your Symfony/Laravel project to - to replace this demo page with your application. -

-
- -

Component Status

- -
- $result): ?> -
-

- - -

-
- message) ?> -
- details)): ?> -
- details as $key => $value): ?> -
:
- -
- -
- -
- -
-

Stack Information

-
-
- Docker Image: - -
-
- Stack Version: - -
-
- PHP Version: - -
-
- Web Server: - -
-
- Document Root: - -
-
- Overall Status: - - - -
-
-
- -
-

Quick Links

- -
-
- - -
- - - \ No newline at end of file diff --git a/build-from-env.sh b/build-from-env.sh index cd9414f..16658b7 100755 --- a/build-from-env.sh +++ b/build-from-env.sh @@ -1,7 +1,7 @@ #!/bin/bash # Build script for kariricode/php-api-stack -# This script reads all configurations from .env file and builds the Docker image -# Usage: ./build-from-env.sh [--push] [--no-cache] +# Architecture: Base → Production | Dev +# Usage: ./build-from-env.sh [OPTIONS] set -euo pipefail @@ -19,12 +19,14 @@ CYAN='\033[0;36m' WHITE='\033[1;37m' NC='\033[0m' # No Color -# Parse command line arguments +# Default values PUSH_TO_HUB=false NO_CACHE=false MULTI_PLATFORM=false -TEST_BUILD=false +BUILD_TARGET="production" # default: production +VERSION_ARG="" +# Parse command line arguments for arg in "$@"; do case $arg in --push) @@ -39,8 +41,8 @@ for arg in "$@"; do MULTI_PLATFORM=true shift ;; - --test) - TEST_BUILD=true + --target=*) + BUILD_TARGET="${arg#*=}" shift ;; --version=*) @@ -50,13 +52,21 @@ for arg in "$@"; do --help) echo "Usage: $0 [OPTIONS]" echo "" + echo "Build Architecture: Base → Production | Dev" + echo "" echo "Options:" - echo " --push Push image to Docker Hub after build" - echo " --no-cache Build without using cache" - echo " --multi-platform Build for multiple platforms (amd64, arm64)" - echo " --test Build with comprehensive health check" # <-- ADICIONAR - echo " --version=X.Y.Z Override version instead of using VERSION file" - echo " --help Show this help message" + echo " --target=TARGET Build target stage: base, production, dev (default: production)" + echo " --push Push image to Docker Hub after build" + echo " --no-cache Build without using cache" + echo " --multi-platform Build for multiple platforms (amd64, arm64)" + echo " --version=X.Y.Z Override version instead of using VERSION file" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " $0 # Build production image" + echo " $0 --target=dev # Build dev image with Xdebug" + echo " $0 --target=base # Build base layer only" + echo " $0 --target=production --push # Build and push production" exit 0 ;; *) @@ -67,7 +77,16 @@ for arg in "$@"; do esac done - +# Validate build target +case $BUILD_TARGET in + base|production|dev) + ;; + *) + echo -e "${RED}Invalid build target: $BUILD_TARGET${NC}" + echo "Valid targets: base, production, dev" + exit 1 + ;; +esac # Functions log_info() { @@ -108,7 +127,14 @@ fi # Load .env file log_step "Loading configuration from .env..." set -a -source .env +while IFS='=' read -r key value; do + # Skip comments and empty lines + [[ $key =~ ^#.*$ ]] || [[ -z $key ]] && continue + # Remove leading/trailing whitespace from value + value=$(echo "$value" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + # Export the variable + export "$key=$value" +done < .env set +a # Set default values if not defined in .env @@ -130,12 +156,13 @@ VCS_REF=$(git rev-parse --short HEAD 2>/dev/null || echo "no-git") # Display build configuration echo "" -echo -e "${MAGENTA}╔══════════════════════════════════════════════════════╗${NC}" -echo -e "${MAGENTA}║ PHP API Stack - Docker Build Configuration ║${NC}" -echo -e "${MAGENTA}╚══════════════════════════════════════════════════════╝${NC}" +echo -e "${MAGENTA}╔═══════════════════════════════════════════════════╗${NC}" +echo -e "${MAGENTA}║ PHP API Stack - Docker Build Configuration ║${NC}" +echo -e "${MAGENTA}╚═══════════════════════════════════════════════════╝${NC}" echo "" +echo -e "${WHITE}Architecture:${NC} Base → Production | Dev" +echo -e "${WHITE}Build Target:${NC} ${CYAN}${BUILD_TARGET}${NC}" echo -e "${WHITE}Image:${NC} ${FULL_IMAGE}:${VERSION}" -echo -e "${WHITE}Environment:${NC} ${APP_ENV}" echo "" echo -e "${YELLOW}Stack Versions:${NC}" echo " • PHP: ${PHP_VERSION}" @@ -143,17 +170,24 @@ echo " • Nginx: ${NGINX_VERSION}" echo " • Redis: ${REDIS_VERSION}" echo " • Alpine: ${ALPINE_VERSION}" echo " • Composer: ${COMPOSER_VERSION}" -echo " • Symfony CLI: ${SYMFONY_CLI_VERSION}" + +if [ "$BUILD_TARGET" = "dev" ]; then + echo " • Symfony CLI: ${SYMFONY_CLI_VERSION}" + echo " • Xdebug: ${XDEBUG_VERSION}" +fi + echo "" echo -e "${YELLOW}PHP Extensions:${NC}" echo " • Core: ${PHP_CORE_EXTENSIONS}" echo " • PECL: ${PHP_PECL_EXTENSIONS}" echo "" -echo -e "${YELLOW}Build Options:${NC}" -echo " • No Cache: ${NO_CACHE}" -echo " • Push to Hub: ${PUSH_TO_HUB}" -echo " • Multi-Platform: ${MULTI_PLATFORM}" -echo "" +echo -e "${YELLOW}PECL Versions:${NC}" +echo " • Redis: ${PHP_REDIS_VERSION}" +echo " • APCu: ${PHP_APCU_VERSION}" +echo " • UUID: ${PHP_UUID_VERSION}" +echo " • ImageMagick: ${PHP_IMAGICK_VERSION}" +echo " • AMQP: ${PHP_AMQP_VERSION}" +echo "" # Confirm build read -p "$(echo -e ${GREEN}Proceed with build? [Y/n]: ${NC})" -n 1 -r @@ -164,7 +198,7 @@ if [[ $REPLY =~ ^[Nn]$ ]]; then fi # Prepare build command -log_step "Preparing build command..." +log_step "Preparing build command for target: ${BUILD_TARGET}..." # Base build command BUILD_CMD="docker" @@ -173,7 +207,6 @@ BUILD_CMD="docker" if [ "$MULTI_PLATFORM" = true ]; then log_info "Setting up Docker buildx for multi-platform build..." - # Create builder if it doesn't exist if ! docker buildx ls | grep -q "php-api-stack-builder"; then docker buildx create --name php-api-stack-builder --use docker buildx inspect --bootstrap @@ -195,8 +228,7 @@ if [ "$MULTI_PLATFORM" = true ]; then BUILD_CMD="$BUILD_CMD $PLATFORM_ARG" fi -# Add no-cache if requested -# Add no-cache if requested or guard cache-from +# Add no-cache or cache-from if [ "$NO_CACHE" = true ]; then BUILD_CMD="$BUILD_CMD --no-cache" else @@ -207,72 +239,61 @@ else fi fi - -# Add build arguments +# Common build arguments BUILD_CMD="$BUILD_CMD \ --build-arg PHP_VERSION=${PHP_VERSION} \ --build-arg NGINX_VERSION=${NGINX_VERSION} \ --build-arg REDIS_VERSION=${REDIS_VERSION} \ --build-arg ALPINE_VERSION=${ALPINE_VERSION} \ --build-arg COMPOSER_VERSION=${COMPOSER_VERSION} \ - --build-arg SYMFONY_CLI_VERSION=${SYMFONY_CLI_VERSION} \ - --build-arg PHP_CORE_EXTENSIONS=\"${PHP_CORE_EXTENSIONS}\" \ - --build-arg PHP_PECL_EXTENSIONS=\"${PHP_PECL_EXTENSIONS}\" \ - --build-arg APP_NAME=\"${APP_NAME}\" \ - --build-arg APP_ENV=${APP_ENV} \ - --build-arg APP_PORT=${APP_PORT} \ + --build-arg PHP_CORE_EXTENSIONS=${PHP_CORE_EXTENSIONS} \ + --build-arg PHP_PECL_EXTENSIONS=${PHP_PECL_EXTENSIONS} \ + --build-arg VERSION=${VERSION} \ --build-arg BUILD_DATE=\"${BUILD_DATE}\" \ --build-arg VCS_REF=\"${VCS_REF}\"" -# Add install PHP tools flag based on environment -if [ "${APP_ENV}" = "development" ]; then - BUILD_CMD="$BUILD_CMD --build-arg INSTALL_PHP_TOOLS=true" -else - BUILD_CMD="$BUILD_CMD --build-arg INSTALL_PHP_TOOLS=false" -fi - -# Add health check type for test builds -if [ "$TEST_BUILD" = true ]; then - BUILD_CMD="$BUILD_CMD --build-arg HEALTH_CHECK_TYPE=comprehensive" - log_info "Building with comprehensive health check" -fi - -# Add tags -BUILD_CMD="$BUILD_CMD \ - --tag ${FULL_IMAGE}:${VERSION} \ - --tag ${FULL_IMAGE}:latest" - -# Add minor and major version tags -MAJOR_VERSION=$(echo $VERSION | cut -d. -f1) -MINOR_VERSION=$(echo $VERSION | cut -d. -f1-2) - -# Add tags -if [ "$TEST_BUILD" = true ]; then - BUILD_CMD="$BUILD_CMD \ - --tag ${FULL_IMAGE}:test \ - --tag ${FULL_IMAGE}:test-${VERSION}" - log_info "Using test tags: test, test-${VERSION}" -else - BUILD_CMD="$BUILD_CMD \ - --tag ${FULL_IMAGE}:${VERSION} \ - --tag ${FULL_IMAGE}:latest" - - # Add minor and major version tags - MAJOR_VERSION=$(echo $VERSION | cut -d. -f1) - MINOR_VERSION=$(echo $VERSION | cut -d. -f1-2) - BUILD_CMD="$BUILD_CMD \ - --tag ${FULL_IMAGE}:${MAJOR_VERSION} \ - --tag ${FULL_IMAGE}:${MINOR_VERSION}" -fi - -# Add environment-specific tag -if [ "${APP_ENV}" = "development" ]; then - BUILD_CMD="$BUILD_CMD --tag ${FULL_IMAGE}:dev" -elif [ "${APP_ENV}" = "staging" ]; then - BUILD_CMD="$BUILD_CMD --tag ${FULL_IMAGE}:staging" -elif [ "${APP_ENV}" = "production" ]; then - BUILD_CMD="$BUILD_CMD --tag ${FULL_IMAGE}:stable" -fi +# Target-specific build arguments and tags +case $BUILD_TARGET in + base) + BUILD_CMD="$BUILD_CMD --target base" + BUILD_CMD="$BUILD_CMD --tag ${FULL_IMAGE}:base" + ;; + + production) + BUILD_CMD="$BUILD_CMD --target production" + BUILD_CMD="$BUILD_CMD \ + --build-arg APP_ENV=production \ + --build-arg PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + --build-arg PHP_OPCACHE_MAX_ACCELERATED_FILES=20000 \ + --build-arg PHP_OPCACHE_ENABLE=1 \ + --build-arg PHP_OPCACHE_MEMORY_CONSUMPTION=256" + + # Production tags + BUILD_CMD="$BUILD_CMD \ + --tag ${FULL_IMAGE}:${VERSION} \ + --tag ${FULL_IMAGE}:latest" + + MAJOR_VERSION=$(echo $VERSION | cut -d. -f1) + MINOR_VERSION=$(echo $VERSION | cut -d. -f1-2) + BUILD_CMD="$BUILD_CMD \ + --tag ${FULL_IMAGE}:${MAJOR_VERSION} \ + --tag ${FULL_IMAGE}:${MINOR_VERSION}" + ;; + + dev) + BUILD_CMD="$BUILD_CMD --target dev" + BUILD_CMD="$BUILD_CMD \ + --build-arg APP_ENV=development \ + --build-arg SYMFONY_CLI_VERSION=${SYMFONY_CLI_VERSION} \ + --build-arg XDEBUG_VERSION=${XDEBUG_VERSION} \ + --build-arg XDEBUG_ENABLE=1" + + # Dev tags + BUILD_CMD="$BUILD_CMD \ + --tag ${FULL_IMAGE}:dev \ + --tag ${FULL_IMAGE}:dev-${VERSION}" + ;; +esac # Add push flag if multi-platform and push requested if [ "$MULTI_PLATFORM" = true ] && [ "$PUSH_TO_HUB" = true ]; then @@ -283,7 +304,7 @@ fi BUILD_CMD="$BUILD_CMD --file Dockerfile ." # Execute build -log_step "Building Docker image..." +log_step "Building Docker image (target: ${BUILD_TARGET})..." echo -e "${BLUE}Command:${NC} $BUILD_CMD" echo "" @@ -307,103 +328,95 @@ if [ $? -eq 0 ]; then log_step "Image information:" docker images ${FULL_IMAGE} --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" - # Get image size - IMAGE_SIZE=$(docker images ${FULL_IMAGE}:latest --format "{{.Size}}") + IMAGE_SIZE=$(docker images ${FULL_IMAGE}:${BUILD_TARGET} --format "{{.Size}}" 2>/dev/null || echo "N/A") echo "" echo -e "${GREEN}Image size:${NC} ${IMAGE_SIZE}" fi - # Test the image if not multi-platform - if [ "$MULTI_PLATFORM" = false ]; then + # Test the image if not multi-platform and not base + if [ "$MULTI_PLATFORM" = false ] && [ "$BUILD_TARGET" != "base" ]; then echo "" log_step "Testing image..." - # Quick test - docker run --rm ${FULL_IMAGE}:${VERSION} php -v > /dev/null 2>&1 - if [ $? -eq 0 ]; then - log_info "✓ PHP test passed" - else - log_warning "PHP test failed" - fi + TEST_TAG="${BUILD_TARGET}" + [ "$BUILD_TARGET" = "production" ] && TEST_TAG="latest" - docker run --rm ${FULL_IMAGE}:${VERSION} nginx -v > /dev/null 2>&1 - if [ $? -eq 0 ]; then - log_info "✓ Nginx test passed" - else - log_warning "Nginx test failed" - fi + # Quick tests + docker run --rm ${FULL_IMAGE}:${TEST_TAG} php -v > /dev/null 2>&1 && log_info "✓ PHP test passed" + docker run --rm ${FULL_IMAGE}:${TEST_TAG} nginx -v > /dev/null 2>&1 && log_info "✓ Nginx test passed" + docker run --rm ${FULL_IMAGE}:${TEST_TAG} redis-server --version > /dev/null 2>&1 && log_info "✓ Redis test passed" - docker run --rm ${FULL_IMAGE}:${VERSION} redis-server --version > /dev/null 2>&1 - if [ $? -eq 0 ]; then - log_info "✓ Redis test passed" - else - log_warning "Redis test failed" + if [ "$BUILD_TARGET" = "dev" ]; then + docker run --rm ${FULL_IMAGE}:dev php -m | grep -q xdebug && log_info "✓ Xdebug installed" + docker run --rm ${FULL_IMAGE}:dev symfony version > /dev/null 2>&1 && log_info "✓ Symfony CLI installed" fi fi - # Push to Docker Hub if requested and not already pushed (multi-platform) + # Push to Docker Hub if requested if [ "$PUSH_TO_HUB" = true ] && [ "$MULTI_PLATFORM" = false ]; then echo "" log_step "Pushing to Docker Hub..." - # Check if logged in if ! docker info 2>/dev/null | grep -q "Username"; then log_warning "Not logged in to Docker Hub. Logging in..." docker login -u ${DOCKER_HUB_USER} - - if [ $? -ne 0 ]; then - log_error "Failed to login to Docker Hub" - fi + [ $? -ne 0 ] && log_error "Failed to login to Docker Hub" fi - # Push all tags - for tag in ${VERSION} latest ${MAJOR_VERSION} ${MINOR_VERSION}; do - log_info "Pushing ${FULL_IMAGE}:${tag}..." - docker push ${FULL_IMAGE}:${tag} - done - - # Push environment-specific tag - if [ "${APP_ENV}" = "development" ]; then - docker push ${FULL_IMAGE}:dev - elif [ "${APP_ENV}" = "staging" ]; then - docker push ${FULL_IMAGE}:staging - elif [ "${APP_ENV}" = "production" ]; then - docker push ${FULL_IMAGE}:stable - fi + case $BUILD_TARGET in + production) + for tag in ${VERSION} latest ${MAJOR_VERSION} ${MINOR_VERSION}; do + log_info "Pushing ${FULL_IMAGE}:${tag}..." + docker push ${FULL_IMAGE}:${tag} + done + ;; + dev) + log_info "Pushing ${FULL_IMAGE}:dev..." + docker push ${FULL_IMAGE}:dev + docker push ${FULL_IMAGE}:dev-${VERSION} + ;; + base) + log_info "Pushing ${FULL_IMAGE}:base..." + docker push ${FULL_IMAGE}:base + ;; + esac echo "" log_info "✅ Push completed!" - echo "" - echo -e "${GREEN}Image available at:${NC}" - echo " https://hub.docker.com/r/${FULL_IMAGE}" + echo -e "${GREEN}Image available at:${NC} https://hub.docker.com/r/${FULL_IMAGE}" fi # Show usage instructions echo "" - echo -e "${CYAN}═══ Usage Instructions ═══${NC}" + echo -e "${CYAN}━━━ Usage Instructions ━━━${NC}" echo "" - echo "To run the container:" - echo -e " ${YELLOW}docker run -d -p ${APP_PORT}:80 ${FULL_IMAGE}:${VERSION}${NC}" + + case $BUILD_TARGET in + production) + echo "To run the production container:" + echo -e " ${YELLOW}docker run -d -p 8080:80 ${FULL_IMAGE}:latest${NC}" + ;; + dev) + echo "To run the dev container with Xdebug:" + echo -e " ${YELLOW}docker run -d -p 8080:80 -p 9003:9003 -e XDEBUG_ENABLE=1 ${FULL_IMAGE}:dev${NC}" + ;; + base) + echo "Base image built successfully (foundation layer)" + echo "Use as base for production or dev stages" + ;; + esac + echo "" echo "To pull from Docker Hub:" - echo -e " ${YELLOW}docker pull ${FULL_IMAGE}:latest${NC}" + echo -e " ${YELLOW}docker pull ${FULL_IMAGE}:${BUILD_TARGET}${NC}" echo "" - if [ "$PUSH_TO_HUB" = false ]; then - echo "To push to Docker Hub:" - echo -e " ${YELLOW}docker push ${FULL_IMAGE}:${VERSION}${NC}" - echo "Or run this script with --push flag:" - echo -e " ${YELLOW}./build-from-env.sh --push${NC}" - echo "" - fi - else log_error "❌ Build failed! Check the error messages above." fi # Cleanup buildx if it was created if [ "$MULTI_PLATFORM" = true ]; then - # Don't remove the builder, keep it for future use log_info "Builder 'php-api-stack-builder' kept for future use" log_info "To remove: docker buildx rm php-api-stack-builder" fi diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 7faf4c6..4791a35 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -5,6 +5,7 @@ services: restart: unless-stopped environment: - DEMO_MODE=true + - HEALTH_CHECK_INSTALL=true env_file: - .env ports: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index e87747d..d37cd95 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,7 +1,9 @@ #!/bin/bash set -e +# ============================================================================ # Colors for output +# ============================================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' @@ -20,10 +22,10 @@ log_error() { exit 1 } -# ============================================================================== -# BYPASS RÁPIDO PARA COMANDOS SIMPLES -# ============================================================================== -# Se for apenas verificação de versão ou comando direto, executar sem processamento +# ============================================================================ +# QUICK BYPASS FOR SIMPLE COMMANDS +# ============================================================================ +# If it's only a version check or a direct shell, execute without any processing if [[ "$1" == "php" && "$2" == "-v" ]] || \ [[ "$1" == "php" && "$2" == "--version" ]] || \ [[ "$1" == "nginx" && "$2" == "-v" ]] || \ @@ -35,23 +37,23 @@ if [[ "$1" == "php" && "$2" == "-v" ]] || \ exec "$@" fi -# Se for comando básico sem argumentos complexos +# If it's a basic shell command if [[ "$1" == "bash" ]] || [[ "$1" == "sh" ]] || [[ "$1" == "/bin/bash" ]] || [[ "$1" == "/bin/sh" ]]; then exec "$@" fi -# ============================================================================== -# PROCESSAMENTO COMPLETO PARA EXECUÇÃO NORMAL -# ============================================================================== +# ============================================================================ +# FULL INITIALIZATION FOR NORMAL EXECUTION +# ============================================================================ -# Function to wait for a service to be ready +# Wait until a service is ready (basic checks) wait_for_service() { local service=$1 local max_attempts=${2:-30} local attempt=0 - + log_info "Waiting for $service to be ready..." - + case $service in "php-fpm") while [ $attempt -lt $max_attempts ]; do @@ -84,15 +86,16 @@ wait_for_service() { done ;; esac - + log_warning "$service failed to start after $max_attempts attempts" return 1 } -# Check if configurations need to be processed +# ---------------------------------------------------------------------------- +# Process configuration templates (once per container lifetime) +# ---------------------------------------------------------------------------- CONFIG_PROCESSED_FLAG="/tmp/.config_processed" if [ ! -f "$CONFIG_PROCESSED_FLAG" ]; then - # Process configuration templates log_info "Processing configuration templates..." /usr/local/bin/process-configs touch "$CONFIG_PROCESSED_FLAG" @@ -100,36 +103,40 @@ else log_info "Configurations already processed, skipping..." fi - -# optimization for production environment to force static PHP-FPM mode +# ---------------------------------------------------------------------------- +# Production optimization: enforce static PHP-FPM in prod +# ---------------------------------------------------------------------------- if [ "${APP_ENV}" = "production" ] || [ "${APP_ENV}" = "prod" ]; then if [ "${PHP_FPM_PM}" != "static" ]; then log_info "Environment is production. Overriding PHP_FPM_PM to 'static' for peak performance." export PHP_FPM_PM="static" fi + # Guard low pm.max_children (only warns) if [ -z "${PHP_FPM_PM_MAX_CHILDREN}" ] || [ "${PHP_FPM_PM_MAX_CHILDREN}" -lt 50 ]; then - log_warning "PHP_FPM_PM_MAX_CHILDREN is low for production static mode. Recommended >= 50." + log_warning "PHP_FPM_PM_MAX_CHILDREN may be low for production static mode. Recommended >= 50." fi else log_info "Environment is non-production. Using dynamic PHP-FPM settings." fi - -# Create required directories +# ---------------------------------------------------------------------------- +# Create required directories and fix permissions +# ---------------------------------------------------------------------------- log_info "Creating required directories..." chown -R nginx:nginx /var/log/php /var/log/nginx /var/run/php chown -R redis:redis /var/log/redis || true -# chown -R root:root /var/log/supervisor -chown -R nginx:nginx /var/run/nginx # Nginx run dir +chown -R nginx:nginx /run/nginx -# Fix permissions for session directory if using files +# Session directory (file handler) if [ "${PHP_SESSION_SAVE_HANDLER}" = "files" ]; then mkdir -p /var/lib/php/sessions chown -R nginx:nginx /var/lib/php/sessions chmod 700 /var/lib/php/sessions fi -# Validate configurations apenas se não foram validadas ainda +# ---------------------------------------------------------------------------- +# Validate configurations (only once) +# ---------------------------------------------------------------------------- VALIDATION_FLAG="/tmp/.config_validated" if [ ! -f "$VALIDATION_FLAG" ]; then log_info "Validating configurations..." @@ -138,11 +145,13 @@ if [ ! -f "$VALIDATION_FLAG" ]; then touch "$VALIDATION_FLAG" fi -# Handle Symfony-specific initialization +# ---------------------------------------------------------------------------- +# Symfony bootstrap (if app detected) +# ---------------------------------------------------------------------------- if [ -f "/var/www/html/bin/console" ]; then log_info "Symfony application detected" - - # Clear and warm up cache + + # Cache warmup (prod only) if [ "${APP_ENV}" = "production" ] || [ "${APP_ENV}" = "prod" ]; then CACHE_FLAG="/tmp/.symfony_cache_warmed" if [ ! -f "$CACHE_FLAG" ]; then @@ -152,15 +161,15 @@ if [ -f "/var/www/html/bin/console" ]; then touch "$CACHE_FLAG" fi fi - - # Run migrations if configured + + # Database migrations (optional) if [ "${RUN_MIGRATIONS}" = "true" ]; then log_info "Running database migrations..." su -s /bin/bash -c "php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration" nginx || \ log_warning "Migration failed or no migrations to run" fi - - # Install assets + + # Assets (optional) if [ -d "/var/www/html/public/bundles" ]; then ASSETS_FLAG="/tmp/.symfony_assets_installed" if [ ! -f "$ASSETS_FLAG" ]; then @@ -171,69 +180,64 @@ if [ -f "/var/www/html/bin/console" ]; then fi fi -# Install demo index.php if no application is mounted AND DEMO_MODE is enabled -if [ ! -f "/var/www/html/public/index.php" ]; then - if [ "${DEMO_MODE}" = "true" ]; then - log_info "DEMO_MODE enabled. Installing demo landing page..." - - # Create directory if it doesn't exist - mkdir -p /var/www/html/public - - if [ -f "/usr/local/share/php-api-stack/index.php" ]; then - cp /usr/local/share/php-api-stack/index.php /var/www/html/public/index.php - log_info "Demo landing page installed" - else - log_warning "Demo template not found, creating basic fallback" - cat > /var/www/html/public/index.php << 'EOF' -/dev/null || true fi +# HEALTH_CHECK_INSTALL +if [ "${HEALTH_CHECK_INSTALL}" = "true" ] && [ -f "/opt/php-api-stack-templates/health.php" ]; then + log_info "Publishing health.php to /var/www/html/public" + mkdir -p /var/www/html/public + cp /opt/php-api-stack-templates/health.php /var/www/html/public/health.php + chown nginx:nginx /var/www/html/public/health.php + chmod 644 /var/www/html/public/health.php +else + rm -f /var/www/html/public/health.php 2>/dev/null || true +fi -# Create health check endpoint if doesn't exist -if [ ! -f "/var/www/html/public/health.php" ]; then - # Apenas instala health check se habilitado ou se DEMO_MODE estiver ativo - if [ "${HEALTH_CHECK_INSTALL}" = "true" ] || [ "${DEMO_MODE}" = "true" ]; then - log_info "Installing health check endpoint..." - - # Cria diretório se não existir - mkdir -p /var/www/html/public - - # Copy from template if exists, otherwise create basic fallback - if [ -f "/usr/local/share/php-api-stack/health.php" ]; then - cp /usr/local/share/php-api-stack/health.php /var/www/html/public/health.php - log_info "Health check installed from template" +# ----------------------------------------------------------------------------- +# XDEBUG +# ----------------------------------------------------------------------------- +XDEBUG_IS_ACTIVE=false +# Toggle Xdebug at runtime +if [ "${XDEBUG_ENABLE:-0}" = "1" ]; then + # First, check if the .so module is actually installed + if php -m | grep -q xdebug; then + # Module is installed. Now, apply the configuration. + if [ -f /usr/local/etc/php/conf.d/xdebug.ini.template ]; then + envsubst < /usr/local/etc/php/conf.d/xdebug.ini.template \ + > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + log_info "Xdebug enabled (module loaded and config applied)" + XDEBUG_IS_ACTIVE=true else - log_warning "Health check template not found, creating basic fallback" - cat > /var/www/html/public/health.php << 'EOF' - 'healthy', 'timestamp' => date('c')], JSON_PRETTY_PRINT); -EOF + # Module is installed, but the .ini is missing (bad state) + log_warning "Xdebug module is installed, but xdebug.ini.template is missing!" fi - - chown nginx:nginx /var/www/html/public/health.php - chmod 644 /var/www/html/public/health.php else - log_info "HEALTH_CHECK_INSTALL not enabled - skipping health check installation" + # XDEBUG_ENABLE=1, but the module was not found in the image. + log_warning "XDEBUG_ENABLE=1, but Xdebug module is not installed. Skipping." fi else - log_info "Health check already exists at /var/www/html/public/health.php" + # XDEBUG_ENABLE is not 1, so ensure it's disabled. + rm -f /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini 2>/dev/null || true + log_info "Xdebug disabled" fi -# Set up log rotation apenas uma vez + +# ---------------------------------------------------------------------------- +# Log rotate setup (only useful if logs are bind-mounted) +# ---------------------------------------------------------------------------- if [ ! -f "/etc/logrotate.d/php-api-stack" ]; then - cat > /etc/logrotate.d/php-api-stack << EOF + cat > /etc/logrotate.d/php-api-stack << 'EOF' /var/log/nginx/*.log { daily missingok @@ -244,7 +248,7 @@ if [ ! -f "/etc/logrotate.d/php-api-stack" ]; then create 640 nginx nginx sharedscripts postrotate - [ -f /var/run/nginx.pid ] && kill -USR1 \$(cat /var/run/nginx.pid) + [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid) endscript } @@ -270,61 +274,52 @@ if [ ! -f "/etc/logrotate.d/php-api-stack" ]; then EOF fi -# Performance tuning for production +# ---------------------------------------------------------------------------- +# Production tuning (best-effort inside containers) +# ---------------------------------------------------------------------------- if [ "${APP_ENV}" = "production" ] || [ "${APP_ENV}" = "prod" ]; then log_info "Applying production performance optimizations..." - - # Increase system limits ulimit -n 65536 2>/dev/null || log_warning "Could not set ulimit -n (normal in containers)" ulimit -c unlimited 2>/dev/null || true - - # TCP optimizations (geralmente não funcionam em containers, mas tentamos) - if [ -w /proc/sys/net/core/somaxconn ]; then - echo 1024 > /proc/sys/net/core/somaxconn - fi - - if [ -w /proc/sys/net/ipv4/tcp_max_syn_backlog ]; then - echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog - fi + if [ -w /proc/sys/net/core/somaxconn ]; then echo 1024 > /proc/sys/net/core/somaxconn; fi + if [ -w /proc/sys/net/ipv4/tcp_max_syn_backlog ]; then echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog; fi fi -# # Git safe directory fix for mounted volumes +# ---------------------------------------------------------------------------- +# Git safe directory fix for mounted volumes +# ---------------------------------------------------------------------------- if [ "$1" != "bash" ] && [ "$1" != "sh" ]; then - log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } - log_info "Fixing git safe directory for mounted volumes..." + log_info "Marking /var/www/html as a safe Git directory" git config --global --add safe.directory /var/www/html || true fi -# ============================================================================== +# ============================================================================ # START SERVICES BASED ON COMMAND -# ============================================================================== -# removed: -# supervisord|supervisor) -# log_info "Starting all services with Supervisor..." -# exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf -# ;; -# Especifics commands +# ============================================================================ case "$1" in start) log_info "Starting all services..." - - # Start Redis in background (daemonize) + + # Redis in background (daemonized) log_info " -> Starting Redis..." redis-server /etc/redis/redis.conf --daemonize yes - - # Start PHP-FPM in background (daemonize) + + # PHP-FPM in background (daemonized) log_info " -> Starting PHP-FPM..." + if [ "${XDEBUG_IS_ACTIVE}" = "true" ]; then + log_info " -> Xdebug is active" + fi php-fpm -D - - # Start Nginx in foreground. The `exec` command is crucial here. - # It replaces the script process with the Nginx process, making Nginx - # the main container process (PID 1). This ensures it receives - # Docker signals correctly (e.g., docker stop). + + # Nginx in foreground (PID 1) so it receives Docker signals (e.g., stop) log_info " -> Starting Nginx (foreground)..." exec nginx -g 'daemon off;' ;; php-fpm) log_info "Starting PHP-FPM only..." + if [ "${XDEBUG_IS_ACTIVE}" = "true" ]; then + log_info " -> Xdebug is active" + fi exec php-fpm -F ;; nginx) @@ -341,13 +336,8 @@ case "$1" in exec su -s /bin/bash -c "php bin/console $*" nginx ;; *) - # Default: start supervisord if no command given - # if [ "$#" -eq 0 ]; then - # log_info "Starting all services with Supervisor (default)..." - # exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf - # else - # Pass through any command - exec "$@" - # fi + # Pass through any command + exec "$@" ;; -esac \ No newline at end of file +esac + diff --git a/health.php b/php/health.php similarity index 96% rename from health.php rename to php/health.php index 52f6271..41ce9ae 100644 --- a/health.php +++ b/php/health.php @@ -333,6 +333,29 @@ public function getName(): string return 'redis'; } + /** + * Smart Redis Host Resolution + * + * Tenta resolver o hostname. Se falhar, usa 127.0.0.1 (standalone mode). + */ + protected function getRedisHost(): string + { + $host = getenv('REDIS_HOST') ?: '127.0.0.1'; + + // Se for "redis" (docker-compose), verifica se resolve + if ($host === 'redis') { + // Suprime warning de DNS + $resolved = @gethostbyname($host); + + // Se não resolveu (retorna o próprio hostname), usa localhost + if ($resolved === $host) { + return '127.0.0.1'; + } + } + + return $host; + } + protected function performCheck(): array { if (!extension_loaded('redis')) { @@ -346,12 +369,13 @@ protected function performCheck(): array $redis = new \Redis(); - $host = getenv('REDIS_HOST') ?: '127.0.0.1'; + $host = $this->getRedisHost(); $password = getenv('REDIS_PASSWORD') ?: null; + $port = 6379; try { $connectStart = microtime(true); - $connected = @$redis->connect('127.0.0.1', 6379, self::TIMEOUT); + $connected = @$redis->connect($host, $port, self::TIMEOUT); $connectDuration = (microtime(true) - $connectStart) * 1000; if (!$connected) { diff --git a/index.php b/php/index.php similarity index 96% rename from index.php rename to php/index.php index 3fb168b..4764b8d 100644 --- a/index.php +++ b/php/index.php @@ -159,6 +159,31 @@ public function getName(): string return 'Redis'; } + + /** + * Smart Redis Host Resolution + * + * Tenta resolver o hostname. Se falhar, usa 127.0.0.1 (standalone mode). + */ + protected function getRedisHost(): string + { + $host = getenv('REDIS_HOST') ?: '127.0.0.1'; + + // Se for "redis" (docker-compose), verifica se resolve + if ($host === 'redis') { + // Suprime warning de DNS + $resolved = @gethostbyname($host); + + // Se não resolveu (retorna o próprio hostname), usa localhost + if ($resolved === $host) { + return '127.0.0.1'; + } + } + + return $host; + } + + protected function performCheck(): StatusResult { if (!extension_loaded('redis')) { @@ -171,11 +196,12 @@ protected function performCheck(): StatusResult $redis = new \Redis(); - $host = getenv('REDIS_HOST') ?: '127.0.0.1'; + $host = $this->getRedisHost(); $password = getenv('REDIS_PASSWORD') ?: null; + $port = 6379; try { - $connected = @$redis->connect('127.0.0.1', 6379, 1.0); + $connected = @$redis->connect($host, $port, 1.0); if (!$connected) { return new StatusResult( diff --git a/php/xdebug.ini b/php/xdebug.ini new file mode 100644 index 0000000..aa10b84 --- /dev/null +++ b/php/xdebug.ini @@ -0,0 +1,12 @@ +; Xdebug (dev only) +zend_extension=xdebug + +xdebug.mode = ${XDEBUG_MODE} +xdebug.start_with_request = trigger +xdebug.client_host = ${XDEBUG_HOST} +xdebug.client_port = ${XDEBUG_PORT} +xdebug.idekey = ${XDEBUG_IDE_KEY} +xdebug.max_nesting_level = 256 +xdebug.discover_client_host = 0 +xdebug.output_dir = /tmp +xdebug.log = /var/log/php/xdebug.log diff --git a/redis/redis.conf b/redis/redis.conf index a9a1a12..02f9132 100644 --- a/redis/redis.conf +++ b/redis/redis.conf @@ -16,16 +16,14 @@ daemonize no pidfile /var/run/redis.pid loglevel ${REDIS_LOG_LEVEL} # In containers, send logs to STDOUT (empty string) -logfile /var/log/redis/redis.log +logfile ${REDIS_LOG_FILE} # Number of databases. Default: 16 databases ${REDIS_DATABASES} always-show-logo no # --- SNAPSHOTTING (RDB) --- -# Mantidas fixas; se quiser controlar via REDIS_SAVE, trate no entrypoint -save 900 1 -save 300 10 -save 60 10000 +# Controlled by REDIS_SAVE environment variable +save ${REDIS_SAVE} stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes diff --git a/scripts/process-configs.sh b/scripts/process-configs.sh index 6d9efc5..3730aba 100755 --- a/scripts/process-configs.sh +++ b/scripts/process-configs.sh @@ -2,7 +2,30 @@ # Process configuration templates with default values set -e +# ============================================================================ +# Colors for output +# ============================================================================ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" + exit 1 +} + +# ============================================================================ # Set default environment variables +# ============================================================================ export PHP_FPM_PM=${PHP_FPM_PM:-dynamic} export PHP_FPM_PM_MAX_CHILDREN=${PHP_FPM_PM_MAX_CHILDREN:-60} export PHP_FPM_PM_START_SERVERS=${PHP_FPM_PM_START_SERVERS:-5} @@ -32,13 +55,24 @@ export PHP_OPCACHE_REVALIDATE_FREQ=${PHP_OPCACHE_REVALIDATE_FREQ:-0} export PHP_OPCACHE_JIT=${PHP_OPCACHE_JIT:-tracing} export PHP_OPCACHE_JIT_BUFFER_SIZE=${PHP_OPCACHE_JIT_BUFFER_SIZE:-128M} export PHP_FPM_REQUEST_SLOWLOG_TIMEOUT=${PHP_FPM_REQUEST_SLOWLOG_TIMEOUT:-30s} +export PHP_PECL_EXTENSIONS="${PHP_PECL_EXTENSIONS:-redis apcu uuid}" +export PHP_CORE_EXTENSIONS="${PHP_CORE_EXTENSIONS:-pdo pdo_mysql opcache intl zip bcmath gd mbstring xml sockets}" +export ENABLE_CACHE=${ENABLE_CACHE:-true} +export ENABLE_COMPRESSION=${ENABLE_COMPRESSION:-true} +export ENABLE_HTTP2=${ENABLE_HTTP2:-true} +export ENABLE_METRICS=${ENABLE_METRICS:-true} +export ENABLE_HEALTH_CHECK=${ENABLE_HEALTH_CHECK:-true} +export SECURITY_HEADERS=${SECURITY_HEADERS:-true} +export HEALTH_CHECK_PATH=${HEALTH_CHECK_PATH:-/health} export METRICS_PHP_FPM_PORT=${METRICS_PHP_FPM_PORT:-9000} +export CACHE_TTL=${CACHE_TTL:-3600} export XDEBUG_MODE=${XDEBUG_MODE:-off} export XDEBUG_HOST=${XDEBUG_HOST:-host.docker.internal} export XDEBUG_PORT=${XDEBUG_PORT:-9003} -export XDEBUG_IDE_KEY=${XDEBUG_IDE_KEY:-PHPSTORM} +export XDEBUG_IDE_KEY=${XDEBUG_IDE_KEY:-VSCODE} +export XDEBUG_VERSION=${XDEBUG_VERSION:-3.4.6} export NGINX_WORKER_PROCESSES=${NGINX_WORKER_PROCESSES:-auto} export NGINX_WORKER_CONNECTIONS=${NGINX_WORKER_CONNECTIONS:-2048} @@ -57,7 +91,7 @@ export REDIS_LOG_LEVEL=${REDIS_LOG_LEVEL:-notice} export REDIS_LOG_FILE=${REDIS_LOG_FILE:-""} export REDIS_MAXMEMORY=${REDIS_MAXMEMORY:-256M} -export REDIS_MAXMEMORY_SAMPLES=${REDIS_MAXMEMORY_SAMPLES:-5} +export REDIS_MAXMEMORY_SAMPLES=${REDIS_MAXMEMORY_SAMPLES:-5} export REDIS_PASSWORD=${REDIS_PASSWORD:-} export REDIS_DATABASES=${REDIS_DATABASES:-16} export REDIS_MAXMEMORY_POLICY=${REDIS_MAXMEMORY_POLICY:-volatile-lru} @@ -88,6 +122,8 @@ $PHP_DISPLAY_ERRORS $PHP_ERROR_LOG $PHP_SESSION_SAVE_HANDLER $PHP_SESSION_SAVE_PATH +$PHP_PECL_EXTENSIONS +$PHP_CORE_EXTENSIONS $PHP_OPCACHE_ENABLE $PHP_OPCACHE_MEMORY $PHP_OPCACHE_MAX_FILES @@ -95,11 +131,20 @@ $PHP_OPCACHE_VALIDATE_TIMESTAMPS $PHP_OPCACHE_REVALIDATE_FREQ $PHP_OPCACHE_JIT $PHP_OPCACHE_JIT_BUFFER_SIZE +$ENABLE_CACHE +$ENABLE_COMPRESSION +$ENABLE_HTTP2 +$ENABLE_METRICS +$ENABLE_HEALTH_CHECK +$SECURITY_HEADERS +$HEALTH_CHECK_PATH $METRICS_PHP_FPM_PORT +$CACHE_TTL $XDEBUG_MODE $XDEBUG_HOST $XDEBUG_PORT $XDEBUG_IDE_KEY +$XDEBUG_VERSION $NGINX_WORKER_PROCESSES $NGINX_WORKER_CONNECTIONS $NGINX_KEEPALIVE_TIMEOUT @@ -124,7 +169,7 @@ $REDIS_TIMEOUT ' -echo "Ensuring log directories exist and have correct permissions for validation..." +log_info "Ensuring log directories exist and have correct permissions for validation..." # Create log directories mkdir -p /var/log/php /var/log/nginx /var/log/redis /var/log/supervisor /var/log/symfony /var/run/php /var/run/nginx @@ -134,68 +179,82 @@ chown -R nginx:nginx /var/log/php /var/log/nginx /var/log/symfony /var/run/php / chown -R redis:redis /var/log/redis || true chown -R root:root /var/log/supervisor -echo " ✓ Log directories created and permissions set" +log_info " ✓ Log directories created and permissions set" -echo "Processing configuration templates..." +log_info "Processing configuration templates..." # Process templates if [ -f /etc/nginx/nginx.conf.template ]; then envsubst "$VARS_TO_SUBSTITUTE" < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf - echo " ✓ Nginx main configuration processed" + log_info " ✓ Nginx main configuration processed" fi if [ -f /etc/nginx/conf.d/default.conf.template ]; then envsubst "$VARS_TO_SUBSTITUTE" < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf - echo " ✓ Nginx site configuration processed" + log_info " ✓ Nginx site configuration processed" fi if [ -f /usr/local/etc/php/php.ini.template ]; then envsubst "$VARS_TO_SUBSTITUTE" < /usr/local/etc/php/php.ini.template > /usr/local/etc/php/php.ini - echo " ✓ PHP.ini configuration processed" + log_info " ✓ PHP.ini configuration processed" fi # Process PHP-FPM global configuration if template exists if [ -f /usr/local/etc/php-fpm.conf.template ]; then envsubst "$VARS_TO_SUBSTITUTE" < /usr/local/etc/php-fpm.conf.template > /usr/local/etc/php-fpm.conf - echo " ✓ PHP-FPM global configuration processed" + log_info " ✓ PHP-FPM global configuration processed" fi if [ -f /usr/local/etc/php-fpm.d/www.conf.template ]; then envsubst "$VARS_TO_SUBSTITUTE" < /usr/local/etc/php-fpm.d/www.conf.template > /usr/local/etc/php-fpm.d/www.conf - echo " ✓ PHP-FPM pool configuration processed" + log_info " ✓ PHP-FPM pool configuration processed" fi if [ -f /usr/local/etc/php-fpm.d/monitoring.conf.template ]; then envsubst "$VARS_TO_SUBSTITUTE" < /usr/local/etc/php-fpm.d/monitoring.conf.template > /usr/local/etc/php-fpm.d/monitoring.conf - echo " ✓ PHP-FPM monitoring pool configuration processed" + log_info " ✓ PHP-FPM monitoring pool configuration processed" fi if [ -f /etc/redis/redis.conf.template ]; then envsubst "$VARS_TO_SUBSTITUTE" < /etc/redis/redis.conf.template > /etc/redis/redis.conf - echo " ✓ Redis configuration processed" + log_info " ✓ Redis configuration processed" fi -echo "Configuration templates processed successfully." +if [ "${XDEBUG_ENABLE:-0}" = "1" ]; then + if [ -f /usr/local/etc/php/conf.d/xdebug.ini.template ]; then + envsubst "$VARS_TO_SUBSTITUTE" < /usr/local/etc/php/conf.d/xdebug.ini.template \ + > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + log_info " ✓ Xdebug configuration processed (enabled)" + else + log_warning " ⚠ Xdebug enabled, but template not found: /usr/local/etc/php/conf.d/xdebug.ini.template" + fi +else + rm -f /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini 2>/dev/null || true + log_info " ⟳ Xdebug disabled (no ini applied)" +fi + + +log_info "Configuration templates processed successfully." # Validate configurations -echo "Validating configurations..." +log_info "Validating configurations..." # Validate PHP-FPM if php-fpm -t 2>/dev/null; then - echo " ✓ PHP-FPM configuration is valid" + log_info " ✓ PHP-FPM configuration is valid" else - echo " ✗ PHP-FPM configuration test failed!" + log_error " ✗ PHP-FPM configuration test failed!" php-fpm -t exit 1 fi # Validate Nginx if nginx -t 2>/dev/null; then - echo " ✓ Nginx configuration is valid" + log_info " ✓ Nginx configuration is valid" else - echo " ✗ Nginx configuration test failed!" + log_error " ✗ Nginx configuration test failed!" nginx -t exit 1 fi -echo "All configurations are valid." \ No newline at end of file +log_info "All configurations are valid."