Complete PHP Development Setup Guide voor Linux (2025)

🖋️ bert

Een moderne, schaalbare ontwikkelomgeving opzetten met Docker voor PHP-projecten op Linux Mint/Ubuntu.

# Inhoudsopgave

  1. Waarom Docker in 2025?
  2. Voorbereiding Linux systeem
  3. Docker installatie
  4. Base ontwikkelomgeving
  5. Geavanceerde configuratie
  6. Werkflow en best practices
  7. Troubleshooting
  8. 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

  1. Start nieuw project:

    ~/development/scripts/new-project.sh my-new-app
    cd ~/development/projects/my-new-app
    make up
  2. Dagelijkse ontwikkeling:

    make logs    # Check logs
    make shell   # Access container
    make mysql   # Database access
  3. Code wijzigingen:

    • Bestanden in src/ worden automatisch gesynchroniseerd
    • Geen container restart nodig voor PHP changes
    • Nginx configuratie wijzigingen vereisen restart

# 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

  1. Non-root user in containers
  2. Minimal base images (Alpine Linux)
  3. Regular updates van base images
  4. Secrets management voor gevoelige data
  5. 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:

  1. Start klein: Begin met de basis setup
  2. Experimenteer: Probeer verschillende PHP frameworks
  3. Automatiseer: Gebruik de scripts voor nieuwe projecten
  4. Monitor: Implementeer logging en monitoring
  5. 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.