A production-ready Docker environment for high-performance PHP web development, based on Alpine Linux, Nginx, and PHP-FPM 8.4. This setup provides a secure, efficient, and fully configurable environment suitable for various PHP applications.
- Alpine Linux 3.22 - Minimal and secure base image with pinned versions
- Nginx - High-performance web server with optimized configuration
- PHP-FPM 8.4 - With comprehensive extension support
- Composer - Automatic dependency installation on startup
- Configurable - Environment-based configuration (development/production)
- Best Practices - Security, performance, and comprehensive logging
- Xdebug - Full debugging support in development mode
- Docker (20.10 or higher)
- Docker Compose V2
- Make (optional, but recommended)
# With Make
make setup
# Or manually
mkdir -p app/src data logs/{app,nginx,php} vendor
cp .env.example .envEdit the .env file:
# Environment: development or production
ENV=development
# Nginx Port
NGINX_PORT=8080
# Log Directory
LOG_DIR=./logs
# Data Directory (for application data storage)
DATA_DIR=./data
# User/Group IDs (recommended: set to your host user)
USER_ID=1000
GROUP_ID=1000# With Make
make build
make up
# Or with Docker Compose
docker compose build
docker compose up -dYour application is now available at http://localhost:8080.
.
├── docker/
│ ├── nginx/
│ │ ├── Dockerfile
│ │ ├── nginx.conf
│ │ └── conf.d/
│ │ └── default.conf
│ └── php/
│ ├── Dockerfile
│ ├── docker-entrypoint.sh
│ ├── php.ini # Base configuration
│ ├── php-fpm.conf
│ └── conf.d/
│ ├── development.ini # Development overrides
│ ├── production.ini # Production overrides
│ └── xdebug.ini # Xdebug configuration (dev only)
├── app/
│ ├── public/ # Document Root (web-accessible files)
│ │ └── index.php
│ └── src/ # Application logic (classes, services)
├── data/ # Application data storage (mounted to /var/www/html/data)
├── logs/
│ ├── app/ # Application logs
│ ├── nginx/ # Nginx access and error logs
│ └── php/ # PHP-FPM logs
├── vendor/ # Composer dependencies (stored in named volume)
├── composer.json
├── composer.lock
├── .env
├── compose.yaml # Base configuration
├── compose.override.yaml # Development configuration (auto-loaded)
├── compose.prod.yaml # Production configuration
├── Makefile
└── README.md
make help # Show all available commands
make setup # Create project structure
make build # Build Docker images
make up # Start containers
make down # Stop containers
make restart # Restart containers
make logs # Show all logs
make logs-php # Show PHP logs only
make logs-nginx # Show Nginx logs only
make shell-php # Open shell in PHP container
make shell-nginx # Open shell in Nginx container
make composer # Execute Composer command
make clean # Clean up containers and volumes
make rebuild # Complete rebuild# Install package
make composer CMD="require vendor/package"
# Or directly in container
docker compose exec php composer require vendor/packagePHP is configured with reasonable defaults that can be adjusted:
memory_limit = 512Mmax_execution_time = 300post_max_size = 50Mupload_max_filesize = 50M
Base configuration is in docker/php/php.ini.
PHP uses a base configuration with environment-specific overrides:
- Base:
docker/php/php.ini- Common settings for all environments - Development:
docker/php/conf.d/development.ini- Development overrides - Production:
docker/php/conf.d/production.ini- Production overrides
Development settings:
display_errors = On
opcache.revalidate_freq = 0 # Reload code on every request
error_reporting = E_ALL
zend_extension = xdebug.so # Load XdebugProduction settings:
display_errors = Off
opcache.validate_timestamps = 0 # Maximum performance
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
# Xdebug not loadedThe appropriate configuration is loaded automatically based on the ENV variable in your .env file.
OPcache is enabled for better performance:
- Production: Aggressive caching, no timestamp validation
- Development: Revalidates on every request for immediate code changes
The following PHP extensions are installed:
- GD - With JPEG, PNG, WebP, AVIF support
- GraphicsMagick (gmagick) - For advanced image processing
- OPcache - Performance optimization
- Exif - Image metadata handling
- Zip - Archive handling for Composer
- Xdebug - Debugging and profiling (enabled in development only via development.ini)
Edit docker/php/Dockerfile:
# For standard extensions
RUN docker-php-ext-install extension_name
# For PECL extensions
RUN pecl install extension_name && \
docker-php-ext-enable extension_name
# For extensions via PIE
RUN pie install package/extensionThen rebuild:
make rebuild# In .env
ENV=developmentFeatures:
- Composer installs dev dependencies
- Verbose error reporting and display
- Xdebug loaded and enabled for debugging
- OPcache revalidates on every request
- Full exception details visible
- Assertions enabled
# In .env
ENV=productionFeatures:
- Production dependencies only
- Optimized Composer autoloader
- Errors logged only, not displayed
- Xdebug not loaded (zero overhead)
- Aggressive OPcache settings
- Security-hardened exception handling
- Assertions disabled
Logs are written to the configured LOG_DIR:
logs/
├── app/ # Your application logs
│ └── app.log
├── nginx/
│ ├── access.log
│ └── error.log
└── php/
├── access.log
├── error.log
├── slow.log # Queries taking >30s
└── xdebug.log # Xdebug debug info
View logs in real-time:
make logs # All logs
make logs-nginx # Nginx only
make logs-php # PHP-FPM only- PHP
expose_phpdisabled - Dangerous functions disabled (
exec,shell_exec,system,popen) - Security headers in Nginx (X-Frame-Options, X-Content-Type-Options, X-XSS-Protection)
- Non-root processes (www-data user)
- Read-only mounts where possible
- Hidden file access denied
- Composer/Git file access denied
- Configurable user/group IDs to match host permissions
- Sendfile enabled for efficient file serving
- Gzip compression for text and JavaScript
- Long cache headers for static assets (1 year)
- Optimized buffer sizes for large files
- EPoll event processing
- Dynamic process manager
- 50 max children (configurable)
- Request pooling and termination
- Slow-log for performance monitoring (30s threshold)
- 500 max requests per worker (prevents memory leaks)
- Stored in named Docker volume (
vendor-data) - Persists across container restarts
- No reinstallation needed on rebuild
- Better performance than host mounts
Xdebug is installed via PIE (PHP Installer for Extensions) but only loaded in development mode through
development.ini. This ensures zero overhead in production.
Xdebug is automatically configured in development mode:
- Loaded via:
development.ini(zend_extension=xdebug.so) - Configured via:
xdebug.ini(mounted only in development) - Mode:
develop,debug(improved var_dump + step debugging) - IDE Key:
PHPSTORM - Port:
9003 - Host:
host.docker.internal(automatically connects to your IDE) - Trigger: Only starts when requested (via browser extension or query parameter)
Follow the official guide: Configuring Remote Interpreters
- Go to Settings → PHP
- Click ... next to CLI Interpreter
- Click + → From Docker, Vagrant, VM, WSL, Remote...
- Select Docker Compose
- Configuration file:
./compose.yaml - Service:
php
Follow the official guide: Configuring Xdebug
- Go to Settings → PHP → Debug
- Set Debug port:
9003 - Enable Can accept external connections
- Path mappings:
- Project root →
/var/www/html app/public→/var/www/html/app/public
- Project root →
- Go to Settings → PHP → Servers
- Click + to add a server
- Name:
Docker(or any name matching your IDE key) - Host:
localhost - Port:
8080(or yourNGINX_PORT) - Debugger:
Xdebug - Use path mappings: ✓
- Map project root to
/var/www/html
- Map project root to
-
Install browser extension:
- Chrome: Xdebug Helper
- Firefox: Xdebug Helper
-
Configure extension with IDE Key:
PHPSTORM -
Start debugging:
- Click Start listening for PHP Debug Connections in PhpStorm
- Enable debug in browser extension
- Refresh your page
Add XDEBUG_TRIGGER=1 to any URL:
http://localhost:8080/?XDEBUG_TRIGGER=1
http://localhost:8080/api/endpoint?XDEBUG_TRIGGER=1
Set a cookie manually:
XDEBUG_TRIGGER=PHPSTORM
Check if Xdebug is loaded:
# In development mode
docker compose exec php php -m | grep xdebug
# Should output: xdebug
# In production mode
docker compose exec php php -m | grep xdebug
# Should output: nothingEnable verbose logging:
Edit docker/php/conf.d/xdebug.ini:
xdebug.log_level=7 # Very verboseThen make restart and check logs/php/xdebug.log.
Firewall issues:
Ensure PhpStorm can receive connections on port 9003. On some systems, you may need to allow this in your firewall.
Xdebug is not loaded in production mode - providing zero overhead. The extension is installed in the Docker image
(via PIE with --no-enable flag) but only activated through development.ini in development mode.
The Nginx container includes a health check endpoint:
# Check container health
docker compose ps
# Manual health check
curl http://localhost:8080/health# Check logs
make logs
# Check container status
docker compose ps
# Inspect specific container
docker compose logs php
docker compose logs nginxThe most common issue is file permission mismatches between host and container.
Solution:
# Get your IDs
id -u # Your User ID
id -g # Your Group ID
# Update .env
USER_ID=1000 # Your user ID
GROUP_ID=1000 # Your group ID
# Rebuild containers
make rebuild# Manual installation
make shell-php
composer install
# Clear Composer cache
docker compose exec php composer clear-cache
# Update dependencies
make composer CMD="update"# Access status page
curl http://localhost:8080/status
# Access ping endpoint
curl http://localhost:8080/pingCheck logs/php/slow.log for requests taking longer than 30 seconds.
Modify docker/nginx/conf.d/default.conf for custom routing, caching rules, or server settings.
Common customizations:
- Add additional server blocks
- Configure SSL/TLS
- Adjust buffer sizes
- Add custom headers
- Configure rate limiting
- Base settings: Edit
docker/php/php.ini - Development-specific: Edit
docker/php/conf.d/development.ini - Production-specific: Edit
docker/php/conf.d/production.ini
After changes:
make restartPHP-FPM processes (docker/php/php-fpm.conf):
pm.max_children = 50 # Adjust based on your needs
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20PHP memory (docker/php/php.ini):
memory_limit = 512M # Increase for memory-intensive tasksRequest timeouts (docker/php/php-fpm.conf):
request_terminate_timeout = 300s # Adjust for long-running processes- Docker Documentation
- Docker Compose Documentation
- PHP Documentation
- Nginx Documentation
- PhpStorm Docker Documentation
- Xdebug Documentation
- Docker & Xdebug Guide
Proprietary - All rights reserved. This software is the property of [Your Company Name] and may not be distributed, modified, or used without explicit permission.
This project utilizes Renovate to automatically manage dependency updates across the primary ecosystems: PHP (Composer), Node.js (npm), and Docker.
The core functionality is configured in the renovate.json file and includes:
- Grouping: Multiple small updates are grouped into a single Pull Request (PR) to reduce notification noise.
- Automerge: Patch-level updates are automatically merged once CI/tests have completed successfully.
- Exclusion: Critical components (such as major frameworks) are excluded from automatic grouping for individual manual review. For detailed help and advanced options, please refer to the official Renovate documentation: https://docs.renovatebot.com/
The make renovate command encapsulates the Docker execution of the scanner and automatically distinguishes between two modes, based on the environment variables defined in your .env file.
This mode connects to your Git host (GitHub, GitLab) and creates Pull Requests (PRs) for updates. It is intended for automated execution within CI/CD pipelines.
⚙️ Configuration (in .env):
To enable this mode, the following variables must be set:
| Variable | Example Value | Description |
|---|---|---|
RENOVATE_PLATFORM |
github |
The Git platform being used (github, gitlab, etc.). |
RENOVATE_REPO_SLUG |
your-organization/your-project |
The full name of the repository. |
GITHUB_COM_TOKEN |
ghp_... |
The Personal Access Token (PAT) with write access to the repository. |
When the necessary variables are set, the make renovate command will detect the platform, authenticate, and create all necessary PRs.
make renovate
This is the default mode (fallback) when the variables RENOVATE_PLATFORM and RENOVATE_REPO_SLUG in the .env are empty.
- The scanner runs against the local code clone.
- It modifies files directly (
composer.json,package.json, etc.) in your local branch. - No Pull Requests are created.
This mode is ideal for manual reviews. Always run it on a new branch:
git checkout -b renovate-updatesmake renovate- Review the changes (
git diff) and commit/push them manually.
make renovate