Een moderne, schaalbare ontwikkelomgeving opzetten met Docker voor PHP-projecten op Linux Mint/Ubuntu.
# Inhoudsopgave
- Waarom Docker in 2025?
- Voorbereiding Linux systeem
- Docker installatie
- Base ontwikkelomgeving
- Geavanceerde configuratie
- Werkflow en best practices
- Troubleshooting
- Volgende stappen
# Waarom Docker in 2025?
# Voordelen ten opzichte van traditionele LAMP stack:
- Isolatie: Elk project heeft zijn eigen omgeving
- Portabiliteit: Werkt identiek op development, staging en productie
- Schaalbaarheid: Eenvoudig meerdere projecten en versies beheren
- Teamwork: Gegarandeerd identieke omgeving voor alle ontwikkelaars
- DevOps ready: Directe integratie met CI/CD pipelines
# Wanneer nog steeds lokale LAMP?
- Zeer kleine scripts of experimenten
- Tijdelijke prototypes
- Legacy projecten zonder Docker support
# Voorbereiding Linux systeem
# Systeem updaten
sudo apt update && sudo apt upgrade -y
# Benodigde packages installeren
sudo apt install -y \
curl \
git \
vim \
htop \
tree \
unzip \
software-properties-common \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release
# Optioneel: Moderne shell setup (Zsh + Oh My Zsh)
sudo apt install zsh
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# Docker installatie
# 1. Oude Docker versies verwijderen
sudo apt remove docker docker-engine docker.io containerd runc
# 2. Docker's officiële GPG key toevoegen
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 3. Docker repository toevoegen
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 4. Docker installeren
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 5. Docker configureren
# Gebruiker toevoegen aan docker groep
sudo usermod -aG docker $USER
# Groepswijziging activeren
newgrp docker
# Docker service starten en auto-start inschakelen
sudo systemctl enable docker
sudo systemctl start docker
# 6. Installatie testen
docker run hello-world
docker compose version
# Base ontwikkelomgeving
# Projectstructuur aanmaken
mkdir -p ~/development/{projects,templates,scripts}
cd ~/development/templates
mkdir php-docker-template
cd php-docker-template
# 1. Docker Compose configuratie
Bestand: docker-compose.yml
version: '3.8'
services:
# PHP-FPM Service
app:
build:
context: .
dockerfile: docker/php/Dockerfile
container_name: ${PROJECT_NAME:-myapp}_app
volumes:
- ./src:/var/www/html
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
environment:
- PHP_IDE_CONFIG=serverName=docker
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- app-network
# Nginx Web Server
nginx:
image: nginx:1.25-alpine
container_name: ${PROJECT_NAME:-myapp}_nginx
ports:
- "${NGINX_PORT:-8080}:80"
volumes:
- ./src:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
networks:
- app-network
# MySQL Database
mysql:
image: mysql:8.0
container_name: ${PROJECT_NAME:-myapp}_mysql
ports:
- "${MYSQL_PORT:-3306}:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
MYSQL_DATABASE: ${MYSQL_DATABASE:-app_db}
MYSQL_USER: ${MYSQL_USER:-app_user}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-app_password}
volumes:
- mysql_data:/var/lib/mysql
- ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-network
# Redis Cache
redis:
image: redis:7-alpine
container_name: ${PROJECT_NAME:-myapp}_redis
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
networks:
- app-network
# phpMyAdmin
phpmyadmin:
image: phpmyadmin:latest
container_name: ${PROJECT_NAME:-myapp}_phpmyadmin
ports:
- "${PMA_PORT:-8081}:80"
environment:
PMA_HOST: mysql
PMA_USER: root
PMA_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
UPLOAD_LIMIT: 64M
depends_on:
- mysql
networks:
- app-network
# MailHog (Email testing)
mailhog:
image: mailhog/mailhog:latest
container_name: ${PROJECT_NAME:-myapp}_mailhog
ports:
- "${MAILHOG_PORT:-8025}:8025"
- "1025:1025"
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
mysql_data:
redis_data:
# 2. Environment configuratie
Bestand: .env
# Project Configuration
PROJECT_NAME=myapp
COMPOSE_PROJECT_NAME=myapp
# Port Configuration
NGINX_PORT=8080
MYSQL_PORT=3306
REDIS_PORT=6379
PMA_PORT=8081
MAILHOG_PORT=8025
# Database Configuration
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=app_db
MYSQL_USER=app_user
MYSQL_PASSWORD=app_password
# PHP Configuration
PHP_VERSION=8.2
# 3. PHP Dockerfile
Map: docker/php/
- Bestand: Dockerfile
FROM php:8.2-fpm-alpine
# Install system dependencies
RUN apk add --no-cache \
build-base \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
libzip-dev \
zip \
jpegoptim optipng pngquant gifsicle \
vim \
unzip \
git \
curl \
oniguruma-dev \
libxml2-dev
# Install PHP extensions
RUN docker-php-ext-install \
pdo_mysql \
mbstring \
exif \
pcntl \
bcmath \
gd \
zip \
intl \
soap \
opcache
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Install Node.js and npm
RUN apk add --no-cache nodejs npm
# Create application directory
WORKDIR /var/www/html
# Add user for laravel application
RUN addgroup -g 1000 -S www
RUN adduser -u 1000 -S www -G www
# Change current user to www
USER www
# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]
Bestand: docker/php/local.ini
upload_max_filesize=64M
post_max_size=64M
memory_limit=512M
max_execution_time=300
max_input_vars=3000
display_errors=On
log_errors=On
error_log=/var/log/php_errors.log
[opcache]
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
# 4. Nginx configuratie
Map: docker/nginx/
- Bestand: nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 64M;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
}
Bestand: docker/nginx/default.conf
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html index.htm;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Handle requests
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP handling
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# Increase timeouts
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Optimize static files
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
# 5. Database initialisatie
Map: docker/mysql/
- Bestand: init.sql
-- Create additional databases if needed
CREATE DATABASE IF NOT EXISTS `app_test` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Create additional users if needed
-- GRANT ALL PRIVILEGES ON `app_test`.* TO 'app_user'@'%';
-- Sample tables for testing
USE `app_db`;
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`email` varchar(100) NOT NULL UNIQUE,
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `users` (`name`, `email`) VALUES
('Test User', 'test@example.com'),
('Demo User', 'demo@example.com');
# 6. Basis PHP applicatie
Map: src/public/
- Bestand: index.php
<?php
// Basic error reporting
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Database connection test
function testDatabaseConnection() {
try {
$host = 'mysql';
$dbname = 'app_db';
$username = 'app_user';
$password = 'app_password';
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query("SELECT COUNT(*) as count FROM users");
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return "Database connection successful! Users in database: " . $result['count'];
} catch (PDOException $e) {
return "Database connection failed: " . $e->getMessage();
}
}
// Redis connection test
function testRedisConnection() {
try {
if (!extension_loaded('redis')) {
return "Redis extension not loaded";
}
$redis = new Redis();
$redis->connect('redis', 6379);
$redis->set('test_key', 'Hello Redis!');
$value = $redis->get('test_key');
$redis->close();
return "Redis connection successful! Test value: $value";
} catch (Exception $e) {
return "Redis connection failed: " . $e->getMessage();
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP Development Environment</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
.container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.status { padding: 15px; margin: 10px 0; border-radius: 4px; }
.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
h1 { color: #333; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
h2 { color: #666; margin-top: 30px; }
pre { background: #f8f9fa; padding: 15px; border-left: 4px solid #007bff; overflow-x: auto; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin: 20px 0; }
.card { background: #f8f9fa; padding: 20px; border-radius: 6px; border-left: 4px solid #007bff; }
</style>
</head>
<body>
<div class="container">
<h1>🐳 PHP Development Environment</h1>
<div class="status info">
<strong>Environment Status:</strong> Active and running!
</div>
<h2>📊 System Information</h2>
<div class="grid">
<div class="card">
<h3>PHP Version</h3>
<strong><?php echo PHP_VERSION; ?></strong>
</div>
<div class="card">
<h3>Server Software</h3>
<strong><?php echo $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown'; ?></strong>
</div>
<div class="card">
<h3>Document Root</h3>
<strong><?php echo $_SERVER['DOCUMENT_ROOT']; ?></strong>
</div>
<div class="card">
<h3>Current Time</h3>
<strong><?php echo date('Y-m-d H:i:s'); ?></strong>
</div>
</div>
<h2>🔌 Connection Tests</h2>
<div class="status <?php echo strpos(testDatabaseConnection(), 'successful') !== false ? 'success' : 'error'; ?>">
<strong>Database:</strong> <?php echo testDatabaseConnection(); ?>
</div>
<div class="status <?php echo strpos(testRedisConnection(), 'successful') !== false ? 'success' : 'error'; ?>">
<strong>Redis:</strong> <?php echo testRedisConnection(); ?>
</div>
<h2>🛠 Available Services</h2>
<div class="grid">
<div class="card">
<h3>Web Application</h3>
<a href="http://localhost:8080" target="_blank">http://localhost:8080</a>
</div>
<div class="card">
<h3>phpMyAdmin</h3>
<a href="http://localhost:8081" target="_blank">http://localhost:8081</a>
</div>
<div class="card">
<h3>MailHog</h3>
<a href="http://localhost:8025" target="_blank">http://localhost:8025</a>
</div>
</div>
<h2>📋 PHP Configuration</h2>
<pre><?php
$configs = [
'upload_max_filesize' => ini_get('upload_max_filesize'),
'post_max_size' => ini_get('post_max_size'),
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'display_errors' => ini_get('display_errors') ? 'On' : 'Off',
];
foreach ($configs as $key => $value) {
echo sprintf("%-25s: %s\n", $key, $value);
}
?></pre>
<h2>🧩 Loaded Extensions</h2>
<pre><?php
$extensions = get_loaded_extensions();
sort($extensions);
echo implode(', ', $extensions);
?></pre>
</div>
</body>
</html>
# 7. Project setup scripts
Bestand: setup.sh
#!/bin/bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}🐳 Starting PHP Development Environment Setup${NC}"
# Check if Docker is running
if ! docker info > /dev/null 2>&1; then
echo -e "${RED}❌ Docker is not running. Please start Docker first.${NC}"
exit 1
fi
# Create necessary directories
echo -e "${YELLOW}📁 Creating directory structure...${NC}"
mkdir -p src/public
mkdir -p docker/{php,nginx,mysql}
mkdir -p logs/{nginx,php}
# Set permissions
echo -e "${YELLOW}🔒 Setting permissions...${NC}"
chmod +x setup.sh
chmod 755 src/public
# Build and start containers
echo -e "${YELLOW}🏗 Building and starting containers...${NC}"
docker compose build --no-cache
docker compose up -d
# Wait for services to be ready
echo -e "${YELLOW}⏳ Waiting for services to start...${NC}"
sleep 10
# Install Composer dependencies if composer.json exists
if [ -f "src/composer.json" ]; then
echo -e "${YELLOW}📦 Installing Composer dependencies...${NC}"
docker compose exec app composer install
fi
# Show status
echo -e "${GREEN}✅ Setup complete!${NC}"
echo ""
echo -e "${GREEN}🌐 Your application is available at:${NC}"
echo -e " Web App: http://localhost:8080"
echo -e " phpMyAdmin: http://localhost:8081"
echo -e " MailHog: http://localhost:8025"
echo ""
echo -e "${GREEN}🛠 Useful commands:${NC}"
echo -e " View logs: docker compose logs -f"
echo -e " Stop: docker compose down"
echo -e " Restart: docker compose restart"
echo -e " Shell access: docker compose exec app sh"
Bestand: Makefile
.PHONY: help build up down restart logs shell mysql redis clean
# Default target
help:
@echo "Available commands:"
@echo " make build - Build Docker containers"
@echo " make up - Start containers"
@echo " make down - Stop containers"
@echo " make restart - Restart containers"
@echo " make logs - Show container logs"
@echo " make shell - Access PHP container shell"
@echo " make mysql - Access MySQL shell"
@echo " make redis - Access Redis CLI"
@echo " make clean - Remove containers and volumes"
build:
docker compose build --no-cache
up:
docker compose up -d
@echo "Services started. Visit http://localhost:8080"
down:
docker compose down
restart:
docker compose restart
logs:
docker compose logs -f
shell:
docker compose exec app sh
mysql:
docker compose exec mysql mysql -u root -p
redis:
docker compose exec redis redis-cli
clean:
docker compose down -v
docker system prune -f
# Geavanceerde configuratie
# Multi-project management
Bestand: ~/development/scripts/new-project.sh
#!/bin/bash
if [ -z "$1" ]; then
echo "Usage: $0 <project-name>"
exit 1
fi
PROJECT_NAME=$1
PROJECT_DIR="$HOME/development/projects/$PROJECT_NAME"
echo "Creating new project: $PROJECT_NAME"
# Copy template
cp -r ~/development/templates/php-docker-template "$PROJECT_DIR"
cd "$PROJECT_DIR"
# Update .env file
sed -i "s/PROJECT_NAME=myapp/PROJECT_NAME=$PROJECT_NAME/g" .env
sed -i "s/COMPOSE_PROJECT_NAME=myapp/COMPOSE_PROJECT_NAME=$PROJECT_NAME/g" .env
# Generate unique ports
BASE_PORT=$((8000 + $(date +%s) % 1000))
sed -i "s/NGINX_PORT=8080/NGINX_PORT=$BASE_PORT/g" .env
sed -i "s/PMA_PORT=8081/PMA_PORT=$((BASE_PORT + 1))/g" .env
sed -i "s/MAILHOG_PORT=8025/MAILHOG_PORT=$((BASE_PORT + 2))/g" .env
echo "Project created at: $PROJECT_DIR"
echo "Nginx will run on port: $BASE_PORT"
echo "Run 'cd $PROJECT_DIR && make up' to start"
# Laravel integratie
Bestand: laravel-setup.sh
#!/bin/bash
# Install Laravel via Composer
docker compose exec app composer create-project laravel/laravel temp
docker compose exec app sh -c "mv temp/* temp/.* /var/www/html/ 2>/dev/null || true"
docker compose exec app rm -rf temp
# Update .env for Laravel
docker compose exec app cp .env.example .env
docker compose exec app php artisan key:generate
# Configure database
docker compose exec app sed -i 's/DB_HOST=127.0.0.1/DB_HOST=mysql/' .env
docker compose exec app sed -i 's/DB_DATABASE=laravel/DB_DATABASE=app_db/' .env
docker compose exec app sed -i 's/DB_USERNAME=root/DB_USERNAME=app_user/' .env
docker compose exec app sed -i 's/DB_PASSWORD=/DB_PASSWORD=app_password/' .env
# Run migrations
docker compose exec app php artisan migrate
echo "Laravel installed successfully!"
# WordPress integratie
Bestand: wordpress-setup.sh
#!/bin/bash
# Download WordPress
docker compose exec app wget https://wordpress.org/latest.tar.gz
docker compose exec app tar -xzf latest.tar.gz
docker compose exec app sh -c "mv wordpress/* /var/www/html/"
docker compose exec app rm -rf wordpress latest.tar.gz
# Create wp-config.php
docker compose exec app cp wp-config-sample.php wp-config.php
docker compose exec app sed -i "s/database_name_here/app_db/g" wp-config.php
docker compose exec app sed -i "s/username_here/app_user/g" wp-config.php
docker compose exec app sed -i "s/password_here/app_password/g" wp-config.php
docker compose exec app sed -i "s/localhost/mysql/g" wp-config.php
echo "WordPress installed successfully!"
echo "Visit http://localhost:8080 to complete setup"
# Werkflow en best practices
# Development workflow
-
Start nieuw project:
~/development/scripts/new-project.sh my-new-app cd ~/development/projects/my-new-app make up
-
Dagelijkse ontwikkeling:
make logs # Check logs make shell # Access container make mysql # Database access
-
Code wijzigingen:
- Bestanden in
src/
worden automatisch gesynchroniseerd - Geen container restart nodig voor PHP changes
- Nginx configuratie wijzigingen vereisen restart
- Bestanden in
# Performance optimalisatie
Bestand: docker-compose.override.yml
(voor development)
version: '3.8'
services:
app:
volumes:
# Use delegated consistency for better performance on macOS
- ./src:/var/www/html:delegated
environment:
# Disable OPcache in development
- PHP_OPCACHE_ENABLE=0
nginx:
volumes:
- ./src:/var/www/html:delegated
# Debugging setup
VS Code configuratie - .vscode/launch.json
:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}/src"
}
}
]
}
Xdebug toevoegen aan PHP Dockerfile:
# Add after other extensions
RUN pecl install xdebug && docker-php-ext-enable xdebug
# Add to local.ini
echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/local.ini
echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/local.ini
echo "xdebug.client_port=9003" >> /usr/local/etc/php/conf.d/local.ini
# Troubleshooting
# Veelvoorkomende problemen
Port conflicts:
# Check which process uses port
sudo netstat -tulpn | grep :8080
# Kill process if needed
sudo kill -9 <PID>
Permission issues:
# Fix file permissions
sudo chown -R $USER:$USER ~/development/
chmod -R 755 ~/development/projects/*/src
Database connection errors:
# Check MySQL container logs
docker compose logs mysql
# Reset database
docker compose down -v
docker compose up -d
Slow performance:
# Check Docker stats
docker stats
# Increase Docker memory limit in Docker Desktop
# Or use production docker-compose for better performance
# Log locaties
- Nginx logs:
docker compose logs nginx
- PHP logs:
docker compose logs app
- MySQL logs:
docker compose logs mysql
- Container stats:
docker stats
# Volgende stappen
# CI/CD integratie
GitHub Actions voorbeeld - .github/workflows/deploy.yml
:
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/my-app
git pull origin main
docker compose -f docker-compose.prod.yml up -d --build
# Production deployment
Bestand: docker-compose.prod.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: docker/php/Dockerfile.prod
volumes:
- ./src:/var/www/html:ro
environment:
- APP_ENV=production
restart: unless-stopped
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./src:/var/www/html:ro
- ./docker/nginx/prod.conf:/etc/nginx/conf.d/default.conf
- ./ssl:/etc/nginx/ssl
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD_FILE: /run/secrets/mysql_password
volumes:
- mysql_prod_data:/var/lib/mysql
secrets:
- mysql_root_password
- mysql_password
restart: unless-stopped
secrets:
mysql_root_password:
external: true
mysql_password:
external: true
volumes:
mysql_prod_data:
# Monitoring en logging
Docker Compose monitoring - docker-compose.monitoring.yml
:
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
volumes:
prometheus_data:
grafana_data:
# SSL/HTTPS setup
Nginx SSL configuratie - docker/nginx/prod.conf
:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
root /var/www/html/public;
index index.php;
# Rest of your configuration...
}
# Backup strategieën
Database backup script - scripts/backup.sh
:
#!/bin/bash
BACKUP_DIR="/var/backups/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
PROJECT_NAME="myapp"
mkdir -p $BACKUP_DIR
# Backup database
docker compose exec mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD --all-databases > $BACKUP_DIR/backup_$DATE.sql
# Backup application files
tar -czf $BACKUP_DIR/files_$DATE.tar.gz src/
# Remove backups older than 30 days
find $BACKUP_DIR -name "*.sql" -mtime +30 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
echo "Backup completed: $DATE"
# Performance monitoring
Bestand: monitoring/check-performance.sh
#!/bin/bash
echo "=== Docker Container Stats ==="
docker stats --no-stream
echo -e "\n=== Disk Usage ==="
docker system df
echo -e "\n=== MySQL Performance ==="
docker compose exec mysql mysql -u root -p$MYSQL_ROOT_PASSWORD -e "SHOW PROCESSLIST;" 2>/dev/null
echo -e "\n=== Nginx Access Logs (Last 10) ==="
docker compose logs --tail=10 nginx
echo -e "\n=== PHP Error Logs (Last 10) ==="
docker compose logs --tail=10 app | grep -i error
# Advanced workflows
# Multi-environment setup
Bestand: docker-compose.staging.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: docker/php/Dockerfile
environment:
- APP_ENV=staging
- APP_DEBUG=false
volumes:
- ./src:/var/www/html
nginx:
ports:
- "8080:80"
volumes:
- ./docker/nginx/staging.conf:/etc/nginx/conf.d/default.conf
# Load balancing
Nginx load balancer - docker-compose.lb.yml
:
version: '3.8'
services:
app1:
build: .
volumes:
- ./src:/var/www/html
app2:
build: .
volumes:
- ./src:/var/www/html
nginx-lb:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./docker/nginx/load-balancer.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app1
- app2
Load balancer configuratie:
upstream backend {
server app1:9000;
server app2:9000;
}
server {
listen 80;
location ~ \.php$ {
fastcgi_pass backend;
# ... rest of PHP config
}
}
# Handige tools en extensies
# VS Code extensies
- Docker: Officiële Docker extensie
- Remote - Containers: Voor development in containers
- PHP Intelephense: PHP language support
- GitLens: Git integratie
- Thunder Client: API testing
- MySQL: Database management
# Command line tools
# Lazy Docker - GUI voor Docker
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
# ctop - Container monitoring
sudo wget https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-amd64 -O /usr/local/bin/ctop
sudo chmod +x /usr/local/bin/ctop
# dive - Docker image analysis
wget https://github.com/wagoodman/dive/releases/download/v0.10.0/dive_0.10.0_linux_amd64.deb
sudo apt install ./dive_0.10.0_linux_amd64.deb
# Helpful aliases
Toevoegen aan ~/.bashrc
of ~/.zshrc
:
# Docker aliases
alias dps='docker ps'
alias dls='docker images'
alias dcp='docker compose'
alias dcup='docker compose up -d'
alias dcdown='docker compose down'
alias dcbuild='docker compose build'
alias dclogs='docker compose logs -f'
# Project navigation
alias devcd='cd ~/development/projects'
alias devls='ls -la ~/development/projects'
# Quick project creation
newphp() {
~/development/scripts/new-project.sh $1
cd ~/development/projects/$1
}
# Security best practices
# Container security
- Non-root user in containers
- Minimal base images (Alpine Linux)
- Regular updates van base images
- Secrets management voor gevoelige data
- Network segmentation met Docker networks
# Database security
-- Create limited user instead of using root
CREATE USER 'app_user'@'%' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'%';
FLUSH PRIVILEGES;
# Environment variabelen
Gebruik .env.example
als template:
# Copy to .env and fill in your values
PROJECT_NAME=
MYSQL_ROOT_PASSWORD=
MYSQL_PASSWORD=
JWT_SECRET=
APP_KEY=
# Conclusie
Deze setup geeft je een professionele, schaalbare ontwikkelomgeving die:
- ✅ Portable is tussen verschillende machines
- ✅ Isolatie biedt per project
- ✅ Gemakkelijk schaalbaar is naar productie
- ✅ Modern is met Docker best practices
- ✅ Flexibel genoeg voor verschillende PHP frameworks
- ✅ DevOps-ready met CI/CD integratie
# Next steps voor jouw situatie:
- Start klein: Begin met de basis setup
- Experimenteer: Probeer verschillende PHP frameworks
- Automatiseer: Gebruik de scripts voor nieuwe projecten
- Monitor: Implementeer logging en monitoring
- Scale up: Voeg load balancing toe wanneer nodig
Deze moderne aanpak geeft je de flexibiliteit om te groeien van lokale development naar enterprise-level deployment, terwijl je dezelfde tools en workflows behoudt.
Guide geschreven voor Linux Mint/Ubuntu 2025 - Docker-based PHP development environment
Reacties (0 )
Geen reacties beschikbaar.
Log in om een reactie te plaatsen.