Legacy PHP Containerization: Securing PHP 5.3.3 with Podman
Containerizing legacy PHP applications presents unique challenges, especially when dealing with end-of-life operating systems and PHP versions. This guide provides a comprehensive approach to safely containerizing PHP 5.3.3 applications using Podman, focusing on security research environments and migration planning.
Table of Contents
Introduction
Legacy PHP applications often run on outdated systems that pose significant security risks. Containerization offers a path to isolate these applications while planning for modernization. This guide focuses on PHP 5.3.3 running on CentOS 6, demonstrating secure containerization practices with Podman.
Why Containerize Legacy PHP?
- Security Isolation: Contain legacy applications in isolated environments
- Migration Planning: Enable gradual migration to modern platforms
- Research Environments: Safely analyze legacy codebases
- Dependency Management: Preserve exact runtime environments
- Risk Mitigation: Reduce attack surface through isolation
Podman vs Docker for Legacy Systems
Advantages of Podman
Podman offers several advantages for legacy PHP containerization:
- Rootless Operation: Enhanced security through unprivileged containers
- No Daemon: Eliminates single point of failure and attack surface
- Systemd Integration: Better process management for legacy services
- OCI Compliance: Standard container formats and compatibility
- SELinux Support: Enhanced security through mandatory access controls
Key Differences for Legacy Workloads
# Podman rootless executionpodman run --rm -p 8080:8080 php-legacy
# Docker requires daemon (running as root)docker run --rm -p 8080:8080 php-legacy
Basic PHP 5.3.3 Container
Dockerfile Configuration
FROM centos:6
LABEL maintainer="Security Research Environment - NOT FOR PRODUCTION"
# Configure vault repositories for CentOS 6 (EOL)RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Base.repo && \ sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Base.repo
# Install EPEL repository for additional packagesRUN rpm -Uvh https://vault.centos.org/6.10/extras/x86_64/Packages/epel-release-6-8.noarch.rpm
# Install PHP 5.3.3 and dependenciesRUN yum -y update && \ yum -y install \ php \ php-cli \ php-common \ php-mysql \ php-pdo \ php-gd \ php-xml \ php-mbstring \ httpd \ && yum clean all
# Configure Apache for container environmentRUN sed -i 's/Listen 80/Listen 8080/g' /etc/httpd/conf/httpd.conf && \ echo "ServerName localhost" >> /etc/httpd/conf/httpd.conf && \ echo "<?php phpinfo(); ?>" > /var/www/html/info.php
# Set necessary permissions for rootless containerRUN chmod -R 755 /var/www/html/ && \ chmod -R 755 /var/log/httpd/ && \ chmod -R 755 /var/run/httpd/ && \ chown -R root:root /var/log/httpd/ && \ chown -R root:root /var/run/httpd/ && \ chown -R root:root /var/www/html/
# Create non-root user for securityRUN useradd -r -s /bin/false -c "PHP Application User" phpuser && \ chown -R phpuser:phpuser /var/www/html
EXPOSE 8080
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:8080/info.php || exit 1
# Run Apache in foregroundCMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
Enhanced Dockerfile for Production-Like Usage
FROM centos:6
LABEL maintainer="Legacy PHP Research Environment"LABEL version="1.0"LABEL description="CentOS 6 with PHP 5.3.3 for legacy application research"
# Security warning labelLABEL security.warning="This container contains EOL software and should NOT be used in production"
# Configure repositoriesRUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Base.repo && \ sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Base.repo
# Install EPELRUN rpm -Uvh https://vault.centos.org/6.10/extras/x86_64/Packages/epel-release-6-8.noarch.rpm || true
# Install comprehensive PHP stackRUN yum -y update && \ yum -y install \ # Core PHP php \ php-cli \ php-common \ php-fpm \ # Database extensions php-mysql \ php-pgsql \ php-pdo \ # Common extensions php-gd \ php-xml \ php-mbstring \ php-json \ php-curl \ php-soap \ php-ldap \ php-zip \ # Web server httpd \ # Utilities curl \ wget \ vim \ less \ # Security tools sudo \ && yum clean all && \ rm -rf /var/cache/yum
# Configure ApacheCOPY apache/httpd.conf /etc/httpd/conf/httpd.confCOPY apache/security.conf /etc/httpd/conf.d/security.conf
# Configure PHPCOPY php/php.ini /etc/php.iniCOPY php/security.ini /etc/php.d/99-security.ini
# Create application structureRUN mkdir -p /var/www/html/app && \ mkdir -p /var/log/app && \ mkdir -p /var/lib/php/session
# Create non-privileged userRUN groupadd -r phpapp && \ useradd -r -g phpapp -s /bin/false -c "PHP Application User" phpapp
# Set ownership and permissionsRUN chown -R phpapp:phpapp /var/www/html/app && \ chown -R phpapp:phpapp /var/log/app && \ chown -R phpapp:phpapp /var/lib/php/session && \ chmod -R 755 /var/www/html && \ chmod -R 750 /var/log/app
# Security hardeningRUN chmod 600 /etc/shadow && \ chmod 644 /etc/passwd && \ find /var/www/html -name "*.php" -exec chmod 644 {} \; && \ find /var/www/html -type d -exec chmod 755 {} \;
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:8080/ || exit 1
# Use exec form for better signal handlingCMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
Configuration Files
Apache Security Configuration
# Hide Apache versionServerTokens ProdServerSignature Off
# Disable unnecessary modulesLoadModule rewrite_module modules/mod_rewrite.so
# Security headersHeader always set X-Content-Type-Options nosniffHeader always set X-Frame-Options DENYHeader always set X-XSS-Protection "1; mode=block"Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Disable server status<Location "/server-status"> Order deny,allow Deny from all</Location>
# Disable server info<Location "/server-info"> Order deny,allow Deny from all</Location>
# Hide .git directories<DirectoryMatch "\.git"> Order deny,allow Deny from all</DirectoryMatch>
# Hide configuration files<Files ~ "\.(conf|ini|log|sql|md)$"> Order deny,allow Deny from all</Files>
# Disable directory browsingOptions -Indexes
# Prevent access to PHP files in uploads directory<Directory "/var/www/html/uploads"> <Files "*.php"> Order deny,allow Deny from all </Files></Directory>
PHP Security Configuration
; /etc/php.d/99-security.ini
; Disable dangerous functionsdisable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_get_contents
; Hide PHP versionexpose_php = Off
; Limit script executionmax_execution_time = 30max_input_time = 30memory_limit = 64M
; File upload restrictionsfile_uploads = Onupload_max_filesize = 2Mmax_file_uploads = 5upload_tmp_dir = /tmp
; Session securitysession.cookie_secure = Onsession.cookie_httponly = Onsession.use_strict_mode = Onsession.cookie_samesite = Strict
; Error handlingdisplay_errors = Offlog_errors = Onerror_log = /var/log/app/php_errors.log
; Open basedir restrictionopen_basedir = /var/www/html:/tmp:/var/lib/php/session
; SQL injection protectionmagic_quotes_gpc = Offmagic_quotes_runtime = Off
; Include path securityinclude_path = ".:/usr/share/php"
; Allow only specific file extensions; This should be configured per application
Building and Running with Podman
Build Process
#!/bin/bash
# Build script for legacy PHP containerset -e
CONTAINER_NAME="php-legacy"TAG="5.3.3-centos6"FULL_NAME="${CONTAINER_NAME}:${TAG}"
echo "Building legacy PHP container..."
# Build with Podmanpodman build \ --tag "$FULL_NAME" \ --label "build.date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ --label "build.version=1.0.0" \ --label "security.scan=required" \ .
# Verify buildif podman images | grep -q "$CONTAINER_NAME"; then echo "✓ Container built successfully: $FULL_NAME"else echo "✗ Container build failed" exit 1fi
# Security scan (if available)if command -v podman &> /dev/null; then echo "Running security scan..." podman run --rm -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy image "$FULL_NAME" || truefi
echo "Build completed: $FULL_NAME"
Running the Container
#!/bin/bash
# Run script for legacy PHP containerCONTAINER_NAME="php-legacy"TAG="5.3.3-centos6"INSTANCE_NAME="php-legacy-instance"
# Stop and remove existing instancepodman stop "$INSTANCE_NAME" 2>/dev/null || truepodman rm "$INSTANCE_NAME" 2>/dev/null || true
echo "Starting legacy PHP container..."
# Run with security considerationspodman run \ --name "$INSTANCE_NAME" \ --detach \ --publish 8080:8080 \ --volume "./app:/var/www/html/app:Z" \ --volume "./logs:/var/log/app:Z" \ --read-only \ --tmpfs /tmp:rw,noexec,nosuid,size=100m \ --tmpfs /var/run/httpd:rw,size=10m \ --security-opt no-new-privileges \ --cap-drop ALL \ --cap-add SETUID \ --cap-add SETGID \ --cap-add DAC_OVERRIDE \ --memory 256m \ --cpus 0.5 \ --restart unless-stopped \ "$CONTAINER_NAME:$TAG"
# Verify container is runningif podman ps | grep -q "$INSTANCE_NAME"; then echo "✓ Container started successfully" echo "Access the application at: http://localhost:8080" echo "PHP info available at: http://localhost:8080/info.php"else echo "✗ Container failed to start" echo "Checking logs..." podman logs "$INSTANCE_NAME" exit 1fi
Security Hardening
Container Security Configuration
# Enhanced security run commandrun_secure_php_container() { local container_name="php-legacy-secure" local image_name="php-legacy:5.3.3-centos6"
# Create necessary directories mkdir -p ./app ./logs ./tmp
# Set proper permissions chmod 755 ./app ./logs chmod 1777 ./tmp
podman run \ --name "$container_name" \ --detach \ --publish 127.0.0.1:8080:8080 \ \ `# Volume mounts with SELinux labels` \ --volume "./app:/var/www/html/app:Z,ro" \ --volume "./logs:/var/log/app:Z" \ \ `# Read-only root filesystem` \ --read-only \ \ `# Temporary filesystems` \ --tmpfs /tmp:rw,noexec,nosuid,nodev,size=50m \ --tmpfs /var/run:rw,noexec,nosuid,nodev,size=10m \ --tmpfs /var/lib/php/session:rw,noexec,nosuid,nodev,size=10m \ \ `# Security options` \ --security-opt no-new-privileges \ --security-opt label=type:container_t \ \ `# Capability restrictions` \ --cap-drop ALL \ --cap-add SETUID \ --cap-add SETGID \ --cap-add DAC_OVERRIDE \ \ `# Resource limits` \ --memory 128m \ --memory-swap 128m \ --cpus 0.25 \ --pids-limit 50 \ \ `# Network security` \ --network slirp4netns:allow_host_loopback=false \ \ `# Process limits` \ --ulimit nproc=50:50 \ --ulimit nofile=1024:1024 \ \ `# Environment restrictions` \ --env-file /dev/null \ \ "$image_name"}
Security Monitoring
#!/bin/bash
# Security monitoring script for PHP containermonitor_container_security() { local container_name="$1"
echo "=== Container Security Monitoring ==="
# Check container status if podman ps | grep -q "$container_name"; then echo "✓ Container is running" else echo "✗ Container is not running" return 1 fi
# Check resource usage echo "Resource Usage:" podman stats --no-stream "$container_name" | tail -n +2
# Check for privilege escalation attempts echo "Security Events:" podman logs "$container_name" 2>&1 | grep -i "permission\|denied\|failed\|error" | tail -5
# Check file system integrity echo "File System Check:" podman exec "$container_name" find /var/www/html -type f -perm /u+s,g+s 2>/dev/null | head -5
# Check network connections echo "Network Connections:" podman exec "$container_name" netstat -tuln 2>/dev/null | grep LISTEN || echo "No listening ports found"
# Check process list echo "Running Processes:" podman exec "$container_name" ps aux | head -10}
# Usagemonitor_container_security "php-legacy-instance"
Migration Strategies
Assessment and Planning
#!/bin/bash
# Legacy PHP application assessment scriptassess_php_application() { local app_path="$1"
echo "=== PHP Application Assessment ===" echo "Application path: $app_path"
# PHP version detection echo "PHP Version Requirements:" grep -r "php" "$app_path" | grep -i version | head -5
# Framework detection echo "Framework Detection:" if [ -f "$app_path/wp-config.php" ]; then echo "- WordPress detected" elif [ -f "$app_path/configuration.php" ]; then echo "- Joomla detected" elif [ -f "$app_path/config/config.php" ]; then echo "- Custom framework detected" fi
# Security issues echo "Potential Security Issues:" grep -r "mysql_query\|eval\|system\|exec" "$app_path" --include="*.php" | wc -l | xargs echo "Dangerous function calls:" grep -r "register_globals\|magic_quotes" "$app_path" --include="*.php" | wc -l | xargs echo "Legacy PHP features:"
# Database connections echo "Database Connections:" grep -r "mysql_connect\|mysqli_connect\|PDO" "$app_path" --include="*.php" | head -3
# File upload handling echo "File Upload Handlers:" grep -r "\$_FILES\|move_uploaded_file" "$app_path" --include="*.php" | wc -l | xargs echo "Upload handlers found:"
# External dependencies echo "External Dependencies:" find "$app_path" -name "*.xml" -o -name "composer.json" -o -name "package.json" | head -5}
# Usageassess_php_application "./legacy-app"
Modernization Path
apiVersion: v1kind: ConfigMapmetadata: name: php-migration-plandata: phases: | Phase 1: Containerization (Current) - Isolate legacy application in container - Implement security hardening - Establish monitoring and logging
Phase 2: Security Hardening - Update to PHP 5.6 (last 5.x version) - Implement WAF protection - Add vulnerability scanning
Phase 3: Framework Modernization - Identify migration path to PHP 7.4/8.x - Refactor deprecated functions - Update database connections
Phase 4: Full Migration - Migrate to modern PHP version - Implement modern framework - Deploy to production infrastructure
security_checklist: | - [ ] SQL injection protection - [ ] XSS prevention - [ ] CSRF protection - [ ] File upload validation - [ ] Session security - [ ] Error handling - [ ] Input validation - [ ] Output encoding
compatibility_matrix: | Legacy (PHP 5.3.3): - mysql_* functions (deprecated) - register_globals support - magic_quotes support
Modern (PHP 8.x): - mysqli_* or PDO required - Strict type checking - Improved error handling
Monitoring and Logging
Container Monitoring Setup
#!/bin/bash
# Monitoring setup for legacy PHP containersetup_monitoring() { local container_name="$1"
# Create monitoring directories mkdir -p ./monitoring/{logs,metrics,alerts}
# Setup log aggregation cat > ./monitoring/rsyslog.conf << 'EOF'# Forward container logs to central location*.* @@localhost:514EOF
# Create monitoring script cat > ./monitoring/monitor.sh << 'EOF'#!/bin/bash
CONTAINER_NAME="$1"LOG_DIR="./monitoring/logs"
while true; do TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Resource usage STATS=$(podman stats --no-stream "$CONTAINER_NAME" 2>/dev/null | tail -n +2) echo "$TIMESTAMP - STATS: $STATS" >> "$LOG_DIR/resource.log"
# Health check HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/info.php) echo "$TIMESTAMP - HTTP_STATUS: $HTTP_STATUS" >> "$LOG_DIR/health.log"
# Security events SECURITY_EVENTS=$(podman logs "$CONTAINER_NAME" 2>&1 | grep -i "error\|warning\|failed" | tail -1) [ -n "$SECURITY_EVENTS" ] && echo "$TIMESTAMP - SECURITY: $SECURITY_EVENTS" >> "$LOG_DIR/security.log"
sleep 60doneEOF
chmod +x ./monitoring/monitor.sh
# Start monitoring in background nohup ./monitoring/monitor.sh "$container_name" > ./monitoring/monitor.out 2>&1 & echo "Monitoring started for container: $container_name"}
# Alerting systemsetup_alerting() { cat > ./monitoring/alerts.sh << 'EOF'#!/bin/bash
LOG_DIR="./monitoring/logs"ALERT_EMAIL="admin@example.com"
# Check for high resource usagecheck_resources() { local cpu_threshold=80 local mem_threshold=80
if [ -f "$LOG_DIR/resource.log" ]; then local latest_stats=$(tail -1 "$LOG_DIR/resource.log") local cpu_usage=$(echo "$latest_stats" | awk '{print $4}' | sed 's/%//') local mem_usage=$(echo "$latest_stats" | awk '{print $6}' | sed 's/%//')
if (( $(echo "$cpu_usage > $cpu_threshold" | bc -l) )); then echo "ALERT: High CPU usage: $cpu_usage%" | mail -s "Container Alert" "$ALERT_EMAIL" fi
if (( $(echo "$mem_usage > $mem_threshold" | bc -l) )); then echo "ALERT: High memory usage: $mem_usage%" | mail -s "Container Alert" "$ALERT_EMAIL" fi fi}
# Check for security eventscheck_security() { if [ -f "$LOG_DIR/security.log" ]; then local recent_events=$(tail -10 "$LOG_DIR/security.log" | grep "$(date '+%Y-%m-%d')") if [ -n "$recent_events" ]; then echo "SECURITY ALERT: Recent security events detected" | mail -s "Security Alert" "$ALERT_EMAIL" fi fi}
check_resourcescheck_securityEOF
chmod +x ./monitoring/alerts.sh
# Add to crontab (crontab -l 2>/dev/null; echo "*/5 * * * * $(pwd)/monitoring/alerts.sh") | crontab -}
Application Performance Monitoring
<?php// monitoring/apm.php - Simple APM for legacy PHP
class LegacyAPM { private $start_time; private $start_memory; private $log_file;
public function __construct($log_file = '/var/log/app/apm.log') { $this->start_time = microtime(true); $this->start_memory = memory_get_usage(); $this->log_file = $log_file;
// Register shutdown function register_shutdown_function(array($this, 'shutdown_handler')); }
public function log_event($event, $data = array()) { $timestamp = date('Y-m-d H:i:s'); $memory = memory_get_usage(); $elapsed = microtime(true) - $this->start_time;
$log_entry = array( 'timestamp' => $timestamp, 'event' => $event, 'memory_mb' => round($memory / 1024 / 1024, 2), 'elapsed_ms' => round($elapsed * 1000, 2), 'data' => $data );
error_log(json_encode($log_entry) . "\n", 3, $this->log_file); }
public function shutdown_handler() { $error = error_get_last(); $total_time = microtime(true) - $this->start_time; $peak_memory = memory_get_peak_usage();
$this->log_event('request_end', array( 'total_time_ms' => round($total_time * 1000, 2), 'peak_memory_mb' => round($peak_memory / 1024 / 1024, 2), 'error' => $error )); }
public function track_database_query($query, $execution_time) { $this->log_event('database_query', array( 'query' => substr($query, 0, 100), 'execution_time_ms' => round($execution_time * 1000, 2) )); }}
// Usage in legacy application$apm = new LegacyAPM();$apm->log_event('request_start', array('uri' => $_SERVER['REQUEST_URI']));?>
Backup and Recovery
Container Backup Strategy
#!/bin/bash
# Backup strategy for legacy PHP containerbackup_container() { local container_name="$1" local backup_dir="./backups/$(date +%Y%m%d_%H%M%S)"
echo "Creating backup for container: $container_name" mkdir -p "$backup_dir"
# Export container as tar podman export "$container_name" > "$backup_dir/container.tar"
# Backup volumes if [ -d "./app" ]; then tar -czf "$backup_dir/app_data.tar.gz" ./app fi
if [ -d "./logs" ]; then tar -czf "$backup_dir/logs.tar.gz" ./logs fi
# Backup configuration cp Dockerfile "$backup_dir/" cp -r ./config "$backup_dir/" 2>/dev/null || true
# Create manifest cat > "$backup_dir/manifest.json" << EOF{ "container_name": "$container_name", "backup_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "podman_version": "$(podman --version)", "image_info": $(podman inspect "$container_name" | jq '.[0].Image' || echo "null"), "files": [ "container.tar", "app_data.tar.gz", "logs.tar.gz", "Dockerfile" ]}EOF
echo "Backup completed: $backup_dir"
# Cleanup old backups (keep last 7 days) find ./backups -type d -mtime +7 -exec rm -rf {} + 2>/dev/null || true}
# Restore from backuprestore_container() { local backup_dir="$1" local container_name="$2"
if [ ! -d "$backup_dir" ]; then echo "Backup directory not found: $backup_dir" return 1 fi
echo "Restoring container from: $backup_dir"
# Stop existing container podman stop "$container_name" 2>/dev/null || true podman rm "$container_name" 2>/dev/null || true
# Import container cat "$backup_dir/container.tar" | podman import - "restored-$container_name"
# Restore volumes if [ -f "$backup_dir/app_data.tar.gz" ]; then tar -xzf "$backup_dir/app_data.tar.gz" fi
if [ -f "$backup_dir/logs.tar.gz" ]; then tar -xzf "$backup_dir/logs.tar.gz" fi
echo "Restore completed. You may need to manually start the container."}
Best Practices
Development Workflow
- Isolated Development: Use containers for all legacy development work
- Version Control: Maintain Dockerfile and configurations in version control
- Security Scanning: Regular vulnerability assessments of container images
- Resource Monitoring: Continuous monitoring of container performance
- Backup Strategy: Regular automated backups of container state and data
Security Guidelines
- Principle of Least Privilege: Run containers with minimal required permissions
- Network Isolation: Restrict network access to necessary services only
- Regular Updates: Keep base images updated within compatibility constraints
- Log Monitoring: Implement comprehensive logging and alerting
- Incident Response: Prepare procedures for security incidents
Migration Planning
- Assessment Phase: Thoroughly assess legacy application requirements
- Compatibility Testing: Test modernization changes in isolated environments
- Incremental Updates: Plan gradual migration to avoid breaking changes
- Rollback Procedures: Maintain ability to revert to legacy systems
- Documentation: Document all changes and migration steps
Conclusion
Containerizing legacy PHP applications with Podman provides a secure approach to managing outdated systems while planning for modernization. This approach offers:
- Security Isolation: Contains legacy vulnerabilities within controlled environments
- Migration Path: Enables gradual modernization without service disruption
- Research Environment: Provides safe space for security analysis and testing
- Operational Continuity: Maintains service availability during transition periods
Remember that this approach is intended for research, migration planning, and controlled environments. Legacy PHP versions contain known security vulnerabilities and should never be exposed to production traffic without proper security controls and isolation measures.
This containerization approach is designed for security research and migration planning only. Always consult security professionals before deploying legacy systems in any environment.