Set up OpenLiteSpeed with Docker containers for development

Intermediate 45 min Apr 27, 2026 86 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Deploy OpenLiteSpeed web server in Docker containers with PHP-FPM, SSL certificates, and persistent volumes for scalable development environments.

Prerequisites

  • Docker and Docker Compose installed
  • Basic familiarity with web servers
  • Understanding of container networking

What this solves

OpenLiteSpeed with Docker containers provides a flexible development environment that mirrors production setups while maintaining isolation and portability. This approach lets you test web applications with realistic performance characteristics, manage multiple PHP versions, and implement SSL/TLS configurations without affecting your host system.

Prerequisites

You need Docker and Docker Compose installed on your system. You also need basic familiarity with web server configuration and container networking concepts.

Step-by-step installation

Install Docker and Docker Compose

Install Docker engine and Docker Compose for container orchestration.

sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 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
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
sudo dnf update -y
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
Note: Log out and back in for the docker group membership to take effect.

Create project directory structure

Set up the directory structure for your OpenLiteSpeed Docker environment with proper permissions.

mkdir -p ~/openlitespeed-docker/{config,web,logs,ssl}
cd ~/openlitespeed-docker
chmod 755 ~/openlitespeed-docker
chmod 775 ~/openlitespeed-docker/{web,logs}

Create Docker Compose configuration

Configure the OpenLiteSpeed container with PHP-FPM, persistent volumes, and networking.

version: '3.8'

services:
  openlitespeed:
    image: litespeedtech/openlitespeed:1.7.19-lsphp82
    container_name: openlitespeed-web
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "7080:7080"
    volumes:
      - ./web:/var/www/vhosts/example.com/html:rw
      - ./config:/usr/local/lsws/conf/vhosts/example.com:rw
      - ./logs:/usr/local/lsws/logs:rw
      - ./ssl:/usr/local/lsws/conf/cert:rw
    environment:
      - TZ=UTC
      - DOMAIN=example.com
    networks:
      - lsws-network
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8.0
    container_name: openlitespeed-mysql
    restart: unless-stopped
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=secure_root_password_2024
      - MYSQL_DATABASE=development
      - MYSQL_USER=devuser
      - MYSQL_PASSWORD=dev_password_2024
    volumes:
      - mysql-data:/var/lib/mysql
      - ./config/mysql.cnf:/etc/mysql/conf.d/custom.cnf:ro
    networks:
      - lsws-network
    command: --default-authentication-plugin=mysql_native_password

  redis:
    image: redis:7.2-alpine
    container_name: openlitespeed-redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
      - ./config/redis.conf:/usr/local/etc/redis/redis.conf:ro
    networks:
      - lsws-network
    command: redis-server /usr/local/etc/redis/redis.conf

volumes:
  mysql-data:
    driver: local
  redis-data:
    driver: local

networks:
  lsws-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

Configure OpenLiteSpeed virtual host

Create the virtual host configuration for your development domain.

docRoot                   /var/www/vhosts/example.com/html
index                     {
  useServer               0
  indexFiles              index.php index.html
  autoIndex               0
}

scriptHandler             {
  add                     lsphp82 php
}

phpIniOverride            {
  php_admin_value upload_max_filesize 128M
  php_admin_value post_max_size 128M
  php_admin_value memory_limit 256M
  php_admin_value max_execution_time 300
  php_admin_value max_input_vars 3000
  php_admin_value session.save_handler redis
  php_admin_value session.save_path "tcp://redis:6379"
}

rewrite                   {
  enable                  1
  autoLoadHtaccess        1
  logLevel                0
}

accessControl             {
  allow                   *
}

realm                     SampleProtectedArea {
  userDB                  {
    location              /usr/local/lsws/conf/vhosts/example.com/htpasswd
  }
  groupDB                 {
    location              /usr/local/lsws/conf/vhosts/example.com/htgroup
  }
}

context /protected/ {
  location                /var/www/vhosts/example.com/html/protected/
  realm                   SampleProtectedArea
  authName                Protected
  required                user admin
}

context /api/ {
  type                    proxy
  uri                     http://127.0.0.1:8080/
  extraHeaders            X-Forwarded-Proto $scheme
  addDefaultCharset       off
}

Create MySQL configuration

Configure MySQL for development workloads with appropriate memory settings.

[mysqld]
innodb_buffer_pool_size = 256M
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_file_per_table = ON

max_connections = 100
connect_timeout = 60
wait_timeout = 300
interactive_timeout = 300

query_cache_size = 32M
query_cache_type = 1
query_cache_limit = 2M

tmp_table_size = 64M
max_heap_table_size = 64M

slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow.log
long_query_time = 2

character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

Configure Redis for session storage

Set up Redis configuration for PHP session handling and caching.

bind 0.0.0.0
protected-mode no
port 6379
tcp-backlog 511

timeout 300
tcp-keepalive 300

maxmemory 128mb
maxmemory-policy allkeys-lru
maxmemory-samples 5

save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb

appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec

no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

loglevel notice
logfile "/data/redis.log"

Create sample PHP application

Set up a test application to verify OpenLiteSpeed functionality and database connectivity.

<?php
// PHP Info and System Check
echo "<h1>OpenLiteSpeed Development Environment</h1>";
echo "<h2>PHP Information</h2>";
echo "PHP Version: " . phpversion() . "<br>";
echo "Server Software: " . $_SERVER['SERVER_SOFTWARE'] . "<br>";
echo "Document Root: " . $_SERVER['DOCUMENT_ROOT'] . "<br>";

// Database Connection Test
echo "<h2>Database Connection</h2>";
try {
    $pdo = new PDO('mysql:host=mysql;dbname=development', 'devuser', 'dev_password_2024');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "MySQL Connection: <span style='color: green;'>SUCCESS</span><br>";
    
    $stmt = $pdo->query('SELECT VERSION() as version');
    $result = $stmt->fetch();
    echo "MySQL Version: " . $result['version'] . "<br>";
} catch(PDOException $e) {
    echo "MySQL Connection: <span style='color: red;'>FAILED - " . $e->getMessage() . "</span><br>";
}

// Redis Connection Test
echo "<h2>Redis Connection</h2>";
try {
    $redis = new Redis();
    $redis->connect('redis', 6379);
    $redis->set('test_key', 'Hello from Redis!');
    $value = $redis->get('test_key');
    echo "Redis Connection: <span style='color: green;'>SUCCESS</span><br>";
    echo "Test Value: " . $value . "<br>";
    echo "Redis Version: " . $redis->info()['redis_version'] . "<br>";
} catch(Exception $e) {
    echo "Redis Connection: <span style='color: red;'>FAILED - " . $e->getMessage() . "</span><br>";
}

// Session Test
echo "<h2>Session Management</h2>";
session_start();
if (!isset($_SESSION['visit_count'])) {
    $_SESSION['visit_count'] = 1;
} else {
    $_SESSION['visit_count']++;
}
echo "Session ID: " . session_id() . "<br>";
echo "Visit Count: " . $_SESSION['visit_count'] . "<br>";
echo "Session Handler: " . ini_get('session.save_handler') . "<br>";
echo "Session Path: " . ini_get('session.save_path') . "<br>";

// PHP Extensions
echo "<h2>Available PHP Extensions</h2>";
$extensions = get_loaded_extensions();
sort($extensions);
foreach($extensions as $ext) {
    echo $ext . ", ";
}
?>

Create SSL certificate for HTTPS

Generate self-signed SSL certificates for development use.

cd ~/openlitespeed-docker/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout example.com.key \
  -out example.com.crt \
  -subj "/C=US/ST=Development/L=Local/O=Development/OU=IT/CN=example.com/emailAddress=admin@example.com"
chmod 644 example.com.crt
chmod 600 example.com.key
Security Note: Self-signed certificates are only for development. Use proper certificates from Let's Encrypt or a certificate authority for production.

Configure firewall rules

Open the necessary ports for web traffic and administrative access.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 7080/tcp
sudo ufw reload
sudo ufw status
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=7080/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports

Launch the container stack

Start all services using Docker Compose with proper dependency order.

cd ~/openlitespeed-docker
docker compose up -d
docker compose ps
docker compose logs openlitespeed

Configure OpenLiteSpeed admin access

Set up admin credentials for the OpenLiteSpeed web interface.

docker exec -it openlitespeed-web /usr/local/lsws/admin/misc/admpass.sh
docker exec -it openlitespeed-web /usr/local/lsws/bin/lshttpd -r
Note: Choose a strong password for the admin interface. The web console will be available at https://203.0.113.10:7080

Configure SSL certificates and production deployment

Configure HTTPS virtual host

Add SSL configuration to your virtual host for secure connections.

docRoot                   /var/www/vhosts/example.com/html
vhSSL                     1
vhssl                     {
  keyFile                 /usr/local/lsws/conf/cert/example.com.key
  certFile                /usr/local/lsws/conf/cert/example.com.crt
  certChain               1
  sslProtocol             24
  ciphers                 ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS
  enableECDHE             Yes
  renegProtection         Yes
  sslSessionCache         Yes
  sslSessionTickets       Yes
  enableSpdy              15
  enableQuic              Yes
}

index                     {
  useServer               0
  indexFiles              index.php index.html
  autoIndex               0
}

scriptHandler             {
  add                     lsphp82 php
}

rewrite                   {
  enable                  1
  autoLoadHtaccess        1
  RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
}

Create production deployment script

Automate the deployment process with health checks and rollback capabilities.

#!/bin/bash

set -e

DOCKER_COMPOSE_FILE="docker-compose.yml"
DOCKER_COMPOSE_PROD_FILE="docker-compose.prod.yml"
BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)"

echo "=== OpenLiteSpeed Production Deployment ==="

Create backup

echo "Creating backup..." mkdir -p "$BACKUP_DIR" docker compose exec mysql mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases > "$BACKUP_DIR/mysql_backup.sql" docker compose exec redis redis-cli BGSAVE cp -r ./web "$BACKUP_DIR/" cp -r ./config "$BACKUP_DIR/"

Pull latest images

echo "Updating Docker images..." docker compose pull

Stop services gracefully

echo "Stopping services..." docker compose down --timeout 30

Start services with health checks

echo "Starting production services..." docker compose -f "$DOCKER_COMPOSE_FILE" -f "$DOCKER_COMPOSE_PROD_FILE" up -d

Wait for services to be healthy

echo "Waiting for services to be ready..." sleep 15

Health check

echo "Performing health checks..." if curl -f -s http://localhost:80 > /dev/null && curl -f -s https://localhost:443 -k > /dev/null; then echo "✓ Health checks passed" echo "✓ Deployment successful" # Cleanup old backups (keep last 5) find ./backups -maxdepth 1 -type d -name "20*" | sort -r | tail -n +6 | xargs rm -rf else echo "✗ Health checks failed - rolling back" docker compose down # Restore from backup logic would go here exit 1 fi echo "Deployment completed successfully!"
chmod +x ~/openlitespeed-docker/deploy.sh

Create production Docker Compose override

Configure production-specific settings with resource limits and security hardening.

version: '3.8'

services:
  openlitespeed:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    security_opt:
      - no-new-privileges:true
    read_only: false
    tmpfs:
      - /tmp:size=100M,noexec,nosuid,nodev
    environment:
      - TZ=UTC
      - DOMAIN=yourdomain.com
      - ENVIRONMENT=production
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  mysql:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.25'
          memory: 256M
    security_opt:
      - no-new-privileges:true
    environment:
      - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_root_password
    secrets:
      - mysql_root_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s

  redis:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
        reservations:
          cpus: '0.1'
          memory: 64M
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

secrets:
  mysql_root_password:
    file: ./secrets/mysql_root_password.txt

Configure log rotation and monitoring

Set up log management and basic monitoring for the containerized environment.

/home/user/openlitespeed-docker/logs/*.log {
    daily
    missingok
    rotate 7
    compress
    delaycompress
    copytruncate
    create 644 root root
    notifempty
    sharedscripts
    postrotate
        docker exec openlitespeed-web /usr/local/lsws/bin/lshttpd -g
    endscript
}
sudo cp ~/openlitespeed-docker/config/logrotate.conf /etc/logrotate.d/openlitespeed-docker
sudo chmod 644 /etc/logrotate.d/openlitespeed-docker

Configure PHP-FPM and web applications

Create PHP-FPM pool configuration

Configure dedicated PHP-FPM pools for different applications with resource isolation.

[app]
user = www-data
group = www-data

listen = 127.0.0.1:9001
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500

request_terminate_timeout = 300
request_slowlog_timeout = 60
slowlog = /var/log/php-fpm/app-slow.log

php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 300
php_admin_value[max_input_vars] = 3000

php_admin_value[session.save_handler] = redis
php_admin_value[session.save_path] = "tcp://redis:6379"

php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 128
php_admin_value[opcache.interned_strings_buffer] = 16
php_admin_value[opcache.max_accelerated_files] = 10000
php_admin_value[opcache.validate_timestamps] = 0

env[ENVIRONMENT] = production
env[DB_HOST] = mysql
env[DB_NAME] = development
env[DB_USER] = devuser
env[DB_PASS] = dev_password_2024
env[REDIS_HOST] = redis
env[REDIS_PORT] = 6379

Create advanced application example

Build a more comprehensive PHP application that demonstrates caching, database operations, and session management.

<?php
class DevEnvironment {
    private $pdo;
    private $redis;
    
    public function __construct() {
        $this->connectDatabase();
        $this->connectRedis();
        $this->initializeDatabase();
    }
    
    private function connectDatabase() {
        try {
            $this->pdo = new PDO(
                'mysql:host=mysql;dbname=development;charset=utf8mb4',
                'devuser',
                'dev_password_2024',
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_PERSISTENT => true
                ]
            );
        } catch(PDOException $e) {
            throw new Exception('Database connection failed: ' . $e->getMessage());
        }
    }
    
    private function connectRedis() {
        try {
            $this->redis = new Redis();
            $this->redis->connect('redis', 6379);
            $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_JSON);
        } catch(Exception $e) {
            throw new Exception('Redis connection failed: ' . $e->getMessage());
        }
    }
    
    private function initializeDatabase() {
        $this->pdo->exec(
            "CREATE TABLE IF NOT EXISTS page_views (
                id INT AUTO_INCREMENT PRIMARY KEY,
                ip_address VARCHAR(45),
                user_agent TEXT,
                page_url VARCHAR(255),
                visit_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_visit_time (visit_time),
                INDEX idx_ip_address (ip_address)
            ) ENGINE=InnoDB"
        );
    }
    
    public function recordPageView() {
        $stmt = $this->pdo->prepare(
            "INSERT INTO page_views (ip_address, user_agent, page_url) VALUES (?, ?, ?)"
        );
        $stmt->execute([
            $_SERVER['REMOTE_ADDR'],
            $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown',
            $_SERVER['REQUEST_URI']
        ]);
        
        // Increment Redis counter
        $this->redis->incr('total_page_views');
        $this->redis->incr('daily_views:' . date('Y-m-d'));
        $this->redis->expire('daily_views:' . date('Y-m-d'), 86400 * 7);
    }
    
    public function getStatistics() {
        $cache_key = 'statistics_' . date('Y-m-d_H');
        $stats = $this->redis->get($cache_key);
        
        if (!$stats) {
            $stmt = $this->pdo->query(
                "SELECT 
                    COUNT(*) as total_views,
                    COUNT(DISTINCT ip_address) as unique_visitors,
                    DATE(visit_time) as visit_date
                 FROM page_views 
                 WHERE visit_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
                 GROUP BY DATE(visit_time)
                 ORDER BY visit_date DESC"
            );
            $stats = $stmt->fetchAll();
            $this->redis->setex($cache_key, 3600, json_encode($stats));
        } else {
            $stats = json_decode($stats, true);
        }
        
        return $stats;
    }
    
    public function displayEnvironmentInfo() {
        session_start();
        $this->recordPageView();
        $stats = $this->getStatistics();
        
        echo "<h1>OpenLiteSpeed Development Environment</h1>";
        echo "<div style='display: grid; grid-template-columns: 1fr 1fr; gap: 20px;'>";
        
        // System Information
        echo "<div><h2>System Information</h2>";
        echo "<ul>";
        echo "<li>PHP Version: " . phpversion() . "</li>";
        echo "<li>Server: " . ($_SERVER['SERVER_SOFTWARE'] ?? 'Unknown') . "</li>";
        echo "<li>Environment: " . (getenv('ENVIRONMENT') ?: 'development') . "</li>";
        echo "<li>Load Average: " . sys_getloadavg()[0] . "</li>";
        echo "<li>Memory Usage: " . round(memory_get_usage(true)/1024/1024, 2) . " MB</li>";
        echo "</ul></div>";
        
        // Database Status
        echo "<div><h2>Database Status</h2>";
        $stmt = $this->pdo->query('SHOW STATUS LIKE "Threads_connected"');
        $connections = $stmt->fetch()['Value'];
        echo "<ul>";
        echo "<li>Active Connections: $connections</li>";
        echo "<li>Total Page Views: " . $this->redis->get('total_page_views') . "</li>";
        echo "<li>Today's Views: " . ($this->redis->get('daily_views:' . date('Y-m-d')) ?: 0) . "</li>";
        echo "</ul></div>";
        
        echo "</div>";
        
        // Statistics Table
        if (!empty($stats)) {
            echo "<h2>7-Day Statistics</h2>";
            echo "<table border='1' style='width:100%; border-collapse: collapse;'>";
            echo "<tr><th>Date</th><th>Total Views</th><th>Unique Visitors</th></tr>";
            foreach($stats as $row) {
                echo "<tr>";
                echo "<td>" . $row['visit_date'] . "</td>";
                echo "<td>" . $row['total_views'] . "</td>";
                echo "<td>" . $row['unique_visitors'] . "</td>";
                echo "</tr>";
            }
            echo "</table>";
        }
    }
}

try {
    $app = new DevEnvironment();
    $app->displayEnvironmentInfo();
} catch(Exception $e) {
    echo "<h1>Application Error</h1>";
    echo "<p style='color: red;'>" . htmlspecialchars($e->getMessage()) . "</p>";
}
?>

Verify your setup

Test all components of your OpenLiteSpeed Docker environment to ensure proper functionality.

# Check all containers are running
docker compose ps

Test HTTP access

curl -I http://localhost:80/

Test HTTPS access (ignore certificate warnings for development)

curl -I -k https://localhost:443/

Check application functionality

curl -s http://localhost:80/app.php | grep -o "<h1>.*</h1>"

Verify database connectivity

docker exec openlitespeed-mysql mysql -u devuser -p"dev_password_2024" -e "SELECT 'Database OK' as status;"

Check Redis functionality

docker exec openlitespeed-redis redis-cli ping

View container logs

docker compose logs --tail=50 openlitespeed

Check resource usage

docker stats --no-stream

Common issues

SymptomCauseFix
Container won't startPort conflicts or permission issuesCheck docker compose logs and ensure ports 80, 443, 7080 are free
502 Bad Gateway errorPHP-FPM not running or misconfiguredCheck PHP configuration in /usr/local/lsws/lsphp82/etc/php.ini
Database connection failedMySQL not ready or wrong credentialsWait for MySQL to fully start, verify environment variables
SSL certificate warningsSelf-signed certificateAccept warnings for development or use proper certificates
Session data not persistingRedis connection issuesVerify Redis container is running and accessible
File upload failuresPermission or size limitsCheck upload_max_filesize and directory permissions
Admin panel inaccessibleFirewall or container networkingEnsure port 7080 is open and container is running

Next steps

Running this in production?

Need this managed for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. See how we run infrastructure like this for European teams who need their development environments to mirror production.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.